半年以上前にReactのContextとHooksで日本語のふりがな入力を支援するコンポーネント書いた - valid,invalidという記事を書いたが、このときは何もわかってなかったということがわかった。
長々とContext
, Provider
, useReducer
あたりのことを書きましたが、同じことは以下の十数行のcustom hookで実現できた。
import historykana from 'historykana'; export const useKana = (fieldNames: string[]) => { const [history, setHistory] = useState({}); const [kana, setKana] = useState({}); const setKanaSource = ({ fieldName, inputtedValue }) => { const newHistory = inputtedValue ? [...history[fieldName], inputtedValue] : []; setHistory({ ...history, [fieldName]: newHistory }); setKana({ ...kana, [fieldName]: historykana(newHistory) }); }; return { kana, setKanaSource }; };
state
を更新するロジックは大した複雑性もないのでuseReducer
は不要。state
を更新するにはdispatcher
相当のcallback関数を自前で書いてユーザーに提供すればよし。
ライブラリを使う側の気持ちとしても今だったらContext
じゃなくふつうにcustom hookを使いたいと思う。
import React from 'react'; import ReactDOM from 'react-dom'; import { useKana } from 'react-use-kana'; const App = () => { const { kana: { lastNameKana }, setKanaSource } = useKana(['lastNameKana']); return ( <> <input type="text" onChange={e => setKanaSource({ fieldName: 'lastNameKana', inputtedValue: e.target.value }) } /> <input type="text" value={lastNameKana} /> </> ); }; ReactDOM.render(<App />, document.getElementById('root'));
Test for React Hooks
React hooksのテストってどうやって書くのかな?と思ってHooks FAQ – Reactを見ると@testing-library/react-hooks
を見つけたので試してみた。今回はJestと併用。
基本的にはReact Testing Libraryと類似のAPI(act
とか)が使える…と思ったらreact-test-renderer
に依存しており、そこからexportされているだけのようだった。
React Componentのテストのときと同様にact
にはユーザーインタラクションのいち単位を記述する。今回は一文字ずつの入力をインタラクションとして定義したく、ループ内でact
を呼ぶようなテストになった
import { renderHook, act } from '@testing-library/react-hooks'; import { useKana } from '../useKana'; test('returns kana based on user input', () => { const { result } = renderHook(() => useKana(['lastNameKana', 'firstNameKana']), ); expect(result.current.kana).toEqual({ lastNameKana: '', firstNameKana: '', }); // Emulates to input a user's last name [ 'や', 'やm', 'やま', 'やまd', 'やまだ', '山田', ].forEach(value => { act(() => { result.current.setKanaSource({ fieldName: 'lastNameKana', inputtedValue: value, }); }); }); expect(result.current.kana).toEqual({ lastNameKana: 'やまだ', firstNameKana: '', }); });
Publised
今回書いたreact-use-kana
はnpm registoryに公開し、react-kana-provider
はnpm deprecate
した。