#Should I use react-router's new action feature with react-hook-form?

1 messages · Page 1 of 1 (latest)

timid sentinel
#

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?

calm coral
#

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.

timid sentinel
#

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

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

timid sentinel
#

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

calm coral
timid sentinel
#

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 ?
calm coral
#

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

timid sentinel
#

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

calm coral
#

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

timid sentinel
#

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.

calm coral
#

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

timid sentinel
# calm coral From your action set the token in a cookie. Then read the cookie off the reques...

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 ?

calm coral
#

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

timid sentinel
#

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.

calm coral
#

Ok, if it's ok to lose the access token on refresh you could do it through actionData

timid sentinel
#

okay, cool, I'll try it tomorrow then

timid sentinel
#

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.