T
TanStack3mo ago
sensitive-blue

Recommended way to get fields errors in a "form composition" scenario

I used to only do that:
const field = useFieldContext<string>()
const errors = field.state.meta.errors
const field = useFieldContext<string>()
const errors = field.state.meta.errors
But while looking at the official form compostion example (https://tanstack.com/form/latest/docs/framework/react/examples/large-form?path=examples%2Freact%2Flarge-form%2Fsrc%2Fcomponents%2Ftext-fields.tsx) I noticed this pattern:
const field = useFieldContext<string>()
const errors = useStore(field.store, (state) => state.meta.errors)
const field = useFieldContext<string>()
const errors = useStore(field.store, (state) => state.meta.errors)
Any reason to prefer useStore in this scenario?
React TanStack Form Large Form Example | TanStack Form Docs
An example showing how to implement Large Form in React using TanStack Form.
12 Replies
complex-teal
complex-teal3mo ago
errors might be stale if you do not use the store to retreive it's value. See https://tanstack.com/form/latest/docs/framework/react/guides/reactivity
sensitive-blue
sensitive-blueOP3mo ago
It's very confusing because if you look at the link to the example again, field.state.value is used directly without getting it from the store. But field.state.meta might be stale?
fascinating-indigo
fascinating-indigo3mo ago
from what I understood, it‘s the combination of meta properties being 2 layers deep and the component being a new react component that causes it so in the field callback, you wouldn‘t need to use useStore, but in a react component you have to / should the same issue arises when accessing form properties from field.form.<x> the react context isn‘t reactive itself, which can be counter-intuitive to what you‘re likely used to for context hooks
sensitive-blue
sensitive-blueOP3mo ago
Ok, so to be safe this is what I use now:
export const TextField = (props: TextInputProps) => {
const field = useFieldContext<string>();
const form = useFormContext();
const meta = useStore(field.store, (state) => state.meta);
const submissionAttempts = useStore(form.store, (state) => state.submissionAttempts);
// ....
export const TextField = (props: TextInputProps) => {
const field = useFieldContext<string>();
const form = useFormContext();
const meta = useStore(field.store, (state) => state.meta);
const submissionAttempts = useStore(form.store, (state) => state.submissionAttempts);
// ....
It also implies that when used, this field should be a child of <form.AppForm>
complex-teal
complex-teal3mo ago
You can also get the from via field.form
const submissionAttempts = useStore(
field.form.store,
(state) => state.submissionAttempts,
);
const submissionAttempts = useStore(
field.form.store,
(state) => state.submissionAttempts,
);
This way you don't need to call useFormContext
sensitive-blue
sensitive-blueOP3mo ago
Oh nice! So many ways to get the same data though 😅
fascinating-indigo
fascinating-indigo3mo ago
it‘s technically doable without context too but idk who would call form.Field to then use field.form to get the form for reactivity‘s sake, I would always try to be as specific as possible with stores so if you only use isTouched and errors, then you don‘t need reloads on blur (which your current implementation would). Two useStores for each property reduces the rerenders
complex-teal
complex-teal3mo ago
- const meta = useStore(field.store, (state) => state.meta);
+ const errors = useStore(field.store, (state) => state.meta.errors);
+ const isBlurred = useStore(field.store, (state) => state.meta.isBlurred);
- const meta = useStore(field.store, (state) => state.meta);
+ const errors = useStore(field.store, (state) => state.meta.errors);
+ const isBlurred = useStore(field.store, (state) => state.meta.isBlurred);
fascinating-indigo
fascinating-indigo3mo ago
thanks for the snippet :Prayge:
complex-teal
complex-teal3mo ago
@Luca | LeCarbonator does it matter if you do this:
const errors = useStore(field.store, (state) => state.meta.errors);
const isBlurred = useStore(field.store, (state) => state.meta.isBlurred);
const errors = useStore(field.store, (state) => state.meta.errors);
const isBlurred = useStore(field.store, (state) => state.meta.isBlurred);
or this:
const [errors, isBlurred] = useStore(field.store, (state) => [state.meta.errors, state.meta.isBlurred]);
const [errors, isBlurred] = useStore(field.store, (state) => [state.meta.errors, state.meta.isBlurred]);
?
fascinating-indigo
fascinating-indigo3mo ago
only if you had used an object instead of an array (which is discouraged). These two should be equivalent but typescript tends to dislike it you‘d have to add an as const to type it as tuple
complex-teal
complex-teal3mo ago
Yeah, I'll stick with multiple lines - thanks 🙂

Did you find this page helpful?