Search code examples
javascriptreactjsreact-reduxreact-router-dom

Is there a way to use react-redux dispatch inside the loader function of react-router-dom?


I am using react-router-dom@6.8.1 along with redux-toolkit. I have a typical scenario where I'm making a backend call to fetch data (this is happening inside a loader function offered by RRD).

Once I fetch the data, I need to update the redux state inside the loader function. (This way, I can avoid another useEffect inside my function component). Here is a code snippet to give you a better understanding.

const router = createBrowserRouter([
  {
    path: "/",
    errorElement: <Error />,
    children: [
      {
        index: true,
        element: <Home />,
        loader: async () => {
          try {
            const response = await axios({
              method: "get",
              url: "some URL here",
            });
            // State update should happen here using dispatch. **********
            return response;
          } catch (e: any) {
            throw json(
              { message: "Error occured while fetching data" },
              { status: e.status }
            );
          }
        },
      },
    ],
  },
]);

Since we cannot use useDispatch outside of the React function components or custom hooks, I am wondering if there is any way to achieve this. I am glad to provide any more details like package.json etc, upon requirement.


Solution

  • You can do that by wrapping the rendering of your routes inside a component. For example, something like so in index.js:

    function Index() {
      const dispatch = useDispatch();
      const router = createBrowserRouter([
        {
          path: "/",
          errorElement: <Error />,
          children: [
            {
              index: true,
              element: <Home />,
              loader: async () => {
                try {
                  const response = await axios({
                    method: "get",
                    url: "some URL here",
                  });
                  dispatch({ type: "Something" });
                  return response;
                } catch (e: any) {
                  throw json({ message: "Error occured while fetching data" }, { status: e.status });
                }
              },
            },
          ],
        },
      ]);
      return <RouterProvider router={router} />;
    }
    
    // As of React 18
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(
      <Provider store={store}>
        <Index />
      </Provider>
    );
    

    If you want to pass dispatch as a parameter to a loader in a different file, you could use a function that returns the loader that React Router Dom expects, like so, for example:

    const loadereGetter = (dispatch) => async () => {
      try {
        const response = await axios({
          method: "get",
          url: "some URL here",
        });
        dispatch({ type: "Something" });
        return response;
      } catch (e: any) {
        throw json({ message: "Error occured while fetching data" }, { status: e.status });
      }
    };
    
    function Index() {
      const dispatch = useDispatch();
      const router = createBrowserRouter([
        {
          path: "/",
          errorElement: <Error />,
          children: [
            {
              index: true,
              element: <Home />,
              loader: loaderGetter(dispatch),
            },
          ],
        },
      ]);
      return <RouterProvider router={router} />;
    }
    
    // As of React 18
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(
      <Provider store={store}>
        <Index />
      </Provider>
    );