TanStackT
TanStack7mo ago
2 replies
popular-magenta

Validation not triggering correctly for conditionally rendered fields

Quick note upfront: I know this is a “me” problem, not a TanStack Form issue 😄 — but I could use some help figuring it out.

I’m running into a validation issue with conditionally rendered fields. For example, I have a radio button that toggles between “Yes” and “No.” When “Yes” is selected, additional form fields are shown — but those fields don’t trigger validation until I click somewhere on the screen or focus a field.

The same thing happens when I dynamically add fields to a field array. The validation does happen, but only after I interact with the form again (like clicking or focusing somewhere).

Here’s a simplified version of my code for reference. Happy to share more details if needed. Really appreciate any guidance!

Just to clarify the UX I’m aiming for:
- On blur or submit: Errors should appear when a user blurs a field or submits the form (validating all fields).
- While editing: If a user starts typing in a field that previously had an error, the error should disappear while they’re editing — and only reappear if the field is still invalid after blur.
- Dynamic fields: When new fields are rendered conditionally (e.g. based on a selection or added to a field array), they should follow the same validation rules as the rest of the form — without needing an extra click or interaction to trigger validation.

^ Everything works as expected, except for the dynamic field portion

Composed text field component:

import TextField from "@custom-library/textfield";
import { useStore } from "@tanstack/react-form";
import React from "react";
import { useFieldContext, useFormContext } from "../../../../hooks/form";

export const FormTextField = ({
  ...textFieldProps
}: Omit<
  React.ComponentProps<typeof TextField>,
  "name" | "onChange" | "value" | "onBlur"
>) => {
  const field = useFieldContext<string>();
  const form = useFormContext();

  const isValid = useStore(field.store, (state) => state.meta.isValid);
  const isBlurred = useStore(field.store, (state) => state.meta.isBlurred);
  const fieldError = useStore(
    field.store,
    (state) => state.meta.errors?.[0]?.message,
  );
  const submissionAttempts = useStore(
    field.form.store,
    (state) => state.submissionAttempts,
  );

  const displayError =
    (isBlurred || submissionAttempts > 0) && !isValid ? fieldError : undefined;

  return (
    <form.Subscribe selector={(state) => state.isSubmitting}>
      {(isSubmitting) => (

          <TextField
            {...textFieldProps}
            name={field.name}
            value={field.state.value || ""}
            onChange={(event) => field.handleChange(event.target.value)}
            onBlur={field.handleBlur}
            disabled={isSubmitting || textFieldProps.disabled}
            error={displayError}
          />
      )}
    </form.Subscribe>
  );
};


Form component (I’m using Zod): 


export const ExampleForm = () => {
  const form = useAppForm({
    defaultValues,
    validators: {
      onBlur: myZodSchema,
    },

    listeners: {
      onChange: ({ fieldApi }) => {
        if (fieldApi.state.meta.errors.length > 0) {
          fieldApi.setMeta((prev) => ({
            ...prev,
            errorMap: {},
          }));
        }
      },
    },

    onSubmit: async ({ value }) => {
      console.log("🔥 Form submitted:", value);
    },
  });
Was this page helpful?