Search code examples
javascriptreactjsreduxreact-router-dom

How to deal with error "could not find react-redux context value"?


I need to move redux Provider from index.js to App.jsx. When it was set in index.js, it was working fine, but I was asked to set it in the App component. The working state is the below:

index.js

import ReactDOM from "react-dom";
import { HashRouter as Router, Routes, Route } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./redux/store/index";

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <Router>
        <Routes>
          <Route path="/*" element={<App />} />
        </Routes>
      </Router>
    </Provider>
  </React.StrictMode>,
  document.getElementById("app")
);

App.jsx

return (
  <Routes>
    {isLoggedIn ? (
      <Route path="/" element={<Home onLogout={logout} />}>
        <Route index element={<Information />} />
        <Route path="users" element={<UserList />} />
      </Route>
    ) : (
      <Route path="/login" element={<Login onLogin={login} />} />
    )}
  </Routes>
);

redux store

import { createStore, combineReducers } from "redux";
import authReducer from "../reducers/authReducer";
import informationReducer from "../reducers/informationReducers";
import userListReducer from "../reducers/userListReducer";

const rootReducer = combineReducers({
  auth: authReducer,
  information: informationReducer,
  userList: userListReducer,
});

const store = createStore(rootReducer);
export default store;

When I moved it to App component, I got an error in the console, and nothing mounts.

Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>

Below is the after moving Provider to App component

index.js

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("app")
);

App.jsx

function App() {
  const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);

  const dispatch = useDispatch();
  const navigate = useNavigate();

  const login = () => {
    dispatch(login());
    navigate("/", { replace: true });
  };

  const logout = () => {
    dispatch(logout());
    navigate("/login", { replace: true });
  };

  useEffect(() => {
    const refresh_token = localStorage.getItem("refresh_token");

    if (!refresh_token) {
      navigate("/login");
      return;
    }

    axiosInstance
      .get("/auth/refresh", {
        headers: {
          Authorization: `Bearer ${refresh_token}`,
        },
      })
      .then(() => {
        dispatch(login());
      })
      .catch((error) => {
        navigate("/login");
        localStorage.clear();
        return Promise.reject(error);
      });
  }, [dispatch, navigate]);

  return (
    <Provider store={store}>
      <Router>
        <Routes>
          {isLoggedIn ? (
            <Route path="/" element={<Home onLogout={logout} />}>
              <Route index element={<Information/>} />
              <Route path="users" element={<UsersList />} />
            </Route>
          ) : (
            <Route path="/login" element={<Login onLogin={login} />} />
          )}
        </Routes>
      </Router>
    </Provider>
  );
}

export default App;

As a test, I deleted all the code from router and subsequent children, and just put a simple in between the provider, and no error occured

return (
  <Provider store={store}>
    <div> TEST </div>
  </Provider>
);

What could be the problem? Are my routes not set properly? react-redux: 7.2.6, react-router-dom: 6.2.1 react: 17.0.2 redux: 4.1.2


Solution

  • The App component can't render the Redux Provider component and attempt to select state from it. The context necessarily needs to be provided from higher up the ReactTree.

    The problem is const isLoggedIn = useSelector((state) => state.auth.isLoggedIn); in App. If pushing Provider down into App then the selection of isLoggedIn also needs to be pushed down. Fortunately this is trivial to solve and also leads to a more commonly used route protection implementation.

    Create PrivateRoutes and AnonymousRoutes components that read the isLoggedIn state and handle protecting the routes.

    Example:

    import { useSelector } from 'react-redux';
    import { Navigate, Outlet } from 'react-router-dom';
    
    export const PrivateRoutes = () => {
      const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
    
      if (isLoggedIn === undefined) {
        return null; // or loading indicator/spinner/etc
      }
    
      return isLoggedIn
        ? <Outlet />
        : <Navigate to="/login" replace />;
    
    };
    
    import { useSelector } from 'react-redux';
    import { Navigate, Outlet } from 'react-router-dom';
    
    export const AnonymousRoutes = () => {
      const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
    
      if (isLoggedIn === undefined) {
        return null; // or loading indicator/spinner/etc
      }
    
      return isLoggedIn
        ? <Navigate to="/" replace />
        : <Outlet />;
    };
    

    App

    function App() {
      ...
    
      return (
        <Provider store={store}>
          <Router>
            <Routes>
              <Route element={<PrivateRoutes />}>
                <Route path="/" element={<Home onLogout={logout} />}>
                  <Route index element={<Information/>} />
                  <Route path="users" element={<UsersList />} />
                </Route>
              </Route>
              <Route element={<AnonymousRoutes />}>
                <Route path="/login" element={<Login onLogin={login} />} />
              </Route>
            </Routes>
          </Router>
        </Provider>
      );
    }
    
    export default App;