Skip to content

Instantly share code, notes, and snippets.

@0u16

0u16/note.md Secret

Last active February 12, 2024 11:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0u16/d3e547a700978cd209139cd52f5a405f to your computer and use it in GitHub Desktop.
Save 0u16/d3e547a700978cd209139cd52f5a405f to your computer and use it in GitHub Desktop.
TanStack Form メモ

前提

  • react-hook-form+zodとの比較
  • react-hook-formはuseController + 個別のコンポーネント切り出し
    • 親からnamecontrolを渡して、子でuseController({ name, control })する
  • validatorはzod

TanStack Form

Docs: https://tanstack.com/form/latest/docs/overview
GitHub: https://github.com/tanstack/form

  • 最新版はv0.13.5
  • React, Vue, Solid対応
  • zod,yup,valibotのアダプタが提供されている

react-hook-formの辛いところ

  • number型のフィールド、useController使うとvalueAsNumber使えない
  • .transformが生えたスキーマを使った時のuseWatch, handleSubmitあたりの型不整合
  • useForm.resolverでスキーマが確定するので、動的にフィールドが変わるフォームはunion/discriminatedUnion/refine/superRefineで頑張ってる

usage

https://tanstack.com/form/latest/docs/framework/react/examples/zod

起点はuseForm
戻り値のformからほぼ全てのapiが生えている(rhfのcontrolみたいな感じ)

const form = useForm({
  defaultValues: {
    firstName: '',
    lastName: '',
  },
  onSubmit: async ({ value }) => {
    // Do something with form data
    console.log(value)
  },
  // Add a validator to support Zod usage in Form and Field
  validatorAdapter: zodValidator,
})

formOptions.validators.onChangeにzodのスキーマを渡せばform levelでバリデーションができるっぽいが現状まだちゃんと実装されていない

form.FieldのrenderPropでFieldApiがもらえるので、そこに生えているhandleChange, handleBlurとかvalueを渡せばOK

<form.Field
  name="firstName"
  validators={{
    onChange: z
      .string()
      .min(3, "First name must be at least 3 characters"),
    onChangeAsyncDebounceMs: 500,
    onChangeAsync: z.string().refine(
      async (value) => {
        await new Promise((resolve) => setTimeout(resolve, 1000));
        return !value.includes("error");
      },
      {
        message: "No 'error' allowed in first name",
      },
    ),
  }}
  children={(field) => {
    // Avoid hasty abstractions. Render props are great!
    return (
      <>
        {/** ここをMUIに差し替えればOK */}
        <label htmlFor={field.name}>First Name:</label>
        <input
          id={field.name}
          name={field.name}
          value={field.state.value}
          onBlur={field.handleBlur}
          onChange={(e) => field.handleChange(e.target.value)}
        />
        <FieldInfo field={field} />
      </>
    );
  }}
/>

react-hook-formのcontrolを受け取る代わりにfieldを受け取ってゴニョゴニョ、みたいな感じになると思われる
validatorまで共通化する場合はformとnameを受け取る感じか

form.Subscribeはうれしい
react-hook-formでフォームにエラーがある場合にボタンを押せなくする、みたいなことをする際、フォームの最上位でconst { formState: { isValid } } = useController({ name, control })すると、フォームのonChangeで毎回コンポーネントの再レンダリングが走ってしまう

なので重いフォームについてはこうやってコンポーネントを分けてformStateを監視し、フォーム側から使っていた

const SubmitButton: FC<Props> = ({ control }) = {
  const { isValid } = useFormState({ control });
  return <Button type="submit" disabled={!isValid}>Submit</Button>
}

form.Subscribeを使うと

<form.Subscribe selector={(state) => [state.canSubmit]>
  {([canSubmit]) => (
    <Button type="submit" disabled={!canSubmit}>Submit</Button>
  )}
</form.Subscribe>

その他

  • number型のフィールド辛い問題についてはそもそもvalueAsNumberみたいなヘルパも(今のところ)用意されていないので同じつらさはあると思う
  • .transformで型が変わる事象については、もしかしたらonSubmitのスキーマだけBaseSchema.transform()でうまいこと推論してくれるようになる可能性はあるかもしれない
  • フィールドレベルでvalidatorが指定できるので、動的にフィールドが変わるスキーマについてはやりやすいかと思った
    • その場合はフィールドで使われうるSchemaのみ共通化する感じになる?
    • 個人的には最初にでかいスキーマを定義する方が好みなので、formレベルでのバリデーションでもうまい具合に動くようになることを期待

所感

  • 当たり前だけどプロダクションで使うには機能が足りていない v1のalphaが出たくらいでまた触ろうかなという気持ち
    • form levelでのバリデーションに未対応とか型周りの不整合だったり ドキュメントもまだまだなので挙動について詳しく知るには実装を読むしかない
  • zod周りの使い勝手は今のところどっちもどっちな気がする
    • そもそもzodがフォームバリデータとしてどうなのか説はある yup,valibotも試してみたい気持ちになった
  • onChangeAsync, onChangeAsyncDebounceMsみたいなプロパティが生えてるので、非同期のバリデーションについてはこっちの方が書きやすいかもしれない
  • rhfに比べてコードが読みやすかった + まだあまり実装が大きくない + 機能も足りてないのでコントリビューションするには良いかも

参考資料

メモ

  • FieldApi.handleChange
    • FieldApi.setValue
      • FieldApi.validate
        • FormApi.validate
          • FormApi.validateSync
          • FormApi.validateAsync
        • FieldApi.validateSync
        • FieldApi.validateAsync
      • FieldApi.setFieldValue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment