skip to Main Content

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

2

Answers


  1. 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;
    
    Login or Signup to reply.
  2. For any component to access redux context , the component need to be wrapped inside Provider .

    After your new changes App component is not wrapped by the Provider , and you still are trying to access redux context , one example is on the following line

    const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
    

    A solution would be clearly to try and wrap the App logic with the Provider , this should do it

    function App() {
    
      // some code 
    
      return (
          // Remove Provider from here
          <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>
       
      );
    }
    
    // Add provider when exporting the component 
    
    export default () => <Provider store={store}> <App /> </Provider>
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search