
리액트 필수예제 투두리스트 내맘대로 만들어보기
npx create-react-app react-todolistfilter()를 사용한다.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;
별 거 아닐 줄 알았는데 이제껏 공부한 걸 많이 활용할 수 있었고 배운 것도 많았던 예제! 아주 재밌었다 😊