Search code examples
javascriptreactjsreact-reduxreact-routerreact-router-dom

Using CreateBrowserRouter from React Router with Redux store


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

Solution

  • 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.