Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Agenda

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Jotaiの紹介

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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 ( ) }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Apollo Clientの紹介

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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 }) => (
(中略...)
)); }

Slide 15

Slide 15 text

GraphQL Code Generatorの紹介

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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 }; };

Slide 24

Slide 24 text

デモ

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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);

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

まとめ

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

質疑応答