리액트 필수예제 투두리스트 내맘대로 만들어보기
npx create-react-app react-todolist
filter()
를 사용한다.const newArray = array.filter((value) => [남길 요소들이 true를 리턴할 조건]);
splice()
을 사용할 수 있다.splice()
는 원본 배열을 수정하므로 주의!array.splice(시작 인덱스, 삭제할 개수, 삭제할 위치에 넣을 데이터1, 데이터2, ...);
DOM 요소를 조건부 렌더링하려면 bool 값을 갖는 ref를 만들고 &&
나 ||
연산자로 처리해준다. (JSX 조건문)
// isUpdate가 거짓일 때 렌더링
{isUpdate || <span onClick={onClickTodo}>{todo}</span>}
// isUpdate가 참일 때 렌더링
{isUpdate && <input ref={input} value={value} onChange={onChangeInput} />}
useRef()
와 useEffect()
활용ref는 렌더링 되면서 연결된다.
예를 들어 위의 코드(조건부 렌더링)를 보면,
처음 렌더링에서 isUpdate
의 기본값은 false
👉 span
만 렌더링이 되고 input
은 렌더링이 되지 않음
👉 span
을 클릭하면 onClickTodo
(isUpdate
를 true
로 변경해서 input
을 렌더링하게 하는 함수) 실행
여기서 input.current.focus()
는 동작하지 않는다!
input
이 렌더링되지 않았으므로 ref가 연결되지 않아 input.current
는 undefined
이다.
👉 useEffect()
를 활용하여 렌더링 후에 isUpdate
가 변경되었으면 input.current.focus()
를 실행한다!
왜 input에 텍스트를 입력할 때마다 전체가 다 리렌더링..?
또는 자식 하나 변경했는데 왜 모든 자식이 리렌더링..?
👉 이런 걸 막기 위해서 자식 컴포넌트는 memo
로 감싸서 기억해두고 props가 변경되지 않으면 리렌더링이 되지 않도록 한다.
localStorage.setItem(key, value)
로 저장하고, localStorage.getItem(key)
로 불러온다.JSON.stringfy(value)
로 JSON 형태의 문자열로 변환하여 저장하고, 불러온 후에는 JSON.parse(value)
를 통해 다시 객체로 변환한다.useEffect()
를 사용하여 마운트되었을 때 불러오고, todoList
나 id
가 업데이트 되었을 때 저장한다.id
값을 불러올 때 parseInt()
로 변환해서 사용한다.// 마운트되었을 때 localStorage 데이터 불러오기
useEffect(() => {
const localTodoList = localStorage.getItem('todoList');
console.log(localTodoList, JSON.parse(localTodoList));
if (localTodoList) {
setTodoList(JSON.parse(localTodoList));
}
const localId = localStorage.getItem('id');
if (localId) {
setId(parseInt(localId));
}
}, []);
// todoList나 id가 업데이트되면 localStorage에 데이터 저장하기
useEffect(() => {
localStorage.setItem('todoList', JSON.stringify(todoList));
localStorage.setItem('id', id);
}, [todoList, id]);
onBlur
, onKeyUp
이벤트esc
키를 누르면 업데이트를 취소(isUpdate
를 false
로 수정)하고 싶어서 onBlur
와 onKeyUp
이벤트를 활용했다.onBlur
는 포커스가 벗어났을 때 발생하는 이벤트onKeyUp
은 키보드로 입력되고 나서 발생하는 이벤트key
에 담겨있다.esc
키는 Escape
, enter
는 Enter
등으로 조건을 검사한다. const onBlurInput = () => {
setIsUpdate(false);
};
const onKeyUpInput = (e) => {
if (e.key === 'Escape') {
setIsUpdate(false);
}
};
import React, { useState, useCallback, useEffect, useRef } from 'react';
import AddForm from './AddForm';
import Todo from './Todo';
import './TodoList.css';
const TodoList = () => {
const [todoList, setTodoList] = useState([]);
const [id, setId] = useState(0);
const isMount = useRef(true);
useEffect(() => {
if (!isMount.current) {
localStorage.setItem('todoList', JSON.stringify(todoList));
localStorage.setItem('id', id);
}
}, [todoList, id]);
useEffect(() => {
const localTodoList = localStorage.getItem('todoList');
if (localTodoList) {
setTodoList(JSON.parse(localTodoList));
}
const localId = localStorage.getItem('id');
if (localId) {
setId(parseInt(localId));
}
isMount.current = false;
}, []);
const addTodo = useCallback(
(todo) => (e) => {
console.log('add');
e.preventDefault();
if (todo) {
setTodoList((prevTodoList) => [
...prevTodoList,
{ id: id, todo: todo, isChecked: false },
]);
setId((prevId) => prevId + 1);
}
},
[id]
);
const updateTodo = useCallback(
(id, todo, isChecked) => {
const index = todoList.findIndex((todoInfo) => todoInfo.id === id);
const newTodoList = [...todoList];
newTodoList.splice(index, 1, {
id: id,
todo: todo,
isChecked: isChecked,
});
setTodoList(newTodoList);
},
[todoList]
);
const deleteTodo = useCallback(
(id) => () => {
const newTodoList = todoList.filter((todoInfo) => todoInfo.id !== id);
setTodoList(newTodoList);
},
[todoList]
);
const toggleCheck = useCallback(
(id) => () => {
const index = todoList.findIndex((todoInfo) => todoInfo.id === id);
const newTodoList = [...todoList];
newTodoList[index].isChecked = newTodoList[index].isChecked
? false
: true;
setTodoList(newTodoList);
},
[todoList]
);
return (
<div className="box">
<div className="todolist-box">
<h1>things to do</h1>
<AddForm addTodo={addTodo} />
<ul>
{todoList.map((todoInfo) => {
return (
<Todo
key={todoInfo.id}
id={todoInfo.id}
todo={todoInfo.todo}
isChecked={todoInfo.isChecked}
updateTodo={updateTodo}
deleteTodo={deleteTodo}
toggleCheck={toggleCheck}
/>
);
})}
</ul>
</div>
</div>
);
};
export default TodoList;
import React, { useState, useRef, useEffect, memo } from 'react';
import './AddForm.css';
const AddForm = memo(({ addTodo }) => {
const [value, setValue] = useState('');
const input = useRef(null);
useEffect(() => {
input.current.focus();
setValue('');
}, [addTodo]);
const onChangeInput = (e) => {
setValue(e.target.value);
};
return (
<form className="add-form">
<input ref={input} value={value} onChange={onChangeInput} />
<button type="submit" onClick={addTodo(value)}>
add
</button>
</form>
);
});
export default AddForm;
import React, { useState, useRef, useEffect, memo } from 'react';
import './Todo.css';
const Todo = memo(
({ id, todo, isChecked, deleteTodo, updateTodo, toggleCheck }) => {
const [value, setValue] = useState(todo);
const [isUpdate, setIsUpdate] = useState(false);
const input = useRef(null);
useEffect(() => {
if (isUpdate) {
input.current.focus();
}
}, [isUpdate]);
useEffect(() => {
setIsUpdate(false);
}, [todo]);
const onClickTodo = () => {
setIsUpdate(true);
};
const onChangeInput = (e) => {
setValue(e.target.value);
};
const onFormSubmit = (e) => {
e.preventDefault();
setIsUpdate(false);
if (!value) {
setValue(todo);
} else {
if (todo !== value) {
updateTodo(id, value, isChecked);
}
}
};
const onBlurInput = () => {
setIsUpdate(false);
};
const onKeyUpInput = (e) => {
if (e.key === 'Escape') {
setIsUpdate(false);
}
};
return (
<li className="list">
<span className="check" onClick={toggleCheck(id)}>
{isChecked ? '◼' : '◻'}
</span>
{isUpdate || (
<span
className={`todo ${isChecked ? 'checked' : ''}`}
onClick={onClickTodo}
>
{todo}
</span>
)}
{isUpdate && (
<form className="update-form" onSubmit={onFormSubmit}>
<input
ref={input}
value={value}
onChange={onChangeInput}
onBlur={onBlurInput}
onKeyUp={onKeyUpInput}
/>
</form>
)}
<button onClick={deleteTodo(id)}>X</button>
</li>
);
}
);
export default Todo;
별 거 아닐 줄 알았는데 이제껏 공부한 걸 많이 활용할 수 있었고 배운 것도 많았던 예제! 아주 재밌었다 😊