useSelector hook not updating in React component despite Redux state changing

108 Views Asked by At

I'm creating a typing game in React and using Redux with Toolkit to manage state. In my DesktopSlice, I'm keeping track of whether certain panels are open or closed. In my **Console component, I'm using the useSelector hook to get the panels state from the desktop slice. The problem is that useSelector is always returning the initial state of the panels object, even when the state is updated. I have a Console component where I run commands, and I have a PanelsWrapper component where I show my Panels.

  • main.tsx
    • App.tsx
      • Console.tsx
      • PanelsWrapper.tsx

In PanelsWrapper Panels are displaying correctly but in my Console.tsx the state is always in the initial state.

Here's my DesktopSlice code:

import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import { PANELS } from '../../data/panels';

type VisiblePanels = {[key in PANELS]?: boolean} 

export type DesktopState = {
    panels: VisiblePanels
}

const initialState: DesktopState = {
    panels: {
        [PANELS.FRIDGE]: false,
        [PANELS.WORKTOP]: false,
        [PANELS.RECIPEBOOK]: false,
    }
};

export const desktopSlice = createSlice({
    name: "desktop",
    initialState,
    reducers: {
        toggleOpenPanel: (state, action: PayloadAction<{panelType: PANELS, opened?: boolean}>) => {
            state.panels[action.payload.panelType] = action.payload.opened;
        },
    },
});

export const { toggleOpenPanel} = desktopSlice.actions;
export default desktopSlice.reducer;

Here's my Console.tsx component code:

const Console : FC = () => {
  const inputRef = useRef<HTMLInputElement>(null);
  const [consoleLog,setConsoleLog] = useState<ICommandLine[]>([]);

  const openedPanels = useSelector((state: RootState) => state.desktop.panels);
  const dispatch = useDispatch();

  const focusConsole = () => {
    inputRef.current && inputRef.current.focus();
  }

  const enterCommand = () => {
    if(inputRef.current && inputRef.current.value){
        //get command value
        const command = inputRef.current.value;
        console.log(openedPanels);
        
        //interpret the command and get a response
        const res : commandResponse | undefined = runCommandInterpreter(command);
        if(res){
            const cmdLine : ICommandLine = {
                command: command,
                info: res.message,
                infoType: res.code
            }
            setConsoleLog(oldArray => [...oldArray,cmdLine]);
        }

        //clear the input
        inputRef.current.value="";
    } 
  }

  const runCommandInterpreter = (input:string) : commandResponse | undefined => {
    const command_args = input.trim().split(" ");
    const command = command_args[0];
    const command_parameters = command_args.slice(1);

    switch(command){
        case COMMAND.OPEN:
        case COMMAND.CLOSE:
            const panelName = command_parameters.join(" ");
            const panel = getPanelByName(panelName);
            const opened = command === COMMAND.OPEN;
            if(panel){
                dispatch(toggleOpenPanel({panelType: panel, opened: opened}));
                return sendResponse(COMMAND_CODE.OK);
            } else {
                return sendResponse(
                    COMMAND_CODE.ERROR, 
                    `${panelName} is not a valid PANEL name.`
                );
            }
    }
  }

  const handleKeyDown = (e:KeyboardEvent) => {
    switch(e.key){
        case 'Enter':
            enterCommand();
            break;
    }
  }

  useEffect(() => {
    inputRef.current?.addEventListener("keydown",handleKeyDown);
  },[]);

  return (
    <StyledConsole onClick={focusConsole}>
        <StyledConsoleHistory>
            {
                consoleLog.map((command,index) => (
                    <CommandLine 
                        key={`${index}-${command.command}`} 
                        command={command.command}
                        info={command.info}
                        infoType={command.infoType}
                    />
                ))
            }
        </StyledConsoleHistory>
        <StyledConsoleLabel>
            <StyledConsoleCommand>
                What would you like to do?
            </StyledConsoleCommand>
            <StyledConsoleInput type="text" ref={inputRef} />
        </StyledConsoleLabel>
    </StyledConsole>
  )
}

export default Console

The problem is that openedPanels always returns the initial state of desktopSlice.panels and doesn't update even when the state in the Redux store updates correctly. This issue does not occur in other components that also use useSelector like PanelsWrapper.

Can anyone help me understand what might be causing this issue?

EDIT I fixed the issue. The problem was that I was using addEventListener instead of onKeyDown on my component, and the component never got to have the updated state.

1

There are 1 best solutions below

1
Magnus On

Can you try const {panels} = useSelector((state: RootState) => state.desktop). I think you should destructure