TodoList
에서는 데이터를 토대로 TodoItem 을 map 을 활용해서 보여준다. 각각의 할 일 데이터는 아래처럼 todoData
라는 이름의 배열에 저장되도록 작성한다. 후에는 키 값을 초기화값으로 수정해줘야한다.
// todoData
const [todoData, setTodoData] = useState([할일])
할일 Id값을 받아서 완료 여부를 setTodoData
로 실행한다.
<div>
{todoData.map((itemData) => {
return(
<TodoItem key={itemData.id} itemData={itemData}/>
)
})}
</div>
props로 받은 데이터를 표시되도록 작성해준다.
function TodoItem({itemData}) {
const {id, date, content, checked} = itemData
return (
<div>
<p>{date}</p>
<p>{content}</p>
</div>
)
}
할일을 추가하는 것은 Input Value를 onChange 기반으로 userInput
에 받을 수 있도록 작성한다.
function TodoAdd() {
const [userInput, setUserInput] = useState({date: '', content: ''})
const userInputHandler = (e) => {
const {name, value} = e.target
setUserInput({...userInput, [name]:value})
}
return (
<div>
<input type='date' name='date' onChange={userInputHandler}/>
<input name='content' onChange={userInputHandler}/>
<button>할일 추가</button>
</div>
)
}
Todo Id로 할일에 추가 순서가 될 수 있도록 추가될때마다 Id값에 +1이 된다. Id값은 표시되지 않고 데이터 추가와 제거만 되는 값이기 때문에 useRef로 만들어준다.
function TodoList() {
const todoId = useRef(1);
const [todoData, setTodoData] = useState([{id: 1, date:'2022-07-28', content:'강의하기', checked:true}])
return (
<div>
<TodoAdd todoId={todoId} todoData={todoData} setTodoData={setTodoData}/>
{todoData.map((itemData) => {
return(
<TodoItem key={itemData.id} itemData={itemData} />
)
})}
</div>
)
}
props로 todoAdd에 내려보낸 Id값을 todoData에 추가해준다. 기존 있던 todo는 전개연산자로 새로운 배열에 들어가고 추가된 todo는 뒤에 추가된다.
// todoAdd
const todoAddHandler = (userInput) => {
setTodoData([...todoData, {id:todoId.current, date:userInput.date, content: userInput.content, checked:false}])
todoId.current += 1
}
<button onClick={() => todoAddHandler(userInput)}>추가하기</button>
Todo Id값을 받아서 삭제하는 todo를 필터링한다.
// todoList
const todoRemoveHandler = (id) => {
setTodoData(todoData.filter(itemData => itemData.id !== id))
}
함수 상위 컴포넌트에서 만들고 props로 내려주는 이유는 todoItem이 여러개 렌더링되면서 제거함수도 여러개 생성되기 때문에 이런것을 방지해주기 위해서이다..
// todoList
return (
<div>
<TodoAdd todoId={todoId} todoData={todoData} setTodoData={setTodoData}/>
{todoData.map((itemData) => {
return(
<TodoItem key={itemData.id} itemData={itemData} todoRemoveHandler={todoRemoveHandler}/>
)
})}
</div>
)
Id값을 인자로 넣어줘서 해당 아이디를 가진 아이템을 삭제해주도록 한다.
// todoItem
return (
<div>
<p>{date}</p>
<p>{content}</p>
<button onClick={() => todoRemoveHandler(id)}>제거하기</button>
</div>
)
특정 할일을 완료하는 기능이다. map 함수와 조건문을 활용해서 인자로 들어온 아이디와 일치하는 item의 checked 값을 느낌표 연산자로 변경해준다.
const todoCheckHandler = (id) => {
setTodoData(todoData.map(itemData => itemData.id === id ? {...itemData, checked: !itemData.checked} : itemData))
}
todoItem에 완료 버튼을 만들어준다.
return (
<div>
<p>{date}</p>
<p>{content}</p>
<button onClick={() => todoCheckHandler(id)}>{checked ? <>완료</> : <>미완료</>}</button>
<button onClick={() => todoRemoveHandler(id)}>제거하기</button>
</div>
)
import {useState, useRef} from 'react'
import TodoAdd from "./TodoAdd"
import TodoItem from "./TodoItem"
function TodoList() {
const todoId = useRef(2);
const [todoData, setTodoData] = useState([{id: 1, date:'2022-07-28', content:'강의하기', checked:true}])
const todoRemoveHandler = (id) => {
setTodoData(todoData.filter(itemData => itemData.id !== id))
}
const todoCheckHandler = (id) => {
setTodoData(todoData.map(itemData => itemData.id === id ? {...itemData, checked: !itemData.checked} : itemData))
}
return (
<div>
<TodoAdd todoId={todoId} todoData={todoData} setTodoData={setTodoData}/>
{todoData.map((itemData) => {
return(
<TodoItem key={itemData.id} itemData={itemData} todoCheckHandler={todoCheckHandler} todoRemoveHandler={todoRemoveHandler}/>
)
})}
</div>
)
}
export default TodoList
스타일
import styled from 'styled-components'
export const TodoContainer = styled.div`
${({ theme }) => theme.common.flexColumnStart};
background-color: ${({ theme }) => theme.palette.red};
border-radius: 10px;
color: white;
width: 50rem;
height: 33rem;
margin: 3rem auto;
font-family: 'NotoSansBold';
`
export const TodoTitle = styled.p`
font-size: ${({ theme }) => theme.fontSizes.subtitle};
`
스타일 적용
<S.TodoContainer>
, <S.TodoTitle>
로 작성해서 특정 요소에 스타일을 입혀준다.
import * as S from './style'
return (
<S.TodoContainer>
<S.TodoTitle>나만의 Todo List</S.TodoTitle>
<TodoAdd todoId={todoId} todoData={todoData} setTodoData={setTodoData} />
{todoData.map((itemData) => {
return (
<TodoItem
key={itemData.id}
itemData={itemData}
todoCheckHandler={todoCheckHandler}
todoRemoveHandler={todoRemoveHandler}
/>
)
})}
</S.TodoContainer>
)
react-icons 여기서 필요한 아이콘이 있는지 찾아본 후에 있다면 라이브러리 설치를 해준다. yarn add react-icons
사용할 아이콘을 import해주고 html 요소로 표시해준다.
// todoItem
import { GrCheckbox, GrCheckboxSelected } from 'react-icons/gr'
import { AiOutlineCloseCircle } from 'react-icons/ai'
return (
<S.ItemContainer>
<S.ItemButton onClick={() => todoCheckHandler(id)}>
{checked ? <GrCheckboxSelected /> : <GrCheckbox />}
</S.ItemButton>
<S.ItemText>{date}</S.ItemText>
<S.ItemText>{content}</S.ItemText>
<S.ItemButton onClick={() => todoRemoveHandler(id)}>
<AiOutlineCloseCircle />
</S.ItemButton>
</S.ItemContainer>
)
완료된 할일 소팅
완료하면 목록 하단으로 배치가된다.const [sortedData, setSortedData] = useState([]) useEffect(() => { setSortedData(todoData.sort((a, b) => a.checked - b.checked)) }, [todoData])