$30 off During Our Annual Pro Sale. View Details »

Jotaiで作ったフォームをApollo_Clientで投げたらいい感じだった件.pdf

 Jotaiで作ったフォームをApollo_Clientで投げたらいい感じだった件.pdf

asazu taiga

March 21, 2023
Tweet

More Decks by asazu taiga

Other Decks in Technology

Transcript

  1. Jotaiで作ったフォームを
    Apollo Clientで投げたら
    いい感じだった件
    Asazu Taiga @JADE K.K.  2023.3.21 Saitama.js@大宮
    【React】

    View Slide

  2. Saitama.js オフライン開催
    おめでとうございます!
    あーしもよろこんでいます
    ※公式の利用ルールに則り、非営利目的にて春日部つむぎさん素材を利用させていただいております

    View Slide

  3. タイトルに「いい感じ」とありますが、
    本当にいい感じなのか?は
    皆さんの目でお確かめください
    (登壇申し込み時点ではいい感じだと思ってた)

    View Slide

  4. 自己紹介
    ● Asazu Taiga
    ● twitter
    ○ @AsazuTaiga
    ● 所属
    ○ 株式会社JADE
    ● 最近好きなもの
    ○ VTuberの音楽
    ○ ボイロキッチン動画
    ■ (自炊しないのに)

    View Slide

  5. Agenda

    View Slide

  6. Agenda
    ● Jotaiの紹介
    ● Apollo Clientの紹介
    ● GraphQL Code Generatorの紹介
    ● Jotaiを使ったフォームの作成
    ● デモ
    ● ちょっといい感じポイント
    ● ちょっとどうかなポイント
    ● まとめ
    ● 質疑応答

    View Slide

  7. Jotaiの紹介

    View Slide

  8. Jotaiとは?
    ● グローバルな状態管理ライブラリ
    ● Recoilに触発されたアトミックなアプローチ
    ● アトムの組み合わせによる依存関係に基づく自動最適化
    ○ 再レンダリング問題の解決、メモ化が不要
    ● シンプルなuseStateの置き換えから複雑な
    アプリケーションまで幅広く対応
    ○ ユーティリティも豊富にある

    View Slide

  9. Jotai サンプル(公式から引用)
    import { atom, useAtom } from 'jotai'
    // Create your atoms and derivatives
    const textAtom = atom('hello')
    const uppercaseAtom = atom(
    (get) => get(textAtom).toUpperCase()
    )
    // Use them anywhere in your app
    const Input = () => {
    const [text, setText] = useAtom(textAtom)
    const handleChange = (e) => setText(e.target.value)
    return (

    )
    }

    View Slide

  10. Jotai サンプル(公式から引用)
    const Uppercase = () => {
    const [uppercase] = useAtom(uppercaseAtom)
    return (
    Uppercase: {uppercase}
    )
    }
    // Now you have the components
    const App = () => {
    return (
    <>


    >
    )
    }

    View Slide

  11. Apollo Clientの紹介

    View Slide

  12. Apollo Clientとは?
    ● JavaScript向けの包括的な状態管理ライブラリ
    ○ GraphQLクライアント
    ○ ローカルとリモートの両方のデータを管理
    ● データの取得、キャッシュ、変更を行い、
    UIを自動的に更新
    ● GraphQL Code Generatorと組み合わせることで、
    TypeScriptとの統合がより便利になる

    View Slide

  13. Apollo Client サンプル(公式から引用)
    const GET_LOCATIONS = gql`
    query GetLocations {
    locations {
    id
    name
    description
    photo
    }
    }
    `;

    View Slide

  14. Apollo Client サンプル(公式から引用、一部略)
    function DisplayLocations() {
    const { loading, error, data } = useQuery(GET_LOCATIONS);
    if (loading) return Loading...;
    if (error) return Error : {error.message};
    return data.locations.map(({ id, name, description, photo }) => (
    (中略...)
    ));
    }

    View Slide

  15. GraphQL Code Generatorの紹介

    View Slide

  16. GraphQL Code Generatorの雰囲気が伝わる図
    .graphql ドキュメント
    .graphqls スキーマ
    @graphql-codegen/cli
    各種プラグイン
    (TypeScriptの型定義生成、
    Apolloのhooksのラッパー生成
    etc…)
    generated.ts

    View Slide

  17. Jotaiを使ったフォームの作成

    View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. Jotaiを使ったフォームの作成
    ※jotai-formというatomWithValidate()を提供する
     utilライブラリもありますが、今回は使いません
    理由
    ● Jotai本体だけで十分に柔軟な表現ができる(気がする)
    ● validate()関数をアトムに追加できるだけなので
    恩恵がさほど大きくない(気がする)

    View Slide

  22. Jotaiを使ったフォームの作成 - nameフィールドの例
    // エラーメッセージを表示するかどうかを判定するためのatom(フォーム全体で共有)
    const shouldShowErrorMessageAtom = atom(false);
    const nameAtom = atom("");
    const nameSchema = z
    .string()
    .min(3, "名前は3文字以上で入力してください")
    .max(20, "名前は20文字以内で入力してください");

    View Slide

  23. Jotaiを使ったフォームの作成 - nameフィールドの例
    const nameErrorAtom = atom((get) => {
    const name = get(nameAtom);
    const result = nameSchema.safeParse(name);
    if (result.success) {
    return "";
    }
    return result.error.issues[0].message;
    });
    const nameDisplayErrorAtom = atom((get) => {
    const shouldShow = get(shouldShowErrorMessageAtom);
    const error = get(nameErrorAtom);
    return shouldShow ? error : "";
    });
    export const useName = () => {
    const [value, setValue] = useAtom(nameAtom);
    const errorMessage = useAtomValue(nameDisplayErrorAtom);
    return { value, setValue, errorMessage };
    };

    View Slide

  24. デモ

    View Slide

  25. デモ
    Website
    https://asazutaiga.github.io/jotai-examples/dist/index.
    html
    Repository
    https://github.com/asazutaiga/jotai-examples

    View Slide

  26. ちょっといい感じポイント

    View Slide

  27. ちょっといい感じポイント
    ● クロスフィールドバリデーションが書きやすい
    ● Mutation引数等、型変換をかませた派生atomが便利
    ● 利用側のコードがシンプルになる
    ● 状態依存部分を最小限に切り出したコンポーネントを書
    けば再レンダリングは控えめ

    View Slide

  28. クロスフィールドバリデーションが書きやすい
    const passwordErrorAtom = atom((get) => {
    const password = get(passwordAtom);
    // example: cross field validation
    // 実際こんなことはしないと思うが、例として
    const name = get(nameAtom);
    if (name === password) {
    return "名前とパスワードは異なるものにしてください ";
    }
    return "";
    });

    View Slide

  29. Mutation引数等、型変換をかませた派生atomが便利
    // mutationの引数:GraphQL Code Generatorで生成した型を使うことができる
    // この例では単にそのまま値を各フィールドに渡しているだけだが、ここで適切な変換をかませることもできる
    const createUserVariablesAtom = atom((get) => ({
    input: {
    name: get(nameAtom),
    email: get(emailAtom),
    password: get(passwordAtom),
    },
    }));
    export const useCreateUserVariables = () => useAtomValue(createUserVariablesAtom);

    View Slide

  30. Mutation引数等、型変換をかませた派生atomが便利
    // 使う側はこんなにシンプル!
    // (いっそこれらをまとめたhookを作ってしまうのもよいが、ややオーバーか?)
    const variables = useCreateUserVariables();
    const [createUser, { loading }] = useCreateUserMutation({
    variables,
    });

    View Slide

  31. 利用側のコードがシンプルになる
    const NameInput = () => {
    const { value, setValue, errorMessage } = useName();
    return (

    label="Name"
    value={value}
    onChange={(e) => setValue(e.target.value)}
    />
    {errorMessage}

    );
    };
    const InputView = () => {
    return (
    e.preventDefault()}>








    );
    };

    View Slide

  32. 状態依存部分を最小限に切り出したコンポーネントを
    書けば再レンダリングは控えめ
    デモで見てもらったように、
    フォーム全体の再レンダリングはリセット時や
    モード(入力or確認)の切り替え時にしかおきません

    View Slide

  33. ちょっとどうかなポイント

    View Slide

  34. ちょっとどうかなポイント
    ● テンプレ的な記述を多く書くのが大変
    ● フィールドが増えた際に依存フィールドの修正もれそう
    ● グローバルで命名の衝突を避けようとすると変数名が長
    くなりがち

    View Slide

  35. テンプレ的な記述を多く書くのが大変
    すでに見てもらったように、一つのフィールドに対して
    ● 値atom
    ● 内部エラーatom
    ● 表示用エラーatom
    の3つは最低でも定義したくなる
    フィールドが増えれば、それだけatomの管理も大変に...

    View Slide

  36. フィールドが増えた際に依存フィールドの修正もれそう
    // フォームの入力値をリセットするためのatom
    const resetFormAtom = atom(null, (get, set) => {
    set(nameAtom, "");
    set(emailAtom, "");
    set(passwordAtom, "");
    set(shouldShowErrorMessageAtom, false);
    });
    addressが増えたら
    ここに足す...

    View Slide

  37. フィールドが増えた際に依存フィールドの修正もれそう
    // フォーム全体をまたいで、エラーがあるかかないかを判定するためのatom
    const hasErrorAtom = atom((get) => {
    const nameError = get(nameErrorAtom);
    const emailError = get(emailErrorAtom);
    const passwordError = get(passwordErrorAtom);
    return nameError || emailError || passwordError;
    }); ここもやんけ...

    View Slide

  38. グローバルで命名の衝突を避けようとするとatom名が長
    くなりがち
    ● フォームの種別+フィールド名(+小区分)+atom
    ○ createUserNameAtom
    ○ createUserNameErrorAtom
    ○ createUserNameDisplayErrorAtom

    View Slide

  39. まとめ

    View Slide

  40. まとめ
    ● 派生状態を作りやすいJotai(atomベースの状態管理)の
    特徴が、フォームの状態管理と結構マッチしている?
    ● Apolloのことを散々書きましたが、APIのInputの型が
    あればGraphQLでもRESTでも関係ないですね(今更)
    ● フォームの状態管理はみんな苦しんでいると思うので、
    ひとつのありうる選択肢として捉えるとよさそう

    View Slide

  41. Special Thanks
    春日部つむぎさん(かわいい)
    Chat GPT(スライド作成手伝ってくれた)

    View Slide

  42. 質疑応答

    View Slide