form icon indicating copy to clipboard operation
form copied to clipboard

form.setFieldValue doesn't trigger validation

Open amirhhashemi opened this issue 7 months ago • 9 comments

I noticed that calling form.setFieldValue("field-name", myvalue) doesn't trigger validation. Looking at the code, similar methods, like pushFieldValue, call form.validateField() after updating the value. That doesn't happen in setFieldValue().

amirhhashemi avatar May 23 '25 06:05 amirhhashemi

setFieldValue is meant for programmatically setting a field value, while pushFieldValue is an array specifically meant to handle change (like field.handleChange).

Could you provide a reproducible example of where the validation from setFieldValue is required / desired?

I can see reasoning for both not forcing validation and forcing validation. It's possible to add it as option parameter like the dontUpdateMeta prop, but I'd like to know what the use case is first.

LeCarbonator avatar May 23 '25 20:05 LeCarbonator

In my situation, I have a file input and I want to implement the logic for handling file changes outside of JSX for better readability. I cannot access field.handleChange outside the form.Field component unless I pass it to my handlePictureChange function. I found that form.setFieldValue is the closest alternative I could use, and I expected it to behave similarly to field.handleChange. It took me some time to realize why changing the file doesn't trigger validation.

In my opinion, form.setFieldValue should trigger validation by default, with an option like dontValidate available to disable it if needed. I don't understand why someone would want to change a field value programmatically without triggering validation. If the user has set up a validator, it should always be respected by default.

import { useState, type ChangeEvent } from "react";
import { useForm } from "@tanstack/react-form";
import { z } from "zod/v4-mini";

const schema = z.object({
  profilePicture: z.optional(z.file().check(z.maxSize(2_000_000))),
});

function ProfileForm() {
  const [avatarPreview, setAvatarPreview] = useState<string>();

  const form = useForm({
    defaultValues: { profilePicture: undefined } as {
      profilePicture: File | undefined;
    },
    validators: {
      onChange: schema,
    },
  });

  async function handlePictureChange(e: ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (file) {
      form.setFieldValue("profilePicture", file);
      const reader = new FileReader();
      reader.onload = () => {
        setAvatarPreview(reader.result as string);
      };
      reader.readAsDataURL(file);
    }
  }

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await form.handleSubmit();
      }}
    >
      <form.Field name="profilePicture">
        {(_field) => (
          <input
            type="file"
            accept="image/jpeg, image/png"
            onChange={handlePictureChange}
          />
        )}
      </form.Field>
    </form>
  );
}

amirhhashemi avatar May 24 '25 04:05 amirhhashemi

@amirhhashemi The closest alternative you can use is to pass the output of handleFieldChange to the updater parameter of field.handleChange. Something like:

import { useState, type ChangeEvent } from "react";
import { useForm } from "@tanstack/react-form";
import { z } from "zod/v4-mini";

const schema = z.object({
  profilePicture: z.optional(z.file().check(z.maxSize(2_000_000))),
});

function ProfileForm() {
  const [avatarPreview, setAvatarPreview] = useState<string>();

  const form = useForm({
    defaultValues: { profilePicture: undefined } as {
      profilePicture: File | undefined;
    },
    validators: {
      onChange: schema,
    },
  });

  function handlePictureChange(e: ChangeEvent<HTMLInputElement>): File | undefined {
    const file = e.target.files?.[0];
    if (file) {
      const reader = new FileReader();
      reader.onload = () => {
        setAvatarPreview(reader.result as string);
      };
      reader.readAsDataURL(file);
    }
    return file;
  }

  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await form.handleSubmit();
      }}
    >
      <form.Field name="profilePicture">
        {(field) => (
          <input
            type="file"
            accept="image/jpeg, image/png"
            onChange={(e) => field.handleChange(handlePictureChange(e))}
          />
        )}
      </form.Field>
    </form>
  );
}

Providing a property as shorthand may be worth looking into, but as for now, you can manually call form.validateField programmatically right after setFieldValue which allows for the same level of control as an option parameter.

field.handleChange will remain the intended way to trigger validation and listeners either way, and in your code snippet, that can be achieved while extracting the callback.

LeCarbonator avatar May 24 '25 08:05 LeCarbonator

Hi, did you find a solution to this?

I have the exact same issue. It should run the validation, but it doesn't! It's very frustrating! I can't figure out a way to deal with this.

2Bros-MX avatar May 25 '25 23:05 2Bros-MX

@LeCarbonator Thank you. Your solution worked for me. However, I still believe this could pose a problem. While I can't think of a specific use case, the existence of a method called form.setFieldValue that can be called from anywhere carries the risk that users might forget to trigger validation manually, allowing unvalidated data to be submitted. It should not be easy to bypass validation. If setFieldValue is not meant to trigger validation, it should at least be documented clearly.

@2Bros-MX

amirhhashemi avatar May 26 '25 04:05 amirhhashemi

@LeCarbonator Thank you. Your solution worked for me. However, I still believe this could pose a problem. While I can't think of a specific use case, the existence of a method called form.setFieldValue that can be called from anywhere carries the risk that users might forget to trigger validation manually, allowing unvalidated data to be submitted. It should not be easy to bypass validation. If setFieldValue is not meant to trigger validation, it should at least be documented clearly.

That is a fair point. I think I've changed my mind about the situation.

LeCarbonator avatar May 26 '25 04:05 LeCarbonator

Hi!

To add to the discussion, I would also like to mention that form.setFieldValue doesn't trigger the form's listeners.onChange either

Do we think that it should?

Thank you 🙂

tmarnet avatar Jun 06 '25 12:06 tmarnet

@LeCarbonator Thank you. Your solution worked for me. However, I still believe this could pose a problem. While I can't think of a specific use case, the existence of a method called form.setFieldValue that can be called from anywhere carries the risk that users might forget to trigger validation manually, allowing unvalidated data to be submitted. It should not be easy to bypass validation. If setFieldValue is not meant to trigger validation, it should at least be documented clearly.

That is a fair point. I think I've changed my mind about the situation.

I also expected form.setFieldValue to trigger respective onChange and onChangeListenTo validators. Looking forward to the future update where this is added, thank you for your work on this amazing lib!

robinli-nue avatar Jun 10 '25 00:06 robinli-nue

Hi!

To add to the discussion, I would also like to mention that form.setFieldValue doesn't trigger the form's listeners.onChange either

Do we think that it should?

Thank you 🙂

I agree that it should!

Planet-Jorge avatar Jun 16 '25 16:06 Planet-Jorge

+1 Any update about this one?

v1narth avatar Jul 05 '25 14:07 v1narth

Any updated on this issue 👀 ?

aburto22 avatar Aug 06 '25 12:08 aburto22

What we ended up doing was creating a utility function like this:

export function setFieldValue<
    TFormData,
    TOnMount extends undefined | FormValidateOrFn<TFormData>,
    TOnChange extends undefined | FormValidateOrFn<TFormData>,
    TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
    TOnBlur extends undefined | FormValidateOrFn<TFormData>,
    TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
    TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
    TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
    TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
    TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
    TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
    TSubmitMeta,
    TField extends DeepKeys<TFormData>,
>(
    form: ReactFormExtendedApi<
        TFormData,
        TOnMount,
        TOnChange,
        TOnChangeAsync,
        TOnBlur,
        TOnBlurAsync,
        TOnSubmit,
        TOnSubmitAsync,
        TOnDynamic,
        TOnDynamicAsync,
        TOnServer,
        TSubmitMeta
    >,
    field: TField,
    value: Updater<DeepValue<TFormData, TField>>,
) {
    form.resetField(field);
    form.setFieldValue(field, value);
}

And using canSubmitWhenInvalid: true on the form. It fixes our specific case (onSubmit validation errors not clearing when values are set with form.setFieldValue()), but probably won't work for all cases.

Would still love an update re: this issue though

fbg871 avatar Aug 07 '25 10:08 fbg871

I'd appreciate it if you could install the PR version to test this functionality. You can find the script in the PR reply

Here's the script for React, pulled from that reply:

npm i https://pkg.pr.new/@tanstack/react-form@1680

LeCarbonator avatar Aug 19 '25 06:08 LeCarbonator

I'd appreciate it if you could install the PR version to test this functionality. You can find the script in the PR reply

Here's the script for React, pulled from that reply:

npm i https://pkg.pr.new/@tanstack/react-form@1680

I tested this and I am seeing listeners being triggered. I only have a form onChangeAsync no field level validations so I can't verify the validators are being triggered since the validation is running anyway because the field I changed. But I can tell you the listeners sure are working now and they were not on the version I was using 1.19.x or something.

I documented my setup / use case in the PR you have open.

MelDommer avatar Aug 29 '25 20:08 MelDommer