[React/state/React상태관리] TodoList 삽질하기 2
클릭시 체크리스트 제거하기
HTML 태그의 내부기능인 onChange를 이용해서, check가 됬을 때 함수를 발생시켜줬다. 그래서, checkHandler에 idx를 넘겨주고,checkedItem["idx"]값을 체크가 되었다면 true로 만들어주고, filter를 이용해서 false인 것들만 남기는 형식으로 구현을 하였습니다.
코드로 보시죠!
const checkHandler = (idx) => {
checkedItems[idx] = !checkedItems[idx];
setItems([...items.filter((item) => !checkedItems[item.id])]);
setCheckedItems([...checkedItems]);
};
//...생략...
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={checkedItems[idx]}
onChange={() => checkHandler(idx)}
></input>
</div>
))}
//...생략...
하지만, 문제가 생겼습니다! 클릭을 해보면, 체크가 되기도 하고 안되기도하고, 이상합니다.
SandBox로 직접해보시면 좋을 듯 합니다.
https://codesandbox.io/s/todolist-error-key-urkzlm?file=/src/App.js:664-928
왜(Why) 위와 같은 문제가 발생했을까
2번째를 체크했는데, 체크하고 나서도 2번째가 그대로 체크가 되어있습니다. 즉, 바뀌고 나서의 순서가 다시 Rendering 되었다는 소리죠. 말이 헷갈릴 수 있습니다. 다시 한번 설명하겠습니다.
제가 "have lunch"를 클릭할 때, state에는 아래와 같은 변화가 일어나게 됩니다. 즉, 1에 있는 "have lunch"가 없어지고, 나머지를 순서대로 넣어서 반환하도록 코드를 짯기 때문에 아래와 같이 Items state의 순서 새롭게 정의됩니다.
이제 문제점이 보이기 시작했나요? 바로 items.map((value,idx) => {}) 에서 , idx가 새롭게 정의된다는 이야기입니다!
Item을 클릭하고, Items의 index는 재정의되기 되어, 2번째가 그대로 true인 상태가 되버렸고, 2번째가 그대로 "click"된 상태로 남아버리는 것입니다.
해결방법
"Key"는 유일하고 ,식별이 가능하면서 , 변동이 되면 안된다.
key를 "todo.id"로 던져주고, checked true로 하기보다는 , todo.id가 있기 때문에, 그걸로 바로 식별해주는 코드로 바꾸어 주겠습니다. (state를 서로 Coupling 시켜놨기 때문에, 좋은 패턴은 아닌거 같아서 그냥 없앴습니다)
const [items, setItems] = React.useState(todos);
// checked state 삭제
const checkHandler = (idx) => {
//console.log((todos.find((todo) => todo.id === idx).checked = true));
//console.log(todos);
setItems([...items.filter((item) => item.id !== idx)]);
};
return (
<div className="App">
//fix.
{items.map((todo) => (
<div key={todo.id}>
<span>{todo.value}</span>
<input type="checkbox" onChange={() => checkHandler(todo.id)}></input>
</div>
))}
<button onClick={restoreHandler}>Restore</button>
</div>
);
이렇게 되면, 코드가 훨씬 간결해지면서 , 직관적으로 되겠죠!
여기서, Restore기능을 추가해보겠습니다!
TodoList를 할 때, 체크를 했는데, 어 "내가 잘못체크를 했다!"고 할 수 있기 때문에, Restore을 간단하게 구현해보겠습니다.
Restore 기능 구현하기
Restore 버튼을 추가하도록 하겠습니다. 그리고, onClick에 restoreHandler를 등록해주도록 하겠습니다.
- restore할게 없는데 , restore할 경우에 에러를 발생시키기 때문에, return으로 바로 함수를 끝내줍니다.
- items뒤에, todo안에는 있고, item에는 없는 object를 찾아서 다시 item 배열 뒤에 붙여주는 형식으로 구현을 했습니다.
return (
<div className="App">
// ...생략...
<button onClick={restoreHandler}>Restore</button>
</div>
);
RestoreHandler
const restoreHandler = () => {
//restore할게 없는게, restore할 경우
if (items.length === todos.length) {
return;
}
//todos 안에 있는데, item 안에는 없는걸 찾아서, 뒤에 붙이는 형식으로 짯습니다.
setItems((items) => [
...items,
todos.find((todo) => !items.includes(todo))
]);
};
TodoList를 좀 더 사용성*UX를 높게 만들기 위해서 다음글에서는 아래 3가지 기능을 추가해보도록 하겠습니다.
1. TodoList 복구 시, 원래 TodoList의 순서 보장해주기.
Restore 시에 , 지금은 원래의 배열 뒤에 그냥 붙여주는 형식입니다만, 생각해보면 사용자입장에는 등록한 일정대로 다시 Restore하는걸 원할 겁니다. 그림으로 설명하자면, 아래와 같은 구조로 구현할 겁니다.
여러개의 TodoList를 없애도 원래의 순서에 맞게 다시 TodoList를 복구하는 기능으로 구현해보겠습니다.
2. TodoList 복구 시, 최근에 없앤 것 부터 복구해주기.
클릭을 해서, 없애는 방식이기 때문에 방금 없앤걸 다시 복구할 확률이 되게 높을 것 같습니다. 따라서, Restore를 했을 시에, 최근에 클릭한 객체순으로 다시 복구하는 식으로 하면 좋을 것 같습니다. 즉 History 기능처럼 만들겠다는 건데, 이건 삭제된 걸 State로 stack처럼 담는 형식으로 구현하면 될 것 같습니다. 대신, 복구되는 순서는 Item의 원래 순서에 끼어들어 갈 수 있게끔 할겁니다.
3.Restore All 기능 구현하기.