Skip to content

UseStore array or object values causing unintended behaviour #1148

@harry-whorlow

Description

@harry-whorlow
Contributor

Describe the bug

Calling useStore with a selector that returns an object or array field can cause weird behaviour. Results have varied from the maximum update depth, to field's un-focusing on handle change.

Interestingly form.Subscribe dose not result in these issues, so as a work around form.Subscribe can be used instead in limited cases. form.getFieldValue can also work but on first render if you're waiting for an async data fetch this can cause the default value to be rendered.

Steps to reproduce

  • create a form with a field that is of type array or object
  • create a useStore with a selector pointing to store.values

Expected behavior

Return the field variable in a manner that doesn’t break.

How often does this bug happen?

Every time / on hot reload

Screenshots or Videos

My production instance
onChange causing the field to be come unfocused. (unfortunately i cannot share more)
https://github.com/user-attachments/assets/c953fa68-13cf-4b6a-af8e-3b51efbead8e

Platform

MacOs Sequoia 15.3
Version 132.0.6834.160 (Official Build) (arm64)

TanStack Form adapter

None

TanStack Form version

0.41.3

TypeScript version

5.6.2

Additional context

I tried recreating this in a code sandbox https://codesandbox.io/p/sandbox/yv7zmh?file=%2Fsrc%2FStoreTest%2FWithStore%2Findex.tsx%3A8%2C24, but can't get the same results, but this happens every time in my work environment and also in others users environments. I'll try again to recreate this behaviour or at least another local instance in a code base I can share.

[edit] so I've recreated a simple vite app with just form at the latest version and can recreate the error, but it is a weird one. The Maximum update depth exceeded error throws only on the hot reload for the dev server, as in if I save a change and the dev server reloads the app the error throws. If I refresh the error goes away

Image

function App() {
  const form = useForm({
    defaultValues: { name: '', array: [{ hi: '' }], obj: {} },
  });

  const data = useStore(form.store, (store) => store.values);

  return (
    <Stack gap={2}>
      <> {JSON.stringify(data)}</>

      <form.Field name="array" mode="array">
        {(field) => {
          return (
            <Stack gap={0.5}>
              {field.state.value.map((_, i) => (
                <button key={i} onClick={() => field.pushValue({ hi: '' })}>
                  Add Value
                </button>
              ))}
            </Stack>
          );
        }}
      </form.Field>

      <form.Field
        name="name"
        listeners={{
          onChange: ({ value }) => {
            console.log(value);
          },
        }}>
        {({ handleChange, state }) => <input value={state.value} onChange={(e) => handleChange(e.target.value)} />}
      </form.Field>
    </Stack>
  );
}

I currently cannot recreate the un-focusing bug I have in my production server but i will continue to investigate, as it's pretty debilitating.

Activity

harry-whorlow

harry-whorlow commented on Feb 12, 2025

@harry-whorlow
ContributorAuthor
beeirl

beeirl commented on Feb 12, 2025

@beeirl

Here are my observations:

  • if there's only an array field, react crashes with that error, even after a refresh. (forked and extended the tanstack array example to reproduce this issue)
  • if there are additional fields (e.g., text fields alongside an array field), hot reload fails with that error, but a refresh fixes it.
crutchcorn

crutchcorn commented on Feb 21, 2025

@crutchcorn
Member

Oooooooh. I sat down to debug this and I just realized why this is happening...

The TLDR is that by using a non-stable value (IE a new object or array reference on every render) we can't detect when it should stop re-rendering :/

We could introduce an ESLint plugin that prevents users from doing stuff like this, but at very least we should document that useStore should be treated more like useSelector and not return any data transforms.

@harry-whorlow @beeirl do either of you want to add this to our docs where you best see fit? (maybe even in source code so it's easily seen in auto-gen'd reference docs?)

harry-whorlow

harry-whorlow commented on Feb 21, 2025

@harry-whorlow
ContributorAuthor

@crutchcorn sure thing, I'll draft one now🤟

So basically this mapping here is causing infinite re-renders.
Image

beeirl

beeirl commented on Feb 24, 2025

@beeirl

@crutchcorn makes sense. should've figured that out myself. so the solution here is to use multiple useStore hooks if the selection includes non-stable values e.g.

const people = useStore(form.store, (state) => state.values.people)
const rest = useStore(form.store, (state) => ({
   foo: state.values.foo,
   bar: state.values.bar,
})
karan042

karan042 commented on Jul 8, 2025

@karan042

Would this also affect code like

  const errors = useStore(field.store, (state) => state.meta.errors)

since it is returning an array.

Taken from docs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @crutchcorn@beeirl@harry-whorlow@karan042

        Issue actions

          UseStore array or object values causing unintended behaviour · Issue #1148 · TanStack/form