Hi frens, I'm trying the new react-router's features (loaders and actions), and while loaders seem pretty cool and easy to understand I can't really understand when it's better to use actions and whether it's actually needed. In my app I'm using react-hook-form so a form has onSubmit={handleSubmit(submitLoginForm)} and inside submitLoginForm I'm getting the form's payload in JSON form and send it to my API server.
With router's action feature I'm getting the formdata and request object instead of the JSON, and in addition handleSubmit function of react-hook-form isn't being called, and I assume it must be called for all the validation and stuff.
So is the action feature of the router really improves developer experience in my case? Maybe there are specific cases when it really improves the code, and in other cases it's better to simply stick to the old approach, and use only loaders without actions?
#Should I use react-router's new action feature with react-hook-form?
1 messages · Page 1 of 1 (latest)
I wouldn't recommend skipping actions. If you do, you'll miss out on all the major benefits of the data APIs - automatic revalidation, interruption handling, cancellation, etc.
Okay, I now see that it's also not mandatory for the react-hook-form to call the handleSubmit function, so I can just omit it.
The only problem I have for now is that I can't use my custom context inside of an action.
In my case I have an AuthContext that has a function signIn which sends a /login request to my API server.
So previously I had this code in my component
const auth = useAuth(); // returns context value
const submitLoginForm = async (loginPayload: LoginPayload) => {
auth.signIn(loginPayload, () => {
console.log('on success');
navigate('/dashboard');
});
};
but now I moved the auth.signIn... code into the action, and dunno how to get the auth context inside an action.
@calm coral
You can't get access to context inside loaders and actions since they exist outside of the react render tree. Do you know what that signIn method is doing? It's probably using a cookie or local storage value or something and making an API call. So you could grab that cookie in your action, or if it's a local storage value you could send it in an hidden input and get access to it via formData
The signIn function is using a react-query mutation function to send the /login request, and the mutation function updates local to context state, which is just the access_token value.
Maybe I can use actions only when I don't need to have access to any contexts or hooks, and in other cases I can just do everything the old way? @calm coral
You could do that if needed but I think you'll have a better time if you can do all mutations through actions. Check out this article for using react query inside loaders and actions - maybe it will provide some insight on how to connect the two: https://tkdodo.eu/blog/react-query-meets-react-router
QueryClient isn't a hook or context, so it can be passed in actions and loaders and then used there.
If I do it this way ofc I'll be able to use those queries and mutations, but I still won't be able to update a state inside the AuthContext, and this context provides all the protected routes with info whether we are logged in or not.
That's quite a challenge tbh.
You know what. Currently my routes are described outside of a component, just like this
const browserRouter = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{ path: '/about', element: <About /> },
...
and then I use it inside my component like so
<QueryClientProvider client={queryClient}>
<AuthProvider>
<RouterProvider router={browserRouter} />
...```
technically I could define those routes inside the component, maybe inside `useCallback` so it's ran only once.
Then I those actions and loaders would have a closure over everything I have in that component, including context.
Do you think it's ok to define routes inside a component like this ?
I wouldn't define the router inside the react lifecycle. It should be a singleton and you would likely run into issues with concurrent mode dual rendering
welp, then I dunno what other options I have, moving code that sends requests to the server into the action doesn't allow to use any react specific stuff. So if I wanna switch some isLoggedIn state inside a context - I won't be able to do it from inside the action, hmmm, idk idk
Can you provide that from a higher loader that will revalidate after the action? Then you get that from useLoaderData inside react? Most of the time I would expect user authentication info to come from a root level loader, And then when you sign in it automatically revalidates
I dunno what you mean tbh.
In my case I just have a login form and when I submit it - I send the payload to myserver.com/api/login endpoint, and if email and password are correct the response contains {access_token: "the token"}.
When the client receives this response I just wanna take the access_token and save it in local state inside of AuthContext. e.g. const [accessToken, setAccessToken] = useState(null) and the isLoggedIn is basically Boolean(accessToken), because if accessToken is true, then we are logged in.
okay, I found this thing
https://reactrouter.com/en/main/hooks/use-action-data
From your action set the token in a cookie. Then read the cookie off the request in the root loader, and set isLoggedIn on your loader data. Then grab that from useLoaderData
oh, it's kinda a workaround...
What do you think about using the useActionData like so...
Inside the Login component we submit the form, it triggers the action, action makes a request to the server, gets response, and then instead of redirection it returns the response, so that we then can use useActionData to get the response inside the same Login component, and then we set the state in the AuthContext and navigate to the next page.
Looks good ?
If you do that it's fully stateful and you lose logged in status on a reload. Using a cookie allows logins to persist across reloads or navigating away and coming back.
Using a cookie isn't a workaround, it's mimicking what you would do in a traditional server-side application
Ideally it would be an http only cookie but in a SPA that's not feasible
that's the main feature of it.
I'll have short living access_token that lives like 10 minutes, and it's going to be stored only in memory, so it's super safe.
Then I'll have a refresh_token that would live much longer, and it's sent from the server as a httpOnly cookie.
So every time we refresh the page - a new access_token will be issued using the refresh_token.
Ok, if it's ok to lose the access token on refresh you could do it through actionData
okay, cool, I'll try it tomorrow then
okay, I have completely forgotten one thing. To send the /login request I use useMutation, and it's hook, so there's no way I can use it inside the loader.
I thought that maybe I can mutate through the QueryClient, just like we can use fetchQuery on the QueryClient, but so far I can't find anything like that.