Jieunny의 블로그

TodoList ( react + style-component) 본문

카테고리 없음

TodoList ( react + style-component)

Jieunny 2022. 1. 5. 19:23
  • App.js
    • import logo from './logo.svg';
      // import './App.css';
      import styled from 'styled-components';
      import Template from './components/Template';
      import TodoList from './components/TodoList';
      import TodoInsert from './components/TodoInsert';
      import { useState } from 'react';
      import { MdAddCircle } from 'react-icons/md';
      
      let nextId = 4;   // 밖에다 선언해놔야 리렌더링해도 4로 돌아가지 않음
      
      function App() {
        const [selectedTodo, setSelectedTodo] = useState('');
        const [insertToggle, setInsertToggle] = useState(false);
        const [todos, setTodos] = useState([
          {
            id: 1,
            text: '할일 1',
            checked: true
          },
          {
            id: 2,
            text: '할일 2',
            checked: false
          },
          {
            id: 3,
            text: '할일 3',
            checked: false
          }
        ]);
      
        function onInsertToggle(){
          if (selectedTodo){
            setSelectedTodo(null);
          }
          setInsertToggle((prev)=>!prev);
        }
      
        function onInsertTodo(text){
          if(text === ''){
            alert('할일을 입력해주세요');
          }
          else{
            const todo = {
              id:  nextId,
              text: text,
              checked : false
            }
            setTodos(todos.concat(todo));
            nextId++;
          }
        }
      
        function onCheckToggle(id){
          setTodos(todos.map((todo)=>{
            return( 
              todo.id === id ? {...todo, checked: !todo.checked} : todo
            );
          }))
        }
      
        function onChangeSelectedTodo(todo){
          setSelectedTodo(todo);
        }
      
        function onRemove(id){
          onInsertToggle();
          setTodos(todos.filter(todo => todo.id !== id))
        }
      
        function onUpdate(id, text){
          onInsertToggle();
          setTodos(todos.map((todo)=>{
            return(
              todo.id === id ? {...todo, text} : todo
            );
          }))
        }
        
        return (
          <div>
            <Template todos={todos}>
              <TodoList  
                todos={todos}
                onCheckToggle={ onCheckToggle }
                onInsertToggle={ onInsertToggle }
                onChangeSelectedTodo={ onChangeSelectedTodo }
              />
              <AddTodoButton onClick={onInsertToggle}>
                <MdAddCircle />
              </AddTodoButton>
              { insertToggle ? 
                <TodoInsert 
                  onInsertToggle={ onInsertToggle } 
                  onInsertTodo={ onInsertTodo }
                  selectedTodo={ selectedTodo }
                  onRemove={ onRemove }
                  onUpdate={ onUpdate }
                  /> : null }
            </Template>
          </div>
        );
      }
      
      const AddTodoButton = styled.div`
        position: fixed;
        right: 10px;
        bottom: 20px;
        font-size: 5rem;
        color: pink;
      `;
      
      export default App;
  • Template.js
    • import React from 'react';
      import TodoList from './TodoList';
      // import './Template.css'
      import styled from 'styled-components';
      
      function Template({ children, todos }){
        return(
          <TodoTemplate>
            <Title>오늘의 할일 ({todos.length})</Title>
            { children }
          </TodoTemplate>
        );
      }
      
      const TodoTemplate = styled.div`
        width: 80%;
        z-index: 50;
        border: 1px solid pink;
        border-radius: 5px;
        padding: 20px;
        margin: auto;
        margin-top: 50px;
        text-align: center;
      `;
      
      const Title = styled.h2`
        margin: 0;
        padding: 0;
        color: rgb(184, 130, 211);
      `;
      
      export default Template;

 

  • TodoList.js
    • import React from 'react';
      // import './TodoList.css'
      import TodoItem from './TodoItem';
      import styled from 'styled-components';
      import TodoInsert from './TodoInsert';
      
      function TodoList({ todos, onCheckToggle, onInsertToggle, onChangeSelectedTodo }){
        return ( 
          <Todolist> {todos.map((todo,i)=>{
          return <TodoItem 
          key={i} 
          todo={todo} 
          onCheckToggle={ onCheckToggle }
          onInsertToggle={ onInsertToggle }
          onChangeSelectedTodo={ onChangeSelectedTodo }
          />
        })}</Todolist> )
      }
      
      const Todolist = styled.div`
        margin-top: 20px;
        text-align: left;
      `;
      
      export default TodoList;

 

  • TodoItem.js
    • import React from "react";
      import { MdCheckCircle, MdCheckBoxOutlineBlank } from 'react-icons/md'
      // import './TodoItem.css'
      import styled, { css } from 'styled-components';
      
      function TodoItem({ todo, onCheckToggle, onInsertToggle, onChangeSelectedTodo }){
        const { id, text, checked } = todo;  // 객체 구조 분해 할당
        return (
          <Item>
            <Check checked={checked}>
            {/* <div className={`content ${checked ? "checked" : ""}`}>  */}
              { checked ? 
                < MdCheckCircle onClick={()=>{onCheckToggle(id)}} /> 
                :
                < MdCheckBoxOutlineBlank onClick={()=>{onCheckToggle(id)}} />} 
              <Text onClick={()=>{
                onInsertToggle()
                onChangeSelectedTodo(todo);
              }}><Content>{ text }</Content></Text>
            </Check>
          </Item>
        );
      }
      
      const Item = styled.div`
        border: 1px solid pink;
        border-radius: 5px;
        padding: 10px;
        box-shadow: 5px 3px 5px 0.5px pink;
        margin-bottom: 10px;
      `;
      
      const Text = styled.div`
        display: inline-block;
        vertical-align: middle;
        cursor: pointer;
      `;
      
      const Content = styled.p`
      `;
      
      const Check = styled.div`
        svg{ 
          display: inline-block;
          padding: 5px;
          vertical-align: middle;
          color: hotpink;
          cursor: pointer; 
        }
        p {
          ${({ checked }) => {
            return checked ? 
              css`
                text-decoration : line-through;
                opacity : 0.5;
              ` 
            : null; 
          }
        }
      `;
      
      export default TodoItem;

 

  • TodoInsert.js
    • import React, { useEffect, useState } from 'react';
      import { MdAddCircle } from 'react-icons/md';
      import { TiPencil, TiTrash } from 'react-icons/ti';
      import styled from 'styled-components';
      // import './TodoInsert.css';
      
      function TodoInsert({ onInsertToggle, onInsertTodo, selectedTodo, onRemove, onUpdate }){
        const [value, setValue] = useState('');
      
        const onChange = (e)=>{
          setValue(e.target.value);
        };
      
        function onSubmit(e){
          e.preventDefault();
          onInsertTodo(value);
          setValue('');
          onInsertToggle();
        };
      
        useEffect(()=>{
          if (selectedTodo){
           setValue(selectedTodo.text);
          }
        }, [selectedTodo]);
        
        return(
          <div>
            <Background onClick={ onInsertToggle } />
              <Form onSubmit={selectedTodo ? ()=>{ onUpdate(selectedTodo.id, value) }: onSubmit }>
                <Input 
                  placeholder="할일을 입력해주세요."
                  onChange={ onChange }
                  value={ value }
                ></Input>
                {selectedTodo ? (
                <div className="rewrite">
                  <TiPencil className="pencil" onClick={()=>{
                    onUpdate(selectedTodo.id, value);
                  }}/>
                  <TiTrash className="trash" onClick={()=>{
                    onRemove(selectedTodo.id);
                  }} />
                </div>
                ) : ( 
                <Submit type="submit"><MdAddCircle /></Submit>
                )
                }
              </Form>
          </div>
        );
      }
      
      const Background = styled.div`
        position: fixed;
        z-index: 200;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        background: rgb(162, 123, 181);
        width: 100vw;
        height: 100vh;
        opacity: 0.8;
        cursor: pointer;
        align-items: center;
      `;
      
      const Form = styled.form`
        position: fixed;
        top: 40%;
        left: 10%;
        z-index: 300;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        width: 300px;
        height: 150px;;
        background: white;
        border-radius: 5px;
        box-shadow: 3px 3px 3px 0.5px pink;
      
        .pencil, .trash{
          font-size: 1.5rem;
          color: pink;
          padding-top: 20px;
          padding-left: 10px;
          padding-right: 20px;
        }
      `;
      
      const Input = styled.input`
        border: 0;
        border-bottom: 1px solid rgb(162, 123, 181);
        background: inherit;
        padding: 5px;
        width: 210px;
        font-size: 1.3rem;
        margin-top: 10px;
        text-align: left;
        outline: none;
      `;
      
      const Submit = styled.button`
        border: 0;
        background: inherit;
        color: pink;
        font-size: 1.5rem; 
        margin-top: 15px; 
      `;
      
      export default TodoInsert;
  • TodoItem.js 에서 checked 클래스이름을 조건부로 전달하는 부분을 styled-component로 바꾸는 데에서 막혔었다.
    • CSS 속성이 아닌 다른 Props 를 넘길때에는 다른 방법을 이용해야한다.