NK-dev
@NK-dev (N K)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

完了、未完了のロジックがうまくいかない

解決したいこと

Recoilで状態管理をして、todo管理の簡易アプリを作成しています。TodoItem.tsx中のボタンをクリックした時に「完了」、「未完了」を切り替える実装を行いたく、出ているエラーに対して助言を頂きたいです。

発生している問題・エラー

Uncaught TypeError: Cannot assign to read only property 'isComplete' of object '#<Object>'

、コンソールにエラーとしてはかれる。

TodoItem.tsx
  const toggleCompletion = (id: number, isComplete: boolean) => {
    const toggleChecked = todoList.map((todo) => {
      if (todo.id === id) {
        todo.isComplete = !isComplete;
      }
      return todo;
    });
    setTodoList(toggleChecked);
  };

上記のtodo.isComplete = !isCompleteの部分で出現する。

該当するソースコード

TodoList.tsx
import React from 'react';
import { useRecoilValue } from 'recoil';
import { todoListState } from '../State/atom';
import TodoItem from './TodoItem';
import TodoItemCreator from './TodoItemCreator';
import TodoListStatus from './TodoListStatus';

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

  return (
    <div>
      <h1>初めてのRecoil</h1>
      <TodoListStatus />
      <TodoItemCreator />
      {todoList.map((todo) => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
};

export default TodoList;
TodoItem.tsx
import React from 'react';
import { useRecoilState } from 'recoil';
import { todoListState } from '../State/atom';
import { ITEMPROPS } from '../types/DataType';

const TodoItem: React.FC<ITEMPROPS> = ({ todo }) => {
  const [todoList, setTodoList] = useRecoilState(todoListState);

  const deleteItem = (id: number) => {
    const newTodos = todoList.filter((todo) => todo.id !== id);
    setTodoList(newTodos);
  };

  const toggleCompletion = (id: number, isComplete: boolean) => {
    const toggleChecked = todoList.map((todo) => {
      if (todo.id === id) {
        todo.isComplete = !isComplete;
      }
      return todo;
    });
    setTodoList(toggleChecked);
  };

  return (
    <div>
      <div key={todo.id}>
        <button onClick={() => toggleCompletion(todo.id, todo.isComplete)}>
          {todo.isComplete ? '完了' : '未完了'}
        </button>
        {todo.title}
        <span className='deletefunc' onClick={() => deleteItem(todo.id)}>
          削除
        </span>
      </div>
    </div>
  );
};

export default TodoItem;
atom.ts
//ストア的な
import { atom } from 'recoil';
import { DATA } from '../types/DataType';

export const todoListState = atom<Array<DATA>>({
  key: 'todoListState',
  default: [
    {
      id: 0,
      title: '送信設定',
      isComplete: false,
    },
  ],
});
selector.ts
//参照
import { selector } from 'recoil';
import { todoListState } from './atom';

export const todoListStatusState = selector<number>({
  key: 'todoListStatusState',
  get: ({ get }) => {
    const todoList = get(todoListState);
    const totalNum = todoList.length;
    return totalNum;
  },
});
DataType.ts
//型定義
export type DATA = {
  id: number;
  title: string;
  isComplete: boolean;
};

export type ITEMPROPS = {
  todo: DATA;
};

自分で試したこと

クリックされるたびに下記の関数(toggleCompletion)が実行されて切り替わるという想定をしました。
idが一致した時に完了と未完了の表示を切り替えるというのを考えていましたが、うまく実装できませんでした。

TodoItem.tsx
  const toggleCompletion = (id: number, isComplete: boolean) => {
    const toggleChecked = todoList.map((todo) => {
      if (todo.id === id) {
        todo.isComplete = !isComplete;
      }
      return todo;
    });
    setTodoList(toggleChecked);
  };
0

1Answer

    const toggleChecked = todoList.map((todo) => {
      if (todo.id === id) {
        todo.isComplete = !isComplete;//NG
      }
      return todo;
    });

この操作は元のObjectを書き換えることになります.

Warning: Concurrent modification of the kind described in the previous paragraph frequently leads to hard-to-understand code and is generally to be avoided (except in special cases).

原則,mapからは新しいObjectを返してください.

    const toggleChecked = todoList.map((todo) => {
      if (todo.id === id) {
        return {...todo, isComplete: !isComplete} //スプレッド構文でコピーする
      }
      return todo;
    });
2Like

Comments

  1. @NK-dev

    Questioner

    お忙しい中ご回答ありがとうございます!
    勉強になりました。ありがとうございます!

Your answer might help someone💌