20211129のReactに関する記事は10件です。

Vanilla JavaScript をずっと書いていたけど、Reactに入門してみた。

はじめに お疲れさまです。急に寒い日々が続いていますが、皆様はいかがお過ごしでしょうか? 半年間、業務量が倍になり、なかなか技術本を読む時間が取れなかったです。。 しかし、最近は落ち着いたので、本を読む時間が取れそうです。。!(あとゲームも) Reactを勉強することにしました。 素のJavaScript(かろうじて、ES2015の記法を習得)に書き慣れているのですが、 本腰を入れて有名なフレームワーク Reactを勉強してみることにしました。 今回は環境構築を中心に備忘録として投稿します。 Windows11 Visual Studio Code Node.js Reactの開発環境を整える。 vscodeを開き、新しいフォルダーを作成します。 cmdを起動し、以下のコマンドを入力します。(sampleは任意のフォルダ名になります。) npx react-create-app sample ローカル環境でReactを起動してみます。 cd sample #プロジェクトに移動 npm start #start コマンドを実行する 下のように、表示されると、環境構築成功です。 Hello World!を表示してみる。 せっかくなんで、Hello world!をしてみましょう。 現在のフォルダ構成は以下の状態になっていると思いますが、 srcフォルダ内のindex.js以外のファイルを削除してください。 sample ├─node_modulues ├─public └─src index.jsを以下のように変更しましょう index.jsx import React from 'react'; import ReactDOM from 'react-dom'; const App = () => { return( <> <h1>Hello World!</h1> </> ); }; ReactDOM.render(<App />, document.getElementById('root'); 下のように表示されましたでしょうか? 所感 今回は、Reactの環境構築とHello Worldの表示を行いました。 index.jsで少しだけ登場したJSX記法。。最初は、違和感ありましたが、 書いてみるとなかなか、面白いですね。 今回はh1タグ一つだけですが、これを関数コンポーネントを利用して画面の一つ一つを部品として とらえて、描画していきます。 終わりに 1回目の投稿で、Pythonを使った自動翻訳を紹介しました。 (動かないコードを公開する大失態をしでかしてますが。。) 2回目でReactの環境構築を紹介して、何をしてるんだと思います。 。。実は、Pythonの自動翻訳ですが、結構注文が来てます。「このファイル翻訳してください」と。 需要があるなら展開しようと考えたのですが、どうせならGUIで操作できるようにしたいな~と それなら、フロントエンドをReactで作ってみよう!と現在学習中です。 Webサーバーの環境はないので、デスクトップアプリとして展開するのですが、 Python寄りでEelを使用するか、JavaScript寄りでElectronを使用するか。ですね。 そこまで、急ぎの案件ではなく、業務改善の一環で行っていることですので、 手堅く作るならElectronでしょうか? EelとElectronも環境構築できる程度は学習しましたので、これらも投稿できればいいかなと 思っています。 長文になりましたが、Reactに入門してみました。 ここまで読んで頂いた方、ありがとうございました。 次の投稿は。。来年かな?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSONのデータを種類ごとにリスト表示する(TypeScript+React)

作成物 ソース(index.tsx除く) JSON ingredients.json { "title": "食材", "testData": [ { "name": "豚バラ", "kinds": "肉" }, { "name": "キャベツ", "kinds": "野菜" }, { "name": "鶏むね", "kinds": "肉" }, { "name": "牛カルビ", "kinds": "肉" }, { "name": "ほうれん草", "kinds": "野菜" }, { "name": "にんじん", "kinds": "野菜" } ] } tsx App.tsx import React from 'react'; import Data from './ingredients.json' import {makeListDivFromJson} from './makeListDivFromJson' export const App = () => { return ( <div> <h1>{Data.title}</h1> <div>{makeListDivFromJson(Data)}</div> </div> ); } makeListDivFromJson.tsx type JsonData = { testData: { name: string, kinds: string }[] }; export const makeListDivFromJson = (Data: JsonData): object[] => { let outDiv = []; let processedKinds: string[] = []//処理済み種類 for (let i = 0; i < Data.testData.length; i++) { let processedKindsCompare = '';//比較用 //処理済みの項目か確認 if (!processedKinds.includes(Data.testData[i].kinds)) { processedKinds.push(Data.testData[i].kinds); processedKindsCompare = Data.testData[i].kinds outDiv.push(<h2>{Data.testData[i].kinds}</h2>); let listData = []; //種類に一致するデータを格納 for (let j = 0; j < Data.testData.length; j++) { if (processedKindsCompare === Data.testData[j].kinds) { listData.push(<li>{Data.testData[j].name}</li>); } } outDiv.push(<ul>{listData}</ul>); } } return outDiv; } 解説 importでJSONのデータを読み込み、処理を行う関数に渡す。 関数内で確認対象のデータの種類が未処理か確認する。 未処理の場合、データの種類部分を取り出し、種類が一致するデータを探索し取り出してリストとしてdivに追加する。 詰まったところ <ul>で囲まれていない<li>要素を連結して最終的に出力divに追加するところ。 解決方法として<li>要素を一時的に配列に保持しておき、追加し終えた際に<ul>で囲んで出力divに追加するようにした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React.js useFormikの使い方を入門者向けに、詳しく詳しく解説!!

はじめに ReactでFormikを使う方法を丁寧に説明します。 各属性(initialValues, handleChange等々)の説明がざっくりしている記事ばかりで、よくわからない!!って方向けにじっくり説明してます。 React初心者でもあるので間違ってたら、逆にガンガン教えてください! 目次 [初期の入力フォーム] [formikをインストール] [initialValues, handleSubmit] [handleChange] [setValues] [validationSchema, Yup] [errors, touched] [API] [resetForm] [最後に] ① 初期の入力フォーム このショボい入力フォームでuseFormikを使用していきます。 chakra-uiというUIコンポーネントを使用していますが、HTMLのlabelやinputタグと大して変わらないので気にしないでください。 return ( <> <div> <FormLabel> <p>タイトル</p> </FormLabel> <Input type="text" /> <div> -------- contentとtagの入力フォームは省略 -------- <Button>送信</Button> </> ); ② formikをインストール まずはformikをインストールします。 // npmでインストール $ npm install formik --save // yarnでインストール $ yarn add formik ③ フォームの初期値を設定 (initialValues) 及び、送信ボタンを押した際の動作 (handleSubmit) ・useFormikのinitialValues属性に初期値を記述すると、各入力フォームの初期値が設定できます。 ・フォームの送信ボタンを押すと、onFinishメソッドが発火→handleSubmitのonSubmit内の処理が動作します。 // useFormikをインポート import { useFormik } from 'formik'; -------- 途中は省略 -------- // 入力フォームの初期値 (initialValues属性にセットする) const initialPostValue = { title: '', content: '', tag: '', }; // 下記追記 const { // ここにフォームで入力した値や初期値が入る values: newPostData,         // この場合、onFinishメソッドを叩くと、onSubmit内の処理が走る // フォームのデータ送信のアクションに用いられる  handleSubmit: onFinish, // TypeScriptなので、newPostDataの型定義をここで行なっている } = useFormik<PostData>({ // 初期値を設定 initialValues: initialPostValue,         // handleSubmit属性のonFinishが発火 → onSubmit内の処理が走る onSubmit: async () => { alert('フォーム送信成功!!'); }, }); return ( <> <div> <FormLabel> <p>タイトル</p> </FormLabel> <Input  type="text" // nameにtitleを追記 name="title"                 // newPostData.titleを追記 → initialPostValueで設定したtitleの初期値が入る value={newPostData.title} /> <div> -------- contentとtagの入力フォームは省略 -------- // onClickでonFinish()を追記 <Button onClick={() => onFinish()}>送信</Button> </> ); ④ フォームの入力データを取得 (handleChange) handleChangeを以下の様に追記するだけで、ユーザーが入力したデータがnewPostDataに入ってきます。 これでユーザーが入力したデータを簡単に取得出来ます。 console.log(newPostData);で確認すると、入力データが取得出来ている事がわかります。 -------- 以上省略 -------- const { values: newPostData, handleSubmit: onFinish, // 下記追記 handleChange, } = useFormik<PostData>({ initialValues: initialPostValue, onSubmit: async () => { alert('フォーム送信成功!!'); }, }); return ( <> <div> <FormLabel> <p>タイトル</p> </FormLabel> <Input type="text" name="title" value={newPostData.title} // これを全ての入力フォームに追記するだけで、newPostDataに入力データが入ってくる onChange={handleChange} /> <div> -------- 以下省略 -------- </> ); ⑤ フォームの入力データを更新 (setValues) setValuesを使えば、useStateの値を更新する時と同じ様な事が可能です。 今回の例では、投稿のテンプレートが欲しい時に「テンプレート」ボタンを押すと、入力フォームの内容がテンプレートの値に変更される処理です。 -------- 以上省略 -------- const { values: newPostData, // 下記追記 setValues: setNewPostData, handleSubmit: onFinish, handleChange, } = useFormik<PostData>({ initialValues: initialPostValue, onSubmit: async () => { alert('フォーム送信成功!!'); }, }); // テンプレートボタンをクリックすると、入力フォームにテンプレートの値が入力される。 const onClickMakeTemplate = () => { // useStateの値を更新する時と同じ様な事が出来ます setNewPostData({ title: 'Formikの使い方', content: 'formikをインストールします。その後・・・', tag: 'React', }); }; return ( <> -------- 途中の入力フォームは省略 -------- // テンプレート作成ボタンを追記 <Button onClick={() => onClickMakeTemplate()}>テンプレート</Button> <Button onClick={() => onFinish()}>送信</Button> </> ); ⑥ フォームのバリデーションを設定 (validationSchema, Yup) 今回はFormikとよく組み合わせて使用される、Yupというライブラリでバリデーションを行います。 まずは、Yupをインストールし、どこかにバリデーション用のファイルを作成して、それをimportしてバリデーションを使用します。 ファイル作成後は下記の様に記述してください。 // npmでYupをインストール $ npm install --save yup // yarnでYupをインストール $ yarn add yup バリデーションの条件とエラーメッセージを記述 // src/utils/validation/schema.tsファイル import * as Yup from 'yup'; export const PostSchema = Yup.object().shape({ // titleのフォームのバリデーション // 型はstringで入力 title: Yup.string()     // required()で入力必須にする .required('タイトルは入力必須です')     // max()で文字数制限を設ける .max(50, 'タイトルは50文字以内で入力してください'), -------- contentとtagは省略 -------- }); Yupのバリデーションファイルを作成後、importして使用します。 これでフォームの送信ボタンを押しても、入力内容に不備があればバリデーションに引っかかり、onSubmitの処理が走るのを防げます。 import { useFormik } from 'formik'; // 下記追記 import { PostSchema } from 'src/utils/validation/schema'; -------- 途中は省略 -------- const { values: newPostData, setValues: setNewPostData, handleSubmit: onFinish, handleChange, } = useFormik<PostData>({ initialValues: initialPostValue, // バリデーションの条件として、schemaファイルのPostSchemaを使用する validationSchema: PostSchema, onSubmit: async () => { alert('フォーム送信成功!!'); }, }); ⑦ バリデーションエラーメッセージを表示する (errors, touched) バリデーションの処理は作成しましたが、このままではバリデーションに引っかかっても何のメッセージも出ない為、このままではバリデーションに引っかかっても何のメッセージも出ない為、ユーザーが困惑してしまいます。 そうならない為にエラーメッセージを出力する方法を紹介します。 -------- 以上省略 -------- const { values: newPostData, setValues: setNewPostData, handleSubmit: onFinish, handleChange, // 下記追記 errors, // 下記追記 touched, } = useFormik<PostData>({ initialValues: initialPostValue, validationSchema: PostSchema, onSubmit: async () => { alert('フォーム送信成功!!'); }, }); return ( <> <div> <FormLabel> <p>タイトル</p> // 更新ボタンを押してバリデーションエラー & 入力フォームをクリック → エラーメッセージを出力 {errors.title && touched.title ? ( // タイトルを空で送信した場合、'タイトルは入力必須です'を出力 <p>{errors.title}</p> ) : null} </FormLabel> <Input type="text" name="title" value={newPostData.title} onChange={handleChange} /> <div> -------- 以下省略 -------- </> ); ・タイトルを空にして、送信ボタンを押した際、console.log(errors.title);を確認すると、'タイトルは入力必須です'と出力されます。 ・touched.titleはタイトルの入力フォームに触れた時点で、trueを出力します。 【バリデーションエラーメッセージが出力された時のイメージ】 ⑧ APIの処理 色々はすっ飛ばしてますが、とりあえず入力フォームのデータをDBに登録出来ました。 入力したデータをDBに送信しましょう。 // これで、APIの処理が無事成功すれば、投稿完了 const { mutate: createNewPost } = useAPICreatePost({ onSuccess: async () => { alert('投稿が完了しました。'); }, }); const { values: newPostData, setValues: setNewPostData, handleSubmit: onFinish, handleChange, errors, touched, } = useFormik<PostData>({ initialValues: initialPostValue, validationSchema: PostSchema, onSubmit: async () => { // 投稿作成のAPIの処理を追記し、入力フォームの値をDBに送信する createNewPost(newPostData); }, }); ⑨ フォームのリセット (resetForm) DBへの登録が完了したものの、フォームには前回の値がそのまま残っています。もし、DBに登録が完了した際にフォームの内容をリセットしたい場合はresetFormを使用します。 resetForm()が動作すると、入力フォームの値がinitialValuesの値に戻ります。 const { mutate: createNewPost } = useAPICreatePost({ onSuccess: async () => { alert('投稿が完了しました。'); // 投稿が完了すると、フォームの入力内容を初期値に戻す (initialValuesの値) resetForm() }, }); const initialPostValue = { title: '', content: '', tag: '', }; const { values: newPostData, setValues: setNewPostData, handleSubmit: onFinish, handleChange, errors, touched, // 下記追記 resetForm, } = useFormik<PostData>({ initialValues: initialPostValue, validationSchema: PostSchema, onSubmit: async () => { createNewPost(newPostData); }, }); ⑩ 最後に 色々省略したりハショッってますが、formikの部分はちゃんと書きました。 とりあえず最後にはこんな感じになります。 なんか間違ってたらガンガン意見ください!! 以上です!! import { useFormik } from 'formik'; import { PostSchema } from 'src/utils/validation/schema'; -------- 途中は省略 -------- const { mutate: createNewPost } = useAPICreatePost({ onSuccess: async () => { alert('投稿が完了しました。'); resetForm() }, }); const initialPostValue = { title: '', content: '', tag: '', }; const { values: newPostData, setValues: setNewPostData, handleSubmit: onFinish, handleChange, errors, touched, resetForm, } = useFormik<PostData>({ initialValues: initialPostValue, validationSchema: PostSchema, onSubmit: async () => { createNewPost(newPostData); }, }); const onClickMakeTemplate = () => { setNewPostData({ title: 'Formikの使い方', content: 'formikをインストールします。その後・・・', tag: 'React', }); }; return ( <> <div> <FormLabel> <p>タイトル</p> {errors.title && touched.title ? ( <p>{errors.title}</p> ) : null} </FormLabel> <Input type="text" name="title" value={newPostData.title} onChange={handleChange} /> <div> -------- contentとtagの入力フォームは省略 -------- <Button onClick={() => onClickMakeTemplate()}>テンプレート</Button> <Button onClick={() => onFinish()}>送信</Button> </> ); // src/utils/validation/schema.tsファイル import * as Yup from 'yup'; export const PostSchema = Yup.object().shape({ title: Yup.string() .required('タイトルは入力必須です') .max(50, 'タイトルは50文字以内で入力してください'), -------- contentとtagは省略 -------- });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

yarn start エラー

備忘録として、記事にします。 ①「npx create-react-app」 後に「yarn start」できなかった  エラー内容は残っていないのですが、「node_modules」内にある「tokenize」がexportできないようでした。 ②結論  色々と試しましたが、状況もいまいち把握できていなかったため、「node_,modules」に問題があるとしたら、再インストールすることが有用であると仮定して実行しました。 「rm -rf node_modules」を実行し、「npm install」を実行し、再インストールしました。そして再び「yarn start」すると成功しました ③まとめ  何に問題があったかは不明瞭であるため、教えていただけると幸いです。yarnやNode.jsに関して知識が無いに等しいので学習していきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React.js 勉強 #3

React.jsを学んでいく学習記録になります。 今までの記録はこちらから。 React.js 勉強 #1 React.js 勉強 #2 propsについて WEBアプリケーションを作っていってるのですが、Modal機能を実装する際にpropsを使用したので、 メモ程度になりますが、書き留めておきます。 propsはVue.jsでも触ったことがあり、僕の認識ではコンポーネント間のデータ受け渡しができるという感じです。 まずは簡単に記述していきます。 src/test.js import React from 'react' const ChildrenTest = () => { return ( <div> 子コンポーネント </div> ) } const ParentTest = () => { return ( <div> 親コンポーネント <ChildrenTest /> </div> ) } export default ParentTest ・・・これコンポーネントできてますよね? 独学だと合っているか本当に分からないので不安ですので、間違っていたら教えて頂けますと幸いでございます 上記を記述すると、ParentTestの内容とChildrenTestの内容が表示されていると思います。 じゃあ、ParentTestでの値をChildrenTestに渡したい時はどうすればいいの?って時に活躍するのが、propsです!! 例えば親コンポーネント(ParentTest)の値を子コンポーネント(ChildrenTest)に渡して表示したい場合、まずは子コンポーネントを呼び出している後ろに渡したい値を追加してあげます。 src/test.js import React from 'react' const ChildrenTest = () => { return ( <div> 子コンポーネント </div> ) } const ParentTest = () => { const text = "最近寒いですね" return ( <div> 親コンポーネント <ChildrenTest text={text} /> </div> ) } export default ParentTest 子コンポーネントのtextにtext(最近寒いですね)を渡しています。 では、それをどう受け取るかというと、 src/test.js import React from 'react' const ChildrenTest = (props) => { return ( <div> 子コンポーネント:{props.text} </div> ) } const ParentTest = () => { const text = "最近寒いですね" return ( <div> 親コンポーネント <ChildrenTest text={text} /> </div> ) } export default ParentTest こうすることで、無事コンポーネント間の値の受け渡しに成功しているはずです。 ちなみに今回は関数コンポーネントで実装していますが、クラスコンポーネントの際では記述の仕方が大きく変わってくるので、注意が必要です。 最後に 今回のサンプルでは実用的なイメージは沸かないと思いますが、 僕自身の備忘録として残しておきます。 何か間違いがあれば、ご指摘頂けますと幸いでございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React]カスタムフックのテストを書いてみる

はじめに カスタムフックをテストしたい!、、、となった時のライブラリの導入手順や簡単な例をまとめました。 対象読者 Reactのカスタムフックをどのようにテストするか、導入部を知りたい人 カスタムフックのテストを書く準備 以下のライブラリのインストールがまだの方は以下をインストールしてください。 npm install --save-dev @testing-library/react-hooks npm install --save-dev react-test-renderer ライブラリの公式ドキュメントはこちら ライブラリの主要なAPI ドキュメントはすぐに目を通せる分量ですが、hooksのテストを導入して、よく利用するであろうものは以下になるかなと思います。 renderHook hooksは、関数コンポーネントの中でしか呼ぶことができないのですが、この関数を使用するとテストファイルの中でも呼ぶことができるようになります。 result これは、result.current.**でカスタムフックの現在の戻り値を取得することが可能です。 expectでstateの検証をしたり、カスタムフックの関数を呼ぶときに使用します。 const { result } = renderHook(useCounter); expect(result.current.count).toBe(0); // 初期値を検証 何らかのエラーが投げられた場合、result.errorが返ってきます。 11以上加算されるとエラーが投げられる場合 test("countがMAX_COUNTを超えた場合、例外がスローされること", () => { const { result } = renderHook(useCounter); expect(result.error).toBeUndefined(); act(() => result.current.incrementByAmountValue(20)); expect(result.error.message).toBe("10までしか加算できません"); }); rerender useEffect等、コンポーネントが再レンダーされることをシュミレートしたい時に使われるケースが多いかなと思います。 以下のように引数に新たなpropsを渡すことが可能です。 const { result, rerender } = renderHook( ({ initialCount }) => useCounter(initialCount), { initialProps: { initialCount: 0 }, } ); rerender({ initialCount: 11 }) waitForNextUpdate Promiseを返す関数で、stateの更新を待ちます。 設定されたtimeout値まで待機することができます。(デフォルトは1000ms) 例えば、以下のように非同期の関数を読んだ時に、2000ms待機した後に、値を評価することができます。 act(() => result.current.asyncIncrement()); await waitForNextUpdate({ timeout: 2000 }); expect(result.current.count).toBe(1); waitFor この関数は引数に真偽値を返す関数を渡して、それがtrueになるまで、待ちます。 何らかのアクションを起こし、値が期待した結果かどうかを判定するなどのケースで使えそうです。 intervalとtimeoutがそれぞれ設定でき、デフォルトはそれぞれ50ms(interval) 1000ms(timeout)です。 他にも、useEffectのクリーンアップ関数の実行をテストしたい時に使用するunmount等が用意されています。 act stateの更新関数を呼ぶ際は、actを利用します。 act(() => result.current.increment()); 実際に書いてみる よくあるカウンターコンポーネントでhooksをどのようにテストするか確認していきましょう。 CustomCounter import React from "react"; import { useCounter } from "./hooks/useCustomCounter"; export const CustomCounter: React.FC = () => { const { count, amountVal, increment, asyncIncrement, incrementByAmountValue, onChangeAmountValue, } = useCounter(); return ( <> <h2>CustomHookTestSample</h2> <p>{count}</p> <button onClick={increment}>+1</button> <button onClick={asyncIncrement}>+1(非同期)</button> <div> <label htmlFor="amountValue">AmountValue</label> <input onChange={onChangeAmountValue} id="amountValue" type="text" /> <button onClick={() => incrementByAmountValue(amountVal)}> incrementByAmountValue </button> </div> </> ); }; useCustomCounter.ts import { useCallback, useState } from "react"; export type UseCustomCounterReturnType = { count: number; amountVal: number; increment: () => void; asyncIncrement: () => void; incrementByAmountValue: (num: number) => void; onChangeAmountValue: (e: React.ChangeEvent<HTMLInputElement>) => void; }; const MAX_COUNT = 10; export const useCounter = (): UseCustomCounterReturnType => { const [count, setCount] = useState(0); const [amountVal, setAmountVal] = useState<number>(0); const increment = () => { setCount((prevCount) => prevCount + 1); }; const sleep = (msec: number) => new Promise((resolve) => setTimeout(resolve, msec)); const asyncIncrement = useCallback(async () => { await sleep(1000); setCount(count + 1); }, [count]); const incrementByAmountValue = (amountVal: number) => { setCount((prevCount) => prevCount + amountVal); }; const onChangeAmountValue = (e: React.ChangeEvent<HTMLInputElement>) => { setAmountVal(Number(e.target.value)); }; if (count > MAX_COUNT) { throw new Error("10までしか加算できません"); } return { count, amountVal, increment, asyncIncrement, incrementByAmountValue, onChangeAmountValue, }; }; CustomCounter.test.js import { act, renderHook } from "@testing-library/react-hooks"; import { useCounter } from "pages/TestSample/components/CustomCounter/hooks/useCustomCounter"; describe("useCustomCounter", () => { test("incrementボタン押下時、1加算されること", () => { const { result } = renderHook(useCounter); expect(result.current.count).toBe(0); // 初期値を検証 act(() => result.current.increment()); expect(result.current.count).toBe(1); }); test("incrementByAmountボタン押下時に入力値が加算されること", () => { const { result } = renderHook(useCounter); act(() => result.current.incrementByAmountValue(10)); expect(result.current.count).toBe(10); }); test("countがMAX_COUNTを超えた場合、例外がスローされること", () => { const { result } = renderHook(useCounter); expect(result.error).toBeUndefined(); act(() => result.current.incrementByAmountValue(20)); expect(result.error.message).toBe("10までしか加算できません"); }); test("非同期のincrementボタン押下時、1加算されること", async () => { const { result, waitForNextUpdate } = renderHook(useCounter); expect(result.current.count).toBe(0); // 初期値を検証 result.current.asyncIncrement(); await waitForNextUpdate({ timeout: 3000 }); expect(result.current.count).toBe(1); }); }); describe("useCustomCounter FetchUser", () => { test("incrementボタン押下時、1加算されること", () => { const { result } = renderHook(useCounter); expect(result.current.count).toBe(0); // 初期値を検証 act(() => result.current.increment()); expect(result.current.count).toBe(1); }); }); 以上。 Reduxやfetch関数が絡むとmockが必要になりますが、そこら辺も整理してまとめたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React]カスタムフックのテストを書く準備と基本的な書き方

はじめに カスタムフックをテストしたい!、、、となった時のライブラリの導入手順や簡単な例をまとめました。 対象読者 Reactのカスタムフックをどのようにテストするか、導入部を知りたい人 カスタムフックのテストを書く準備 以下のライブラリのインストールがまだの方は以下をインストールしてください。 npm install --save-dev @testing-library/react-hooks npm install --save-dev react-test-renderer ライブラリの公式ドキュメントはこちら ライブラリの主要なAPI ドキュメントはすぐに目を通せる分量ですが、hooksのテストを導入して、よく利用するであろうものは以下になるかなと思います。 renderHook hooksは、関数コンポーネントの中でしか呼ぶことができないのですが、この関数を使用するとテストファイルの中でも呼ぶことができるようになります。 result これは、result.current.**でカスタムフックの現在の戻り値を取得することが可能です。 expectでstateの検証をしたり、カスタムフックの関数を呼ぶときに使用します。 const { result } = renderHook(useCounter); expect(result.current.count).toBe(0); // 初期値を検証 何らかのエラーが投げられた場合、result.errorが返ってきます。 11以上加算されるとエラーが投げられる場合 test("countがMAX_COUNTを超えた場合、例外がスローされること", () => { const { result } = renderHook(useCounter); expect(result.error).toBeUndefined(); act(() => result.current.incrementByAmountValue(20)); expect(result.error.message).toBe("10までしか加算できません"); }); rerender useEffect等、コンポーネントが再レンダーされることをシュミレートしたい時に使われるケースが多いかなと思います。 以下のように引数に新たなpropsを渡すことが可能です。 const { result, rerender } = renderHook( ({ initialCount }) => useCounter(initialCount), { initialProps: { initialCount: 0 }, } ); rerender({ initialCount: 11 }) waitForNextUpdate Promiseを返す関数で、stateの更新を待ちます。 設定されたtimeout値まで待機することができます。(デフォルトは1000ms) 例えば、以下のように非同期の関数を読んだ時に、2000ms待機した後に、値を評価することができます。 act(() => result.current.asyncIncrement()); await waitForNextUpdate({ timeout: 2000 }); expect(result.current.count).toBe(1); waitFor この関数は引数に真偽値を返す関数を渡して、それがtrueになるまで、待ちます。 何らかのアクションを起こし、値が期待した結果かどうかを判定するなどのケースで使えそうです。 intervalとtimeoutがそれぞれ設定でき、デフォルトはそれぞれ50ms(interval) 1000ms(timeout)です。 他にも、useEffectのクリーンアップ関数の実行をテストしたい時に使用するunmount等が用意されています。 act stateの更新関数を呼ぶ際は、actを利用します。 act(() => result.current.increment()); 実際に書いてみる よくあるカウンターコンポーネントでhooksをどのようにテストするか確認していきましょう。 CustomCounter import React from "react"; import { useCounter } from "./hooks/useCustomCounter"; export const CustomCounter: React.FC = () => { const { count, amountVal, increment, asyncIncrement, incrementByAmountValue, onChangeAmountValue, } = useCounter(); return ( <> <h2>CustomHookTestSample</h2> <p>{count}</p> <button onClick={increment}>+1</button> <button onClick={asyncIncrement}>+1(非同期)</button> <div> <label htmlFor="amountValue">AmountValue</label> <input onChange={onChangeAmountValue} id="amountValue" type="text" /> <button onClick={() => incrementByAmountValue(amountVal)}> incrementByAmountValue </button> </div> </> ); }; useCustomCounter.ts import { useCallback, useState } from "react"; export type UseCustomCounterReturnType = { count: number; amountVal: number; increment: () => void; asyncIncrement: () => void; incrementByAmountValue: (num: number) => void; onChangeAmountValue: (e: React.ChangeEvent<HTMLInputElement>) => void; }; const MAX_COUNT = 10; export const useCounter = (): UseCustomCounterReturnType => { const [count, setCount] = useState(0); const [amountVal, setAmountVal] = useState<number>(0); const increment = () => { setCount((prevCount) => prevCount + 1); }; const sleep = (msec: number) => new Promise((resolve) => setTimeout(resolve, msec)); const asyncIncrement = useCallback(async () => { await sleep(1000); setCount(count + 1); }, [count]); const incrementByAmountValue = (amountVal: number) => { setCount((prevCount) => prevCount + amountVal); }; const onChangeAmountValue = (e: React.ChangeEvent<HTMLInputElement>) => { setAmountVal(Number(e.target.value)); }; if (count > MAX_COUNT) { throw new Error("10までしか加算できません"); } return { count, amountVal, increment, asyncIncrement, incrementByAmountValue, onChangeAmountValue, }; }; CustomCounter.test.js import { act, renderHook } from "@testing-library/react-hooks"; import { useCounter } from "pages/TestSample/components/CustomCounter/hooks/useCustomCounter"; describe("useCustomCounter", () => { test("incrementボタン押下時、1加算されること", () => { const { result } = renderHook(useCounter); expect(result.current.count).toBe(0); // 初期値を検証 act(() => result.current.increment()); expect(result.current.count).toBe(1); }); test("incrementByAmountボタン押下時に入力値が加算されること", () => { const { result } = renderHook(useCounter); act(() => result.current.incrementByAmountValue(10)); expect(result.current.count).toBe(10); }); test("countがMAX_COUNTを超えた場合、例外がスローされること", () => { const { result } = renderHook(useCounter); expect(result.error).toBeUndefined(); act(() => result.current.incrementByAmountValue(20)); expect(result.error.message).toBe("10までしか加算できません"); }); test("非同期のincrementボタン押下時、1加算されること", async () => { const { result, waitForNextUpdate } = renderHook(useCounter); expect(result.current.count).toBe(0); // 初期値を検証 result.current.asyncIncrement(); await waitForNextUpdate({ timeout: 3000 }); expect(result.current.count).toBe(1); }); }); describe("useCustomCounter FetchUser", () => { test("incrementボタン押下時、1加算されること", () => { const { result } = renderHook(useCounter); expect(result.current.count).toBe(0); // 初期値を検証 act(() => result.current.increment()); expect(result.current.count).toBe(1); }); }); 以上。 Reduxやfetch関数が絡むとmockが必要になりますが、そこら辺も整理してまとめたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでアプリを作成しました【9】【Firebaseを使ったチャットアプリ】

Reactアプリの作成する $ npx create-react-app <アプリ名> 必要なパッケージのインストール Material-UI を利用するため、事前にライブラリをインストールする。 $ npm install @mui/material @emotion/react @emotion/styled $ npm install @mui/material @mui/styled-engine-sc styled-components ※ npm infoを使用して、最新バージョンを確認すること $ npm info @material-ui/icons //インストールしていなかったら、下記のコードを使用する $ yarn add @material-ui/icons@4.11.2 Sign In コンポーネント及びページを作成する $ mkdir src/components $ touch src/components/SignIn.js SignIn.js import * as React from 'react'; import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import CssBaseline from '@mui/material/CssBaseline'; import TextField from '@mui/material/TextField'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import Link from '@mui/material/Link'; import Grid from '@mui/material/Grid'; import Box from '@mui/material/Box'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import Typography from '@mui/material/Typography'; import Container from '@mui/material/Container'; import { createTheme, ThemeProvider } from '@mui/material/styles'; function Copyright(props) { return ( <Typography variant="body2" color="text.secondary" align="center" {...props}> {'Copyright © '} <Link color="inherit" href="https://mui.com/"> Your Website </Link>{' '} {new Date().getFullYear()} {'.'} </Typography> ); } const theme = createTheme(); export default function SignIn() { const handleSubmit = (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); // eslint-disable-next-line no-console console.log({ email: data.get('email'), password: data.get('password'), }); }; return ( <ThemeProvider theme={theme}> <Container component="main" maxWidth="xs"> <CssBaseline /> <Box sx={{ marginTop: 8, display: 'flex', flexDirection: 'column', alignItems: 'center', }} > <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}> <LockOutlinedIcon /> </Avatar> <Typography component="h1" variant="h5"> Sign in </Typography> <Box component="form" onSubmit={handleSubmit} noValidate sx={{ mt: 1 }}> <TextField margin="normal" required fullWidth id="email" label="Email Address" name="email" autoComplete="email" autoFocus /> <TextField margin="normal" required fullWidth name="password" label="Password" type="password" id="password" autoComplete="current-password" /> <FormControlLabel control={<Checkbox value="remember" color="primary" />} label="Remember me" /> <Button type="submit" fullWidth variant="contained" sx={{ mt: 3, mb: 2 }} > Sign In </Button> <Grid container> <Grid item xs> <Link href="#" variant="body2"> Forgot password? </Link> </Grid> <Grid item> <Link href="#" variant="body2"> {"Don't have an account? Sign Up"} </Link> </Grid> </Grid> </Box> </Box> <Copyright sx={{ mt: 8, mb: 4 }} /> </Container> </ThemeProvider> ); } Appファイルを src/componentsへ移動させる $ git mv src/Ap $ git mv src/App.* src/components/ それに伴い、index.js を中身を編集する index.js //省略 import App from './components/App'; //省略 App.jsを編集する App.js import React from 'react'; import SignIn from './SingIn'; export default ()=> { return <SignIn />; }; サインインページの見た目を編集する Firebaseの設定 公式サイト:Firebase ① Firebaseにアクセスし、アカウントでログインし、コンソール画面に移動する。 ②プロジェクトの追加を押して、プロジェクト名を入力する。 ③ウェブアプリを追加するために、ニックネームを入力する。 参考サイト Firebase未経験者のためのReactで作るチャットアプリ開発入門!最速最短でゴール到達!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React.js 勉強 #2

React.jsを学んでいく学習記録になります。 今までの記録はこちらから。 React.js 勉強 #1 手を動かしてみた 調べてみると、Reactはコンポーネントベースで構築していく必要があるっぽいので、とりあえず慣れるためにも触っていきます。 Vue.jsでもコンポーネント化をしてきたので、僕の中ではコンポーネント = パーツ化レベルの認識ですが、一旦コンポーネント化を進めていこうと思いました。 僕の中で作りたいアプリケーションがあったので、とりあえずHeaderとFooterを作成していきます。 前回作成したアプリケーションの中身を見てみると、src/index.jsにAppコンポーネントを読み込んでいる記載があったので、index.jsにHeaderとFooterのコンポーネント読み込めばいいんじゃね?ということで・・・ Headerコンポーネント作成&読み込み まずは、srcディレクトリ内にcomponentsというフォルダを作成し、Header.jsを作成。 どうやらReactのコンポーネント化は クラスコンポーネント 関数コンポーネント があるみたい。 クラスコンポーネントの作り方 src/components/Header.js import React from 'react' class Header extends React.Component { render() { return ( <header> ここはヘッダーになります。 </header> ) } } export default Header クラスコンポーネントにはrender()が必ず必要で、return内で書いた記述が呼び出し先で描画されてるのかな。 ちなみにreturnでは1つの要素で囲む必要あり。つまり、 src/components/Header.js import React from 'react' class Header extends React.Component { render() { return ( <header> ここはヘッダーになります。 </header> <div> ほげ </div> ) } } export default Header 上記だとエラーになるので、注意。 関数コンポーネントの作り方 src/components/Header.js import React from 'react' const Header = () => { return ( <header> ここはヘッダーになります。 </header> ) } export default Header クラスコンポーネントと比べると、かなりスッキリしたプログラムコードになる印象。 どっちがいいかとかはまだ分からないのですが、コードの見やすさ的にも関数コンポーネントのほうが推奨されてる気がします。 Headerのコンポーネントファイルを作成したら、あとは読み込みだけ! Appコンポーネントを読み込んでいたsrc/index.jsに src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import Header from './components/Header'; import App from './App'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( <React.StrictMode> <Header /> <App /> </React.StrictMode>, document.getElementById('root') ); reportWebVitals(); importでHeaderファイルを指定して、Headerを読み込む用記述すると・・・ 無事、Headerを表示することに成功しました! 同じようにFooterコンポーネントの作成&読み込み 同じようにFooterも作っていきます。(今回はクラスコンポーネントで作ってますが、後々関数コンポーネントに切り替える予定です) src/components/Footer.js import React from 'react' class Footer extends React.Component { render() { return ( <div> &copy; </div> ) } } export default Footer src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import Header from './components/Header'; import App from './App'; import Footer from './components/Footer'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( <React.StrictMode> <Header /> <App /> <Footer /> </React.StrictMode>, document.getElementById('root') ); reportWebVitals(); index.jsにFooterコンポーネントを読み出してみると・・・ 無事、Footerにコピーライトを表示することができました! まだまだ表示を行っただけなので、レイアウトは酷いものですが、これからCSSフレームワークを使って整えて行きたいと思っています。 考えているのはmaterialuiっていうのを考えています! CSSフレームワーク自体、ほとんど使ったことがなく(bootstrapぐらい)、これもなかなか苦戦しそうですが・・・ 頑張っていきます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React.js 勉強 #1

React.jsを選んだきっかけ 特にありません!(笑) ただ、業務でVue.jsを触る機会が多く、ある程度慣れてきたので、Vue.js以外がいいなーとググったところ、 知名度もあるReact.jsを知ったので一度触ってみようと考えた次第です。 まあVue.jsも触ったことあるし、サクサク進むだろうと安易な気持ちで挑んだ結果、大苦戦中・・・。 環境構築 公式サイトを参考に進めていきます。 nodeがインストールされているかを確認 $ node -v 上記コマンドを打ってエラー吐かなかればOK! 僕はVue.jsを触ってきたので、インストール済みでした。 同じようにnpmがインストールされているかを確認 $ npm --version エラー吐かなければ、Reactプロジェクトを作成していきます。 $ npm install -g create-react-app $ npx create-react-app 好きなプロジェクト名 上記なぜnpxにしなければいけなかったのが不明・・・。 公式によると、 npx は打ち間違いではありません — これは npm 5.2 から利用できるパッケージランナーツールです。 と記載してあったので、その通りに打ちましたが、npmでもよかったんかな?(エラー吐いた気が) 何はともあれ、上記コマンドを叩いてエラーが発生しなければ $ cd 好きなプロジェクト名 でディレクトリ移動し、 $ npm start で無事に起動ができれば成功です! 今後について 勉強がてらReact.jsを触り始めましたが、ところどころ躓いて、やっぱり自分レベル低いなーと痛感しています・・・。 今後は考えているアプリケーションがあるので、それを作成しながら、都度学習記録として更新していけたらと思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む