facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
228.59k stars 46.79k forks source link

useFormStatus only works from within the scope of the form it relates to #27980

Closed kylemh closed 7 months ago

kylemh commented 9 months ago

As the docs stipulate [here]:

The useFormStatus Hook only returns status information for a parent

and not for any rendered in the same component calling the Hook, or child components. Instead call useFormStatus from inside a component that is located inside .

I understand this is limited as a pitfall, but I also think it's a pretty big limitation. It's possible to render a form's submit button outside of the scope of the form... anywhere on a page even... with the form attribute. I wanted to suggest that we should be offered the same affordances with this API, otherwise we are forced to render our markup differently to use React to the fullest extent.

Talent30 commented 9 months ago

Agreed, if HTML allows us to connect the submit button and form together via an id, then the useFormStatus Hook should also allow us to do the same thing.

slorber commented 8 months ago

Article about this problem: https://allanlasser.com/posts/2024-01-26-avoid-using-reacts-useformstatus

sebmarkbage commented 8 months ago

Note that useFormStatus is a high level helper mainly intended for quick iteration.

The same features can be implemented with onSubmit + startTransition + useOptimistic. Which is really how I’d do any rich complex form.

kylemh commented 8 months ago

Good to know! Not knowing the hook's source code, I was unaware that it's composed from other APIs. If the useFormStatus hook will not change, it'd probably be a good idea to add to the documentation of useFormStatus on how one might get a form's status the correct way using onSubmit + startTransition + useOptimistic if useFormStatus cannot be used.

asherccohen commented 8 months ago

This is exactly the question I wanted to ask...thumbs up for the initiative

wesbos commented 8 months ago

I'm sure there is a valid reason, but can anyone explain why the useFormState can't also give us the useFormStatus data? Or why we can't pass some sort of ref to useFormStatus.

I would love to just have a form and know it's state and status in a single component

sebmarkbage commented 8 months ago

useFormState is a bit of a misnomer and I see it just as a temporary step until it just becomes useReducer on React.

While it wouldn't make sense for the useFormState to track a the status of a specific form. E.g. the form could be submitted with other form states / actions too, and the same useFormState can be used with many forms. In theory it could track the status of the last action that was dispatched against this state. Which is not 1:1 with useFormStatus which is for tracking the state of the form. It probably would make more sense if useFormState wasn't called something with "Form" in it.

In fact, you can just build a Hook around useFormState + useOptimistic that does this exact thing. That's kind of the thing here. You can build nice building blocks out of the primitives.

In theory useFormStatus could also track arbitrary forms. I think the way we would do that is with an id string to it that identifies a form by id. There's some tricky implementation details and it might not actually be good practice since you can have conflicting ids so you'd want to pair it with useId. However, what we're trying to model here is pretty close to the metal of the DOM and using global strings to refer to detached forms is how the DOM does that. E.g. we don't make you pass refs to <label for={...}>.

The built-ins in React is more about mapping DOM primitives than building abstractions out-of-the-box. So it would still make sense to maybe wrap useId + useFormStatus in some helper perhaps using Context in a richer system.

So useFormStatus would be more DOM-like, and useFormState would be more useOptimistic+useState like.

kylemh commented 7 months ago

Sounds like between #6620 eventually getting merged and the emergence of useActionState, this issue is no longer needed!

Either:

  1. Use actions and get access to form state from anywhere

OR

  1. Create your own useFormStatus abstraction that can be called from above the scope of a form and consumed from "above" or "beside" the form.