I am trying to implement both Redux and React-Router in a React blog project in which I fetch data from an API and store in Redux, however, it is not being rendered and no error is given.
I tried the following code in my App.jsx
const getRouter = (store) => {
return (
createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<Layout />}>
<Route
index
element={<PostList />}
errorElement={<Error />}
loader={(arg) => {
arg.post = store.getState().posts
}}
/>
<Route path='/posts' element={<PostForm />}>
<Route />
</Route>
<Route path="*" element={<NotFound />} />
</Route>
)
)
)
}
export default function App() {
<Provider store={store}>
<RouterProvider router={getRouter(store)} />
</Provider>
}
The route loader function accepts a single argument object with params
and request
properties. arg.post
isn't defined.
The App
component also needs to return the JSX it is intending to render.
If you need to access the Redux store context in a route loader then I'd suggest just importing store
and accessing directly in the loader function instead of closing over an instance of the store in the router. The issue with the code you have is that it will create a new router each time the store updates and the Provider
component rerenders.
Example:
const router = createBrowserRouter(
createRoutesFromElements(
<Route element={<Layout />}>
<Route
path="/"
element={<PostList />}
errorElement={<Error />}
loader={({ params, request }) => {
const { posts } = store.getState();
// return some value to `PostList` component
}}
/>
<Route path='/posts' element={<PostForm />}>
...
</Route>
<Route path="*" element={<NotFound />} />
</Route>
)
)
export default function App() {
return (
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
);
}
If you really wanted to still use a function to get the router then at least memoize the router so it's provided as a stable reference and not re-created each time App
or Provider
rerender.
export default function App() {
const router = React.useMemo(() => getRouter(store), [store]);
return (
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
);
}
If you have no need to use the route loader other than to access and return a store value, there are better alternatives. You can simply use the useDispatch
and useSelector
hooks exported from react-redux
in the components that need to subscribe to state updates.
Example:
...
import { useSelector } from 'react-redux';
...
const PostList = () => {
const posts = useSelector(state => state.posts);
...
};
const router = createBrowserRouter(
createRoutesFromElements(
<Route element={<Layout />}>
<Route
path="/"
element={<PostList />}
errorElement={<Error />}
/>
<Route path='/posts' element={<PostForm />}>
...
</Route>
<Route path="*" element={<NotFound />} />
</Route>
)
)
export default function App() {
return (
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
);
}
The main benefit to using the useSelector
hook is that this is a subscription, when the selected state updates the subscribed components will be triggered to rerender with the current state value. This isn't the case when using store.getState
, it gets the current value only once, at the time getState
is called, and doesn't subscribe to future changes.