valid,invalid

関心を持てる事柄について

ReactのuseStateで日本語のふりがな入力を支援するhook書いた

半年以上前に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'));

demo

Test for React Hooks

React hooksのテストってどうやって書くのかな?と思ってHooks FAQ – Reactを見ると@testing-library/react-hooksを見つけたので試してみた。今回はJestと併用。

基本的にはReact Testing Libraryと類似のAPIactとか)が使える…と思ったら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-providernpm deprecateした。

github.com

www.npmjs.com