Closed27

form x Server Actions x useFormStateの探求

hajimismhajimism

ここに本格的にかいてあったはず
https://nextjs.org/learn/dashboard-app/improving-accessibility

hajimismhajimism
  const initialState = { message: null, errors: {} };
  const [state, dispatch] = useFormState(createInvoice, initialState);

reducerみたいな感じでactionとinitialStateを渡してstateとdispatchを生成する

hajimismhajimism

zod側でエラーメッセージを管理する

const FormSchema = z.object({
  id: z.string(),
  customerId: z.string({
    invalid_type_error: 'Please select a customer.',
  }),
  amount: z.coerce
    .number()
    .gt(0, { message: 'Please enter an amount greater than $0.' }),
  status: z.enum(['pending', 'paid'], {
    invalid_type_error: 'Please select an invoice status.',
  }),
  date: z.string(),
});
hajimismhajimism

actionのinterfaceが変わる。第一引数がState

export async function createInvoice(prevState: State, formData: FormData) {
hajimismhajimism

error.flatten()て何者だ

  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      message: 'Missing Fields. Failed to Create Invoice.',
    };
  }
hajimismhajimism

こうなるらしい

if (!result.success) {
  console.log(result.error.flatten());
}
/*
  {
    formErrors: [],
    fieldErrors: {
      name: ['Expected string, received null'],
      contactInfo: ['Invalid email']
    },
  }
*/
hajimismhajimism

だからこうすると。

      <div id="customer-error" aria-live="polite" aria-atomic="true">
        {state.errors?.customerId &&
          state.errors.customerId.map((error: string) => (
            <p className="mt-2 text-sm text-red-500" key={error}>
              {error}
            </p>
          ))}
      </div>
hajimismhajimism

bindするときはbindしてから渡すし

  const initialState = { message: null, errors: {} };
  const updateInvoiceWithId = updateInvoice.bind(null, invoice.id);
  const [state, dispatch] = useFormState(updateInvoiceWithId, initialState);

formStateの前にbinded argsがくる

export async function updateInvoice(
  id: string,
  prevState: State,
  formData: FormData,
) {
hajimismhajimism
hajimismhajimism

The form state is the value returned by the action when the form was last submitted. If the form has not yet been submitted, it is the initial state that you pass.

form stateはactionの返り値。未actionならinitialState。

hajimismhajimism

initialState: The value you want the state to be initially. It can be any serializable value. This argument is ignored after the action is first invoked.

Stateはシリアライズ可能な値

hajimismhajimism

When used with a framework that supports React Server Components, useFormState lets you make forms interactive before JavaScript has executed on the client. When used without Server Components, it is equivalent to component local state.

どゆことやろか、わかる気がするけどわからん

hajimismhajimism

actionのシグネチャ変わるのちょっとキモいけどな、Reducerと同じって考えたら許せるかな

hajimismhajimism
hajimismhajimism

formの中で呼び出せば、直近のform submittionの状態を取ってこれるのか、簡単でつよいな。

const { pending, data, method, action } = useFormStatus();
hajimismhajimism

例としてボタンの状態管理に呼ばれてる。いいね。

function Submit() {
  const status = useFormStatus();
  return <button disabled={status.pending}>Submit</button>
}
hajimismhajimism

自前でlocal state作ってたのがなくなりそう。素晴らしい。

このスクラップは2023/12/08にクローズされました