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.
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} />;
}