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
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;