[React실강] TodoList

youngseo·2022년 7월 30일
0

새로배운 내용

목록 보기
35/42
post-thumbnail

TodoList 만들기

components폴더 내부에 TodoList, TodoItem, ToddAdd 컴포넌트를 각각 만들어줍니다.

1. 컴포넌트 작성

TodoList.js

  • todo 리스트를 저장 할 수 있도록 useState를 이용해 배열을 하나 state로 만들어줍니다.
  • todoData를 map을 이용해 하나씩 하나씩 보여줍니다.
import React, { useState, useRef } from 'react'
import TodoItem from '../TodoItem'
import TodoAdd from '../TodoAdd'

function TodoList() {
  const [todoData, setTodoData] = useState([
    {
      id: 1,
      date: '2022-07-28',
      context: '강의하기',
      checked: false,
    },
  ])

  const deleteTodo = (id) => {
    setTodoData(todoData.filter((todoItem) => todoItem.id !== id))
  }

  const idRef = useRef(2)

  const todoCheckHandler = (id) => {
    setTodoData(
      todoData.map((itemData) =>
        itemData.id === id
          ? { ...itemData, checked: !itemData.checked }
          : itemData,
      ),
    )
  }

  return (
    <div>
      <TodoAdd idRef={idRef} todoData={todoData} setTodoData={setTodoData} />
      {todoData.map((todoItem) => (
        <TodoItem
          todoItem={todoItem}
          key={todoItem.id}
          deleteTodo={() => deleteTodo(todoItem.id)}
          todoCheckHandler={() => todoCheckHandler(todoItem.id)}
        />
      ))}
    </div>
  )
}

export default TodoList

TodoItem

import React, { useState } from 'react'

function TodoAdd({ idRef, setTodoData, todoData }) {
  const [userInput, setUserInput] = useState({
    date: '',
    context: '',
  })

  const addTodo = (userInput) => {
    setTodoData([
      ...todoData,
      {
        id: idRef.current,
        date: userInput.date,
        context: userInput.context,
        checked: false,
      },
    ])
    idRef.current += 1
  }

  const userInputHandler = (e) => {
    const { name, value } = e.target
    setUserInput({
      ...userInput,
      [name]: value,
    })
  }

  return (
    <div>
      <input type="date" name="date" onChange={userInputHandler} />
      <input name="context" onChange={userInputHandler} />
      <button
        onClick={() => {
          addTodo(userInput)
        }}
      >
        추가하기
      </button>
    </div>
  )
}

export default TodoAdd

TodoAdd

import React, { useState } from 'react'

function TodoAdd({ idRef, setTodoData, todoData }) {
  const [userInput, setUserInput] = useState({
    date: '',
    context: '',
  })

  const addTodo = (userInput) => {
    setTodoData([
      ...todoData,
      {
        id: idRef.current,
        date: userInput.date,
        context: userInput.context,
        checked: false,
      },
    ])
    idRef.current += 1
  }

  const userInputHandler = (e) => {
    const { name, value } = e.target
    setUserInput({
      ...userInput,
      [name]: value,
    })
  }

  return (
    <div>
      <input type="date" name="date" onChange={userInputHandler} />
      <input name="context" onChange={userInputHandler} />
      <button
        onClick={() => {
          addTodo(userInput)
        }}
      >
        추가하기
      </button>
    </div>
  )
}

export default TodoAdd

2. 스타일드 컴포넌트 적용

2-1 styles

가장 먼저 해야할 일은 styles폴더 안의 globalStyle.js와 theme.js 적용입니다.

theme,js

컬러파레트의 색상 조합을 이용해 진행하겠습니다.

const palette = {
  yellow: '#F9F9C5',
  green: '#D9F8C4',
  orange: '#FAD9A1',
  red: '#F37878',
}

const common = {
  flexCenter: `
    display: flex;
    align-items: center;
    justify-content: center;
  `,
  flexAround: `
    display: flex;
    align-items: center;
    justify-content: space-around;
  `,
  flexColumnStart: `
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
  `,
}

const fontSizes = {
  title: '2rem',
  subtitle: '1.5rem',
  paragraph: '1rem',
}

const theme = {
  palette,
  common,
  fontSizes,
}

export default theme

globalStyle.js

import { createGlobalStyle } from "styled-components";
import BlackHanSans from '../assets/fonts/BlackHanSans-Regular.ttf'

export default createGlobalStyle`
  @font-face {
    font-family: "BlackHanSans"
    src: url(${BlackHanSans}) format('truetype')
  }
`

App.js

import { ThemeProvider } from 'styled-components';
import TodoList from './components/TodoList';
import theme from './styles/theme'
import GlobalStyle from './styles/globalStyle';

function App() {
  return (
    <ThemeProvider theme={theme}>
      <GlobalStyle />
      <TodoList />
    </ThemeProvider>

  );
}

export default App;

2-2 컴포넌트 별 스타일 적용

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: 'BlackHanSans';
`

export const TodoTitle = styled.p`
  font-size: ${({ theme }) => theme.fontSizes.subtitle};
`
import * as S from './style'

function TodoList(){
  ...
  return (
    <S.TodoContainer>
      <TodoAdd idRef={idRef} todoData={todoData} setTodoData={setTodoData} />
      {todoData.map((todoItem) => (
        <TodoItem
          todoItem={todoItem}
          key={todoItem.id}
          deleteTodo={() => deleteTodo(todoItem.id)}
          todoCheckHandler={() => todoCheckHandler(todoItem.id)}
        />
      ))}
    </S.TodoContainer>
  )
}

TodoItem

import styled from 'styled-components'

export const ItemContainer = styled.div`
  ${({ theme }) => theme.common.flexAround};
  flex-shrink: 0;

  background-color: ${({ theme }) => theme.palette.yellow};
  border-radius: 10px;
  color: black;
  width: 90%;
  height: 5rem;
  margin: 0.5rem;

  font-family: 'NotoSansBold';
`

export const ItemButton = styled.div`
  ${({ theme }) => theme.common.flexCenter};
  border: none;
  outline: none;
  cursor: pointer;

  &:hover {
    opacity: 0.5;
  }
`

export const ItemText = styled.div`
  font-size: ${({ theme }) => theme.fontSizes.paragraph};
  width: 30%;
`

TodoAdd

import styled from 'styled-components'

export const AddContainer = styled.div`
  ${({ theme }) => theme.common.flexAround};
  flex-shrink: 0;

  color: black;
  width: 90%;
  margin-bottom: 1rem;

  font-family: 'NotoSansBold';
`

export const AddButton = styled.button`
  ${({ theme }) => theme.common.flexCenter};
  border: none;
  outline: none;
  cursor: pointer;

  background-color: ${({ theme }) => theme.palette.green};
  height: 2rem;
  width: 5rem;
  border-radius: 5px;

  &:hover {
    opacity: 0.5;
  }
`

export const AddInput = styled.input`
  border: none;
  outline: none;
  border-radius: 5px;
  padding: 0rem 0.5rem;

  height: 2rem;
  width: 30%;
`
import React, { useState } from 'react'
import * as S from './style'

function TodoAdd({ todoId, todoData, setTodoData }) {
  const [userInput, setUserInput] = useState({ date: '', content: '' })

  const userInputHandler = (e) => {
    const { name, value } = e.target
    setUserInput({ ...userInput, [name]: value })
  }

  const todoAddHandler = (userInput) => {
    setTodoData([
      ...todoData,
      {
        id: todoId.current,
        date: userInput.date,
        content: userInput.content,
        checked: false,
      },
    ])
    todoId.current += 1
  }

  return (
    <S.AddContainer>
      <S.AddInput type="date" name="date" onChange={userInputHandler} />
      <S.AddInput name="content" onChange={userInputHandler} />
      <S.AddButton onClick={() => todoAddHandler(userInput)}>
        추가하기
      </S.AddButton>
    </S.AddContainer>
  )
}

export default TodoAdd

2-3 체크박스 아이콘 설정

react-icons

$ yarn add react-icons

TodoItem

import React from 'react'
import * as S from './style'
import { GrCheckbox, GrCheckboxSelected } from 'react-icons/gr'
import { AiOutlineCloseCircle } from 'react-icons/ai'

function TodoItem({ todoItem, deleteTodo, todoCheckHandler }) {
  const { date, context, checked } = todoItem

  return (
    <S.ItemContainer>
      <S.ItemButton onClick={todoCheckHandler}>
        {checked ? <GrCheckboxSelected /> : <GrCheckbox />}
      </S.ItemButton>
      <S.ItemText>{date}</S.ItemText>
      <S.ItemText>{context}</S.ItemText>
      <S.ItemButton onClick={deleteTodo}>
        <AiOutlineCloseCircle />
      </S.ItemButton>
    </S.ItemContainer>
  )
}

export default TodoItem

2-4 완료 시 스타일 변경

TodoItem

export const ItemContainer = styled.div`
  opacity: ${({ isChecked }) => (isChecked ? '0.5' : '1')};
`
return (
    <S.ItemContainer isChecked={todoItem.checked}>
    ...
)

2-5 useEffect을 이용한 체크 기준 정렬

TodoList

  useEffect(() => {
    setSortData(todoData.sort((a, b) => a.checked - b.checked))
  }, [todoData])

깃허브 저장소

0개의 댓글