javascriptreactjsreduxreact-redux

Redux + Selector unknown returned the root state when called. This can lead to unnecessary rerenders. ... warning + where state is an array


I am using Redux 5 in my React 18 application. When using the useSelector() method from the react-redux package, I get the below warning in the browser console:

Selector unknown returned the root state when called. This can lead to unnecessary rerenders.
Selectors that return the entire state are almost certainly a mistake, as they will cause a rerender whenever *anything* in state changes.

I have also seen the redux documentation for this warning at https://react-redux.js.org/api/hooks#identity-function-state--state-check, however, the problem I am facing is that "the state is an array" in my code. Please see my files below:

Notes.jsx

import { useDispatch, useSelector } from 'react-redux';
import { toggleImportanceOf } from '../reducers/noteReducer';

const Note = ({ note, handleClick }) => {
  return (
    <li onClick={handleClick}>
      {note.content}
      <strong> {note.important ? 'important' : ''}</strong>
    </li>
  );
};

const Notes = () => {
  const dispatch = useDispatch();
  const notes = useSelector((state) => {
    console.log(state);  // THIS IS AN ARRAY
    return state;
  });

  return (
    <ul>
      {notes.map((note) => (
        <Note
          key={note.id}
          note={note}
          handleClick={() => dispatch(toggleImportanceOf(note.id))}
        />
      ))}
    </ul>
  );
};

export default Notes;

noteReducer.js is my reducer

const noteReducer = (state = [], action) => {
  switch (action.type) {
    case 'NEW_NOTE':
      return [...state, action.payload];
    case 'TOGGLE_IMPORTANCE': {
      const id = action.payload.id;
      const noteToChange = state.find((n) => n.id === id);
      const changedNote = {
        ...noteToChange,
        important: !noteToChange.important,
      };
      return state.map((note) => (note.id !== id ? note : changedNote));
    }
    default:
      return state;
  }
};

const generateId = () => Number((Math.random() * 1000000).toFixed(0));

export const createNote = (content) => {
  return {
    type: 'NEW_NOTE',
    payload: {
      content,
      important: false,
      id: generateId(),
    },
  };
};

export const toggleImportanceOf = (id) => {
  return {
    type: 'TOGGLE_IMPORTANCE',
    payload: { id },
  };
};

export default noteReducer;

main.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import App from './App';
import noteReducer from './reducers/noteReducer';

const store = createStore(noteReducer);

ReactDOM.createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
);

Please note, I know that createStore is deprecated. This is just an introductory lesson and we will be learning about the new method that has replaced the createStore method.

The array of the state is as below:

enter image description here

The array contents get filled in a form and the list is then rendered on the UI. Please see image below:

enter image description here

QUESTION: How can I fix this warning in my browser console for a 'state' that is an array?

enter image description here


Solution

  • Basically you should just not ever have a selector function that returns the entire state object.

    Nest the notes state under a notes property in the state and select that.

    import { StrictMode } from "react";
    import { createRoot } from "react-dom/client";
    import { Provider } from "react-redux";
    import { createStore, combineReducers } from "redux";
    import App from "./App";
    import noteReducer from "./reducers/noteReducer";
    
    // Create a root reducer function
    const rootReducer = combineReducers({
      notes: noteReducer // <-- note reducer forms state.notes
    });
    const store = createStore(rootReducer);
    
    const rootElement = document.getElementById("root");
    const root = createRoot(rootElement);
    
    root.render(
      <StrictMode>
        <Provider store={store}>
          <App />
        </Provider>
      </StrictMode>
    );
    
    import { useDispatch, useSelector } from "react-redux";
    import { toggleImportanceOf } from "./reducers/noteReducer";
    import { useEffect } from "react";
    
    const Note = ({ note, handleClick }) => {
      return (
        <li onClick={handleClick}>
          {note.content}
          <strong> {note.important ? "important" : ""}</strong>
        </li>
      );
    };
    
    const Notes = () => {
      const dispatch = useDispatch();
      const notes = useSelector((state) => state.notes); // <-- select state.notes array
    
      useEffect(() => {
        console.log(notes);
      }, [notes]);
    
      return (
        <ul>
          {notes.map((note) => (
            <Note
              key={note.id}
              note={note}
              handleClick={() => dispatch(toggleImportanceOf(note.id))}
            />
          ))}
        </ul>
      );
    };
    
    export default Notes;
    

    Note also that you are also learning what is effectively "deprecated" Redux. Modern Redux is written using the Redux-Toolkit library, which is more streamlined and much less boilerplatey.