-
-
Notifications
You must be signed in to change notification settings - Fork 490
Description
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
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 commentedon Feb 12, 2025
Further details can be found at: https://discord.com/channels/719702312431386674/1100437019857014895/1339292949980250208
beeirl commentedon Feb 12, 2025
Here are my observations:
crutchcorn commentedon Feb 21, 2025
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 likeuseSelector
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 commentedon Feb 21, 2025
@crutchcorn sure thing, I'll draft one now🤟
So basically this mapping here is causing infinite re-renders.

beeirl commentedon Feb 24, 2025
@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.karan042 commentedon Jul 8, 2025
Would this also affect code like
since it is returning an array.
Taken from docs