Search code examples
javascriptreactjsreduxreact-hooksredux-toolkit

Redux useSelector hook always return an empty array


I have a react component like this, where I have a websocket set up, and on websocket events I am trying to update state inside of Redux

const Body = () => {
    // Hook for Redux dispatch
    const dispatch = useDispatch();

    // REDUX reducers
    const workCenterAvailabilityQueue = useSelector(selectAllWorkCenterAvailabilities)

    // Add or Update workCenterAvailability
    let existingWorkCenter;
    const workCenterAvailabilityHandler = (id, data) => {
        console.log('id = ', id)
        console.log('data = ', data)
        console.log(workCenterAvailabilityQueue)

        existingWorkCenter = workCenterAvailabilityQueue.find(bm => bm.WorkCenter.WorkCenter_ID === id);
        console.log(existingWorkCenter)

        if (existingWorkCenter) {
            console.log('WorkCenter exists, updating data!')
            dispatch(updateWorkCenterAvailability({
                id: existingWorkCenter.id,
                changes: { Connection_Status: data.Connection_Status }})
            );
        }
        else {
            console.log('WorkCenter does not exist, creating one!')
            dispatch(addWorkCenterAvailability(data));

        }
    }

    // Debugging
    useEffect(() => {
        console.log('Updated state:', workCenterAvailabilityQueue);
        if(workCenterAvailabilityQueue[10]){
            console.log('workCenterAvailabilityQueue[10] -> ', workCenterAvailabilityQueue[10])
        }
        console.log('\n')
    }, [workCenterAvailabilityQueue]);

    // Web Socket setup
    useEffect(() => {
        console.log('connecting to Flask socket')
        const socket = io('http://localhost:5000');

        console.log('Start listening')
        socket.on('workCenterAvailability', (data) => {
            workCenterAvailabilityHandler(data.WorkCenter.WorkCenter_ID, data)
        });

        console.log('emitting message')
        socket.emit('requestWorkCenterAvailability');
        console.log('emitted!')

        // Clean up function
        return () => {
            socket.disconnect();
        };
    }, []);

So my problem is that when I try to use the handler function, the workCenterAvailabilityQueue always come back as an empty array, so when I try to find an object in the Queue it always fails

However based on the useEffect hook I have all the way at the bottom for debugging purposes, I can see that each time I add something to the Queue, it is in fact added to Redux

Why does my workCenterAvailabilityQueue not contain live information?


Solution

  • The second useEffect hook calls workCenterAvailabilityHandler in a "workCenterAvailability" event callback handler, which has a stale Javascript closure over the selected workCenterAvailabilityQueue state.

    You can resolve this issue a couple of ways:

    1. Import the store object and get the current state value in the workCenterAvailabilityHandler callback handler.

      import store from '../path/to/store';
      
      ...
      
      // Add or Update workCenterAvailability
      const workCenterAvailabilityHandler = (id, data) => {
        const state = store.getState();
        const const workCenterAvailabilityQueue = 
          selectAllWorkCenterAvailabilities(state);
      
        const existingWorkCenter = workCenterAvailabilityQueue.find(
          bm => bm.WorkCenter.WorkCenter_ID === id
        );
      
        if (existingWorkCenter) {
          dispatch(updateWorkCenterAvailability({
            id: existingWorkCenter.id,
            changes: { Connection_Status: data.Connection_Status }
          }));
        } else {
          dispatch(addWorkCenterAvailability(data));
        }
      };
      
      ...
      
      // Web Socket setup
      useEffect(() => {
        const socket = io('http://localhost:5000');
      
        socket.on('workCenterAvailability', (data) => {
          workCenterAvailabilityHandler(data.WorkCenter.WorkCenter_ID, data)
        });
      
        socket.emit('requestWorkCenterAvailability');
      
        // Clean up function
        return () => {
          socket.disconnect();
        };
      }, []);
      
      ...
      
    2. Use a React ref to cache the current workCenterAvailabilityQueue state value.

      ...
      
      const workCenterAvailabilityQueue = useSelector(
        selectAllWorkCenterAvailabilities
      );
      
      const workCenterAvailabilityQueueRef = React.useRef();
      
      useEffect(() => {
        workCenterAvailabilityQueueRef.current = workCenterAvailabilityQueue;
      }, [workCenterAvailabilityQueue]);
      
      ...
      
          // Add or Update workCenterAvailability
      const workCenterAvailabilityHandler = (id, data) => {
        const const workCenterAvailabilityQueue = 
          workCenterAvailabilityQueueRef.current;
      
        const existingWorkCenter = workCenterAvailabilityQueue.find(
          bm => bm.WorkCenter.WorkCenter_ID === id
        );
      
        if (existingWorkCenter) {
          dispatch(updateWorkCenterAvailability({
            id: existingWorkCenter.id,
            changes: { Connection_Status: data.Connection_Status }
          }));
        } else {
          dispatch(addWorkCenterAvailability(data));
        }
      };
      
      // Web Socket setup
      useEffect(() => {
        const socket = io('http://localhost:5000');
      
        socket.on('workCenterAvailability', (data) => {
          workCenterAvailabilityHandler(data.WorkCenter.WorkCenter_ID, data)
        });
      
        socket.emit('requestWorkCenterAvailability');
      
        // Clean up function
        return () => {
          socket.disconnect();
        };
      }, []);
      
      ...