TanStack / form

🤖 Powerful and type-safe form state management for the web. TS/JS, React Form, Solid Form, Lit Form and Vue Form.
https://tanstack.com/form
MIT License
3.82k stars 347 forks source link

Accessing form error array using form.useStore and destructuring can cause react update depth issue #902

Open craigbehnke opened 3 months ago

craigbehnke commented 3 months ago

Describe the bug

When using destructuring, the form.useStore hook returns an error array that appears to change on every render. This then causes a react update depth issue.

Your minimal, reproducible example

https://stackblitz.com/edit/tanstack-form-oryfsj?file=README.md

Steps to reproduce

  1. Access the errors array using destructuring, like so:
    const {errors}: {errors: ValidationError[]} = form.useStore(state => ({
    errors: state.errors
    }))
  2. Use the errors array as a dependency to a react hook, such as useEffect or useMemo
  3. Observe that this react hook is perpetually called.

Please see the provided repro.

Expected behavior

As a user, I want the error array to be updated only when the error array changes.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

TanStack Form adapter

react-form

TanStack Form version

v0.29.1

TypeScript version

5.5.4

Additional context

No response

jackcoup commented 1 month ago

I also get this almost every time I use form.useStore((state) => state.values) really not ideal.

SiddOnKeys commented 3 weeks ago

did u find a fix or any way around it, its causing issues for me and I'm on a deadline lol

jackcoup commented 3 weeks ago

I believe I used form.useField() for each prop

Balastrong commented 3 weeks ago

I'm sorry but const errors = form.useStore((state) => state.errors) seems to be working fine, I think I don't understand where's the problem.

What feature are you trying to implement?

craigbehnke commented 3 weeks ago

@Balastrong It's been a bit, but what I was trying to achieve was to have 1 hook that would return the errors and other properties, i.e.

// My one hook to rule them all
const {isValid, isPristine, isSubmitted, isSubmitting,errors} = form.useStore(
        state => ({
            canSubmit: state.canSubmit,
            isPristine: state.isPristine,
            isSubmitted: state.isSubmitted,
            isSubmitting: state.isSubmitting,
            errors: state.errors
        })
    )

Because of this bug (and after a extended debugging session) I had to split it into 2 different hook calls so that it would work:

// The main hook
const {isValid, isPristine, isSubmitted, isSubmitting} = form.useStore(
        state => ({
            canSubmit: state.canSubmit,
            isPristine: state.isPristine,
            isSubmitted: state.isSubmitted,
            isSubmitting: state.isSubmitting
        })
    )
// The separate error hook to bypass the bug
const errors = form.useStore(state => state.errors)
Balastrong commented 3 weeks ago

Ok! I don't have a ready answer sadly, but I understood the issue, thanks :D

crutchcorn commented 3 days ago

This is a complex issue that is understandably a major headache for our users.

While researching a fix, we've also found an issue with how we handle derived state: #1036

Needless to say, we're working on a broader scope fix to this problem rather than a band-aid. This starts with:

https://github.com/TanStack/store/pull/40 And #1038 (via dogfooding https://github.com/TanStack/store/pull/40 )

This might be a larger body of work than anticipated, so please standby while we work hard on making this a reality