简体   繁体   English

React state 在 useEffect() 中触发 socket.on() 时被设置回初始 []

[英]React state being set back to initial [] when socket.on() is triggered inside useEffect()

I am a noob with React functional components and this is my first try with socket-io.我是 React 功能组件的菜鸟,这是我第一次尝试使用 socket-io。

I have a functional component which is an inbox that renders up to ten "alert" rows per page from an InboxList array.我有一个功能组件,它是一个收件箱,每页从InboxList数组呈现多达十个“警报”行。 I am using socket-io so that multiple clients can see in real-time whenever a specific row is clicked (the row turns grey).我正在使用 socket-io,以便在单击特定行时多个客户端可以实时查看(该行变为灰色)。

The problem is that most times the client (observing the row change, not clicking) will have their inbox empty (0 alerts in InboxList) once an alert is clicked.问题是大多数情况下,一旦单击警报,客户端(观察行更改,而不是单击)将使其收件箱为空(InboxList 中的 0 个警报)。

After some debugging, I found that as soon as the socket.on('alertClicked') is triggered, my inboxList state is empty (instead of having the same 10 alerts I am expecting).经过一些调试,我发现一旦触发了 socket.on('alertClicked'),我的 inboxList state 就为空(而不是我期望的相同的 10 个警报)。

This will cause my inboxListCopy inside greyOutAlert() to of course be empty.这将导致我在inboxListCopy greyOutAlert()中的 inboxListCopy 当然为空。

If anyone has suggestions that would be greatly appreciated.如果有人有建议,将不胜感激。

Functional component code for context:上下文的功能组件代码:

import React, { useState, useContext, useEffect } from 'react';
import io from 'socket.io-client';
import Axios from 'axios';
import Table from 'react-bootstrap/Table';
import Pagination from 'react-responsive-pagination';
import { useHistory } from 'react-router-dom';
import '../../App.css';

let socket;

export default function Inbox() {

    const [token, setToken] = useState(() => {
        return localStorage.getItem('auth-token');
    });
    const [currentPage, setCurrentPage] = useState(() => {
        return 1;
    });
    const [alertCount, setAlertCount] = useState(() => {
        return 5;
    });
    const [inboxList, setInboxList] = useState(() => {
        return [];
    });

    const history = useHistory();

    useEffect(() => {
        const connectionOptions = {
            "force new connection" : true,
            "reconnectionAttempts": "Infinity", 
            "timeout" : 10000,                  
            "transports" : ["websocket"] 
        }

        socket = io('http://localhost:5000', connectionOptions);
        
        return () => {
            socket.emit('disconnected');
            socket.off();
        }
    }, []);

    useEffect(()=>{
        fetchAlertCount();
     },[])

    useEffect(()=>{
        socket.on('alertClicked', (alertId) => {
            greyOutAlert(alertId);
          });
     },[])

    const greyOutAlert = (alertId) => {
        let inboxListCopy = [...inboxList];
        for (let i = 0; i < inboxListCopy.length; i++) {
            if (inboxListCopy[i]._id === alertId) {
                inboxListCopy[i].hasBeenReviewed = true;
                break;
            }
        }
        setInboxList(inboxListCopy);
    };

    useEffect(() => {
        fetchAlerts();
    },[currentPage]);

    const fetchAlertCount = async () => {
        const config = {
            headers: {
                'x-auth-token': token,
            }
        };
        const alertCountResponse = await Axios.get('http://localhost:5000/alertinbox/totalAlerts', config);
        setAlertCount(alertCountResponse.data.count);
    };

    const fetchAlerts = async () => {
        const config = {
            headers: {
                'x-auth-token': token,
                'page': currentPage
            }
        };
        const alertsResponse = await Axios.get('http://localhost:5000/alertinbox/', config);
        setInboxList(alertsResponse.data.alerts);
    };

    const handleClick = (alertId, messageType) => {

        socket.emit('clientClickedAlert', {alertId});

        switch (messageType) {
            case '2+ Minutes':
                history.push(`/2plusminutes/${alertId}`, {alertId});
                break;
            case 'SOS':
                history.push(`/sos/${alertId}`, {alertId});
                break;
            case 'Low Battery':
                history.push(`/lowbattery/${alertId}`, {alertId});
                break;
        };
    };

    return (
        <React.Fragment>
            <div id='inbox'>
                <Table>
                    <thead>
                        <tr>
                            <th>Alert Type</th>
                            <th>Customer Name</th>
                            <th>Timestamp</th>
                            <th>Vehicle</th>
                        </tr>
                    </thead>
                    <tbody>
                        {inboxList.map(function(alert, index){
                            return(
                                <tr key={index} onClick={() => handleClick(alert._id, alert.messageType)} className={alert.hasBeenReviewed ? 'darken' : 'lighten'}>
                                    <td>{alert.messageType}</td>
                                    <td>{alert.ownerName}</td>
                                    <td>{alert.createdAt}</td>
                                    <td>{alert.vehicle.year + ' ' + alert.vehicle.make + ' ' + alert.vehicle.model}</td>
                                </tr>
                            )
                        })}
                    </tbody>
                </Table>
            </div>
            <Pagination
                current={currentPage}
                total={Math.ceil(alertCount/10)}
                onPageChange={setCurrentPage}
                maxWidth={100}
            />
        </React.Fragment>
    )
}

I'll suggest you pass the exact initial state to the useState hook instead of a function like below:我建议您将确切的初始 state 传递给 useState 挂钩,而不是像下面的 function :

const [inboxList, setInboxList] = useState([]);

Okay I figured it out.好吧,我想通了。 If you ever need to update the state of something, and that new state depends on the previous state, make sure to use functional updates .如果您需要更新 state 的东西,而新的 state 依赖于以前的 state,请确保使用功能更新

In my code, I needed the current inboxList state (which is actually the previous state since useEffect() is run after a render) to compute the new inboxList state.在我的代码中,我需要当前的 inboxList state(实际上是之前的 state,因为 useEffect() 在渲染后运行)来计算新的 inboxList Z9ED39E2EA931586B6A985A6942EF57 To achieve this, I used the optional callback that is part of React's useState hook.为了实现这一点,我使用了作为 React 的 useState 钩子一部分的可选回调。

Correct code (useEffect() method updated):正确的代码(useEffect() 方法更新):

  useEffect(()=>{
    socket.on('alertClicked', (alertId) => {
        setInboxList(inboxList => {
            let inboxListCopy = [...inboxList];
            for (let i = 0; i < inboxListCopy.length; i++) {
                if (inboxListCopy[i]._id === alertId) {
                    inboxListCopy[i].hasBeenReviewed = true;
                    break;
                }
            }
            return inboxListCopy;
        });
      });
 },[])

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM