React-실험 : [Error] - draft,proxy : Cannot assign to read only property 'isDone' of object '#<Object>'

최문길·2023년 12월 14일
0

react

목록 보기
5/14

Cannot assign to read only property 'isDone' of object #<Object>

ReduxToolkit

reduxToolkit을 사용 하여 todoList를 만들다가

// todoList Component
const todoList = useSelector(state=>state.todoList)
  const newTodoList = todoList.map((target) => {
    if (target) {
      target.isDone = !target.isDone;
      return target;
    }
  });
 // Cannot assign to read only property 'isDone' of object '#<Object>' 

이러한 에러가 발생하였다.

원인

reducer의 state 는 읽기만 해야지 직접적으로 바꾸는 값이 없어야 한다.
reducer 함수 안에는 immerjs 가 내장되어 있으므로

// todoListReducer
    isDoneTodo: (state, { payload }) => {
      const newTodoList = state.map((target) => {
        if (target.id === payload) {
          target.isDone = !target.isDone;
          return target;
        }
        return target;
      });
      
     return newTodoList;
    },

이렇게 하여서 문제를 해결하려 했지만..

[Immer] An immer producer returned a new value and modified its draft. Either return a new value or modify the draft.

이러한 에러가 떴다.

immerJs

reduxToolkit 에는 immer가 내장되어있고 immer로 인해 state 값을 복사한 Proxy를 가지고 우리가 push를 해도 새로운 state 변경으로 받아들여 reRendering이 일어나게끔 도와주는 고마운 녀석이다.

그렇지만

저 위의 에러를 해석해보면

reduxToolkit에서
reducer안에서 사용 할 때 새로운 값을 return 해주거나 아니면 draft를 수정해주어라는 의미인데

draft

공식적으로 proxy라고 immer에서 이야기하지만 draft === proxy이다. 즉, state를 immer에서 자동 복사해주는데,
복사된 state = proxy = draft라고 생각하면 된다.

// draft를 modified 해주라는 의미는
state.isDone = false // 이러한 식으로 말이다.

이런식으로 간단하게 수정해줘~~ 우리가 바뀐값으로 처리해서 return 해줄께 이런 말 뜻임


다시 본론으로 돌아와서

// todoListReducer
    isDoneTodo: (state, { payload }) => {
      const newTodoList = state.map((target) => {
        if (target.id === payload) {
          target.isDone = !target.isDone; // 여기가 문제임
          return target;
        }
        return target;
      });
      
     return newTodoList;
    },

React에서 map 함수는 새로운 배열을 return 해준다. 새로운 배열을 return 해주어야 하는데 나는 지금

Reducer안에서 map함수로 돌려지고 있는 요소에 "직접적으로" 수정을 하려고 하기 때문에 저러한 에러가 발생하는 것이다.

=> Array.map이라는 메소드는 원본을 지킨다. 지금 위 코드는 원본을 바꾸려고 하기 때문이다.
return 하려는 '값'을 얕은 복사본을 만들어서 return 시켜주자

해결책 1.

component 안에서 무언가 reducer의 state 값을 변경하고 싶다면,

// todoList Component
const todoList = useSelector(state=>state.todoList)
   const newTodoList = todoList.map((target) => {
      if (target.id === id) {
        // target.isDone = !target.isDone;
        // return target; 이렇게 말고 
        return { ...target, isDone: !target.isDone };
      }
      return target;
    });

해결책 2.

reducer안에서 정의된 함수안에서 해주고 싶다면

// todoListReducer
   isDoneTodo: (state, { payload }) => {
       const newTodoList = state.map((target) => {
        if (target.id === payload) {
           //target.isDone = !target.isDone;
           return target;
         }
          return { ...target, isDone: !target.isDone };
       });

       return newTodoList;
    },

결론

내 문제점은 map에서 return 되는 값을 수정해주는 것이 아닌 순회하고 있는 요소를 변경하려고 해서 문제가 생기는 것이 point이다.

map 함수는 배열의 원본 은 건들지 않고 새로운 배열을 return 하는데,... 원본을 변경시키려고 하니까 계속해서 에러가 생기는 것. 사실 계속해서 저런식으로 원본의 요소를 건드려서 저런 에러가 나서 deepCopy 함수 만들어서 변경시켰음 ;;;

  1. map 함수를 실행 할 때 원본의 요소를 수정하지 말것
  2. filter, map에서 return 되는 값을 수정 할것 ( return 하는 것은 새로운 배열에 들어가는 요소니까 )

0개의 댓글