Recoil で Todo リストを作ってみます。公式チュートリアルをなぞります。

Atoms にはアプリケーションのステートを含んでいます。Todoリストのステートはオブジェクトの配列になり、各オブジェクトはTodoアイテムです。

atom() 関数で todo リストを表現してみます。

export const todoListState = atom({
  key: "TodoList",
  default: []
});

この atom の内容を useRecoilValue を使って read してみます。

function TodoList() {
  const todoList = useRecoilValue(todoListState);

  return (
    <>
      <TodoItemCreator />
      {todoList.map((todoItem) => (
        <TodoItem key={todoItem.id} item={todoItem} />
      ))}
    </>
  );
}

TodoItemCreator コンポーネントは todo を新規追加するコンポーネントです。TodoListState の内容を更新するセッター関数にアクセスします。

function TodoItemCreator() {
  const [inputValue, setInputValue] = useState("");
  const setTodoList = useSetRecoilState(todoListState);

  const addItem = () => {
    setTodoList((oldTodoList) => [
      ...oldTodoList,
      {
        id: getId(),
        text: inputValue,
        isComplete: false
      }
    ]);
    setInputValue("");
  };

  const onChange = ({ target: { value } }) => {
    setInputValue(value);
  };

  return (
    <div>
      <input type="text" value={inputValue} onChange={onChange} />
      <button onClick={addItem}>Add</button>
    </div>
  );
}

export default TodoItemCreator;

let id = 0;
function getId() {
  return id++;
}

TodoItem コンポーネントは、Todo 項目の値を表示すると同時に、そのテキストを変更したり、項目を削除したりすることができます。useRecoilState() で todoListState を読み込み、セッター関数を取得して、項目のテキストを更新したり、完了マークをつけたり、削除するのに使っています。

function TodoItem({ item }) {
  const [todoList, setTodoList] = useRecoilState(todoListState);
  const index = todoList.findIndex((listItem) => listItem === item);

  const editItemText = ({ target: { value } }) => {
    const newList = replaceItemAtIndex(todoList, index, {
      ...item,
      text: value
    });

    setTodoList(newList);
  };

  const toggleItemCompletion = () => {
    const newList = replaceItemAtIndex(todoList, index, {
      ...item,
      isComplete: !item.isComplete
    });

    setTodoList(newList);
  };

  const deleteItem = () => {
    const newList = removeItemIndex(todoList, index);

    setTodoList(newList);
  };

  return (
    <div>
      <input type="text" value={item.text} onChange={editItemText} />
      <input
        type="checkbox"
        checked={item.isComplete}
        onChange={toggleItemCompletion}
      />
      <button onClick={deleteItem}>X</button>
    </div>
  );
}

export default TodoItem;

function replaceItemAtIndex(arr, index, newValue) {
  return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
}

function removeItemIndex(arr, index) {
  return [...arr.slice(0, index), ...arr.slice(index + 1)];
}

Sample