Search code examples
javascriptreactjsreact-routerreact-context

Is there a way of accessing React context data in React Router 6 action function


I understand hooks can only be called inside the body of a function component but I was wondering if there is a way to access context data from inside react-router@6 action function. I have an action function that handles the user login form process. I'd like to use the setCurrentUser function which is coming from the app context in order to update the state with the logged in user data returned from the API

export const AuthAction = async ({ request }) => {
  const { setCurrentUser } = useAppContext()
  const data = await request.formData()
  const userData = {
    email: data.get('email'),
    password: data.get('password')
  }
  if (!userData.email || !userData.password) {
    throw json(
      { message: 'Please provide all values' },
      { status: 400, statusText: 'Please provide all values' }
    )
  }
  try {
    const { data } = await axios.post('/api/auth/login', userData)
    setCurrentUser(data)
    return redirect('/')
  } catch (error) {
    return error.response.data.msg
  }
}

appContext.js

const AppContextProvider = ({ children }) => {
  const [state, dispatchFn] = useReducer(reducer, initialState)

  const setCurrentUser = (user) => {
    dispatchFn({ type: 'SET_CURRENT_USER', payload: user })
  }

  return (
    <AppContext.Provider value={{setCurrentUser}}>
      {children}
    </AppContext.Provider>
  )
}

const useAppContext = () => {
  return useContext(AppContext)
}

export { AppContextProvider, useAppContext }

I tried getting the setCurrentUser function from the app context from within my action function but I get the Error: Invalid hook call. I am at a loss, I'd truly appreciate a nudge in the right direction.

What I am hoping to achieve is that upon successful login, to update my app context with the current logged in user information.


Solution

  • If the AppContextProvider component is rendered higher in the ReactTree than the RouterProvider component then the component rendering RouterProvider can access the AppContext value and pass it to the authAction function when the routes are instantaited.

    Example:

    index.js

    import { StrictMode } from "react";
    import { createRoot } from "react-dom/client";
    import App from "./App";
    import AppContextProvider from "./app.context";
    
    const rootElement = document.getElementById("root");
    const root = createRoot(rootElement);
    
    root.render(
      <StrictMode>
        <AppContextProvider>
          <App />
        </AppContextProvider>
      </StrictMode>
    );
    

    App

    Update the authAction to curry a passed appContext argument and return the action function. Access appContext via the useAppContext hook and pass to the route action creator.

    import {
      createBrowserRouter,
      RouterProvider,
      json,
      redirect,
    } from "react-router-dom";
    import { useAppContext } from "./app.context";
    
    const authAction = (appContext) => async ({ request }) => {
      const { setCurrentUser } = appContext;
      const data = await request.formData();
      const userData = {
        email: data.get("email"),
        password: data.get("password")
      };
      if (!userData.email || !userData.password) {
        throw json(
          { message: "Please provide all values" },
          { status: 400, statusText: "Please provide all values" }
        );
      }
      try {
        const { data } = await axios.post("/api/auth/login", userData);
        setCurrentUser(data);
        return redirect("/");
      } catch (error) {
        return error.response.data.msg;
      }
    };
    
    export default function App() {
      const appContext = useAppContext();
    
      const router = createBrowserRouter([
        ...
        {
          path: "/login",
          element: <Login />,
          action: authAction(appContext)
        },
        ...
      ]);
    
      return <RouterProvider router={router} />;
    }
    

    Edit is-there-a-way-of-accessing-react-context-data-in-react-router-6-action-function