😊

Formik,Yup,Apolloで非同期バリーデーション作った

2021/12/16に公開

はじめに

この記事は mediba Advent Calendar 2021 19日目の記事です

株式会社medibaでフロントエンジニアをしているyangです。

弊社サービスのCMS開発でBEによるファイルのバリーデーションする必要がありました。
関連する一部のスタックは:

  • Formik
  • Yup
  • Apollo

やりたいこと

  • ファイルのアップロード = バリーデーションAPIを叩く
  • 同じファイル2回アップしてもバリデーションを行う(中身の変更とか)
  • 新たにアップロードされてない限りに、前のバリーデーション結果が出力されている

ファイルアップロードする際の処理

ファイルのアップロード = バリーデーションAPIを叩く ✅

FormikのFieldをカスタマイズして、ファイルアップロードされた際の制御を入れます。

  const handleFileChange = React.useCallback(
    async (event: any) => {
      if (event.target.files && event.target.files[0]) {
        const file = event.target.files[0];
        setLoading(true);
        await client.query({
           query: validationDocument,
	    variables: {
	      fileToBeCheck: file,
	    },
          fetchPolicy: 'network-only',
        });
        setLoading(false);
        form.setFieldValue(fileInput, file);
        form.setFieldTouched(fileInput, true); //Fieldが触れる前にバリーデーション発火しない
      }
    },[]);

ここではfetchPolicyをnetwork-onlyに設定し、
単純にAPIを叩いて、リスポンスをキャッシュに保存しています。

ファイル名に限らず、同じファイル2回アップしてもバリデーションが行う✅

inputを騙すため、下記のonClickイベントを作ります。

const onClickTrick = React.useCallback(
    (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
      event.currentTarget.value = '';
    },
    []
  );

忘れずに、inputに組み付け

<input ...name={fileInput} onClick={onClickTrickInput} onChange={handleFileChange}/>

バリーデーション関数を作る

新たにアップロードされてない限りに、前のバリーデーション結果が出力されている✅

中身は
バリーデーション結果をreadQueryでキャッシュから読み込みだけになります

const customizedValidator = (file: File, context: TestContext) => {
  const validationResult = client.readQuery({
    query: validationDocument,
    variables: {
      fileToBeCheck: file,
    },
  });
  if (validationResult) {
    const errorMessage = validationResult?.error_message;
    if (validationResult?.success) return true;
    if (Array.isArray(errorMessage) && errorMessage.length > 0) {
      return context.createError({
        message: errorMessage,
      });
    }
  }
  return false;  // apollo clientでエラーハンドリングしてるので、ここでfalseだけ返す
};

yupで加工

Yup.object({
 fileInput: yup.mixed().test(
   (file, context) => 
     file ? customizedValidator(file,context) : true
 ),
})

最後に

今回はかなり見たことないやり方でやってしまいましたw、
他のやり方もたくさんあると信じづづ、
ベストプラクティスではなくても、クリエティブ的でありたい

↓我が家の飼い猫↓

Discussion