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

1

7

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.

Gentianella answered 21/3, 2023 at 22:0 Comment(0)
J
7

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

Juicy answered 22/3, 2023 at 4:58 Comment(8)
Hey Drew, thank you for your help! I am just noticing that: I have to click the login button twice before the AuthAction redirects the user upon login. On the first click of the login button I can see from the react console that the state gets updated but I have to click the button again before the user is redirected. I apologise if this is a silly oversight on my part, I am just starting off with REACT, could it be because of the async function we are returning in the AuthAction function?Gentianella
@fatimaaminu Might just be a trivial oversight. Think you could create a codesandbox of your own that reproduces the issue you see that we could inspect live?Juicy
Thank you very much for all your assitance Drew! while setting up the sandbox I found the bug that was causing the lag on login. I am using boostrap Nav and on login it reloads the page which clears the state. Fixed it and all is well again!Gentianella
@fatimaaminu Ah yeah, the old "react-bootstrap nav.link renders a raw anchor tag" issue. Glad you found the cause of your other issue and have it resolved. Cheers.Juicy
@DrewReese what's the equivalent of line const authAction = (appContext) => async ({ request }) => { in the function syntax? I tried this but it's not working: export async function authAction({request, appContext}){Chapen
@D.Rattansingh const authAction = (appContext) => async ({ request }) => { ...} is a curried function, e.g. a function that returns an asynchronous function, so your example should look something more like export function authAction(appContext) { return async function({ request }) { ... } };. Does this make sense?Juicy
I tried your method, it is a good method. But one thing i would like to add is you should probably use useMemo hook on your router component, because I tried without it and my action function was running multiple times.Haskel
@Haskel Yes, absolutely you could/should do that as a performance optimization, if necessary. My answer here is just a barebones example.Juicy

© 2022 - 2024 — McMap. All rights reserved.