본문 바로가기
코리아 IT아카데미/react.js

4,5,6일차 | react3(todoList)

by Sharon kim 2022. 3. 10.

App.js

import React from 'react';
import { createGlobalStyle } from 'styled-components';
//yarn add styled-components
import TodoTemplate from './components/todoTemplate'; 
import TodoHead from './components/todohead';
import TodoList from './components/todolist';
import TodoCreate from './components/todoCreate';
import { TodoProvider } from './todoContext';


const GlobalStyle = createGlobalStyle`
    body {
      background-color: skyblue;
    }
`;

function App() {
  return (
    <>
      <TodoProvider>
        <GlobalStyle />
        <TodoTemplate>
          <TodoHead />
          <TodoList />
          <TodoCreate />
        </TodoTemplate>
      </TodoProvider>
    </>
  );
}

export default App;

 

todoContext.js

import React, { useReducer, createContext, useContext, useRef } from 'react';

const initialTodos = [
    {
        id: 1,
        text: '프론트엔드 프로젝트 만들기',
        done: false
    },
    {
        id: 2,
        text: '밥 잘 챙겨먹기',
        done: true
    },
    {
        id: 3,
        text: '운동하기',
        done: true
    },
    {
        id: 4,
        text: '일기쓰기',
        done: false
    }
];

function todoReducer(state, action){
    switch(action.type){
        case 'CREATE':
            return state.concat(action.todo);
        
        case 'TOGGLE':
            return state.map(todo =>
                todo.id === action.id ? { ...todo, done: !todo.done } : todo );

        case 'REMOVE':
            return state.filter(todo => todo.id !== action.id);
        
        default:
            throw new Error(`${action.type}이 잘못 전달됨`);
    }
}

const TodoStateContext = createContext();
const TodoDispatchContext = createContext();
const TodoNextIdContext = createContext();

export function TodoProvider({ children }) {
    const [state, dispatch] = useReducer(todoReducer, initialTodos);
    const nextId = useRef(5);

    return (
        <TodoStateContext.Provider value={state}>
            <TodoDispatchContext.Provider value={dispatch}>
                <TodoNextIdContext.Provider value={nextId}>
                {children}
                </TodoNextIdContext.Provider>
            </TodoDispatchContext.Provider>
        </TodoStateContext.Provider>
    );
}

export function useTodoState(){
    const context = useContext(TodoStateContext);
    if(!context){
        throw new Error('TodoProvider를 찾을 수 없음');
    }
    return context;
}

export function useTodoDispatch(){
    const context = useContext(TodoDispatchContext);
    if(!context){
        throw new Error('TodoProvider를 찾을 수 없음');
    }
    return context;
}

export function useTodoNextId(){
    const context = useContext(TodoNextIdContext);
    if(!context){
        throw new Error('TodoProvider를 찾을 수 없음');
    }
    return context;
}

 

components>todoCreate.jsx

import React, {useState} from "react";
import styled, {css} from "styled-components";
import {MdAdd} from "react-icons/md";
import {useTodoDispatch, useTodoNextId} from "../todoContext";

const CircleButton = styled.button`
  background-color: #38d9a9;
  &:hover {
    background-color: #63e6be;
  }
  &:active {
    background: #20c997;
  }
  z-index: 5;
  cursor: pointer;
  width: 80px;
  height: 80px;
  position: absolute;
  left: 50%;
  bottom: 0px;

line-height:10px;
  text-align:center;
  font-size: 60px;

  transform: translate(-50%, 50%);
  color: white;
  border-radius: 50%;
  border: none;
  outline: none;
  transition: 0.2s all ease-in;

  ${(props) =>
    props.open &&
    css`
      background-color: #ff6b6b;
      &:hover {
        background: #ff8787;
      }
      &:active {
        background: #fa5252;
      }
      transform: translate(-50%, 50%) rotate(45deg);
    `}
`;

const InsertFormPositioner = styled.div`
  width: 100%;
  bottom: 0;
  left: 0;
  position: absolute;
`;

const InsertForm = styled.form`
  background: #f8f9fa;
  padding: 32px 32px 72px 32px;
  border-bottom-left-radius: 16px;
  border-bottom-right-radius: 16px;
  border-top: 1px solid #e9ecef;
`;

const Input = styled.input`
  padding: 12px;
  border-radius: 4px;
  border: 1px solid #dee2e6;
  width: 100%;
  outline: none;
  font-size: 18px;
  box-sizing: border-box;
`;

function TodoCreate() {
  const [open, setOpen] = useState(false); // <InsertFormPositioner>에 대한 있다 없다 상태 초기값
  const [value, setValue] = useState(""); // <Input> 태그에 들어가는 값을 상태 초기값 설정

  //리덕스함수 생성상태, 현재 배열의 마직막, 키값, 연산값
  const dispatch = useTodoDispatch();
  const nextId = useTodoNextId();
  const onToggle = () => setOpen(!open); // <InsertFormPositioner>에 대한 있다 없다 상태 설정 함수
  const onchange = (e) => setValue(e.target.value);
  const onSubmit = (e) => {
    e.preventDefault(); // 서버로 데이터를 전송하는 기능을 막음(새로고침X)
    dispatch({
      type: "CREATE",
      todo: {
        id: nextId.current,
        text: value,
        done: false,
      },
    });
    setValue("");
    setOpen(false);
    nextId.current += 1;
  };

  return (
    <>
      {open && (
        <InsertFormPositioner>
          <InsertForm onSubmit={onSubmit}>
            <Input
              autoFocus
              placeholder="할 일을 입력 후, Enter를 누르세요"
              onChange={onchange}
              value={value}
            />
          </InsertForm>
        </InsertFormPositioner>
      )}

      <CircleButton onClick={onToggle} open={open}>
        <MdAdd />
      </CircleButton>
    </>
  );
}

export default React.memo(TodoCreate);

 

components>todoHead.jsx

import React from 'react';
import styled from 'styled-components';
import { useTodoState } from '../todoContext';

const TodoHeadBlock = styled.div`
    padding: 48px 32px 24px 32px;
    border-bottom: 1px solid #e9ecef;


    h1 {
        margin: 0;
        font-size: 36px;
        color: #343a40;
    }

    .day {
        margin-top: 4px;
        color: #868e96;
        font-size: 21px;
    }

    .tasks-left {
        color: #20c997;
        font-size: 18px;
        margin-top: 40px;
        font-weight: bold;
    }
`;

function TodoHead(){
  const todos = useTodoState();
  const undoneTasks = todos.filter((todo) => !todo.done);
  //done -> 할일 리스트에서 체크가 되었는지 유무, 선택 안된 개수를 파악

  //map() 배열요소 개수만큼 실행한다.
  //filter(조건) 조건에 해당되는 값을 되돌려 받는

  const today = new Date();
  const dateString = today.toLocaleDateString("ko-KR", {
    year: "numeric",
    month: "long",
    day: "numeric",
  });
  const dayName = today.toLocaleDateString("ko-KR", {weekday: "long"});
  return (
    <TodoHeadBlock>
      <h1>{dateString}</h1>
      <div className="day">{dayName}</div>
          <div className="tasks-left">할 일 {undoneTasks.length}개 남음</div>
    </TodoHeadBlock>
  );
}

export default TodoHead;

 

components>todoItem.jsx

import React from 'react';
import styled, {css} from 'styled-components';
import { MdDone, MdDelete } from 'react-icons/md';
import { useTodoDispatch } from '../todoContext';

//yarn add react-icons

const TodoItemBlock = styled.div`
    display: flex;
    align-items: center;
    padding-top: 12px;
    padding-bottom: 12px;
`;

const CheckCircle = styled.div`
    width: 32px;
    height: 32px;
    border-radius: 16px;
    border: 1px solid #ced4da;
    font-size: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 20px;
    cursor: pointer;
    ${props => props.done && css`
    border: 1px solid #38d9a9;
   `}
`;

const Text = styled.div`
    flex: 1;
    font-size: 21px;
    color: #495057;
    ${props => props.done && 
        css`
            color: #ced4da;
        `}
`;

const Remove = styled.div`
    display: flex;
    align-items: center;
    justify-content: center;
    color: #dee2e6;
    font-size: 24px;
    cursor: pointer;
    // &:hover {
    //     color: #ff6b6b;
    // }
    // display: none;
`;

function TodoItem({ id, done, text }) {
    const dispatch = useTodoDispatch();
    
    const onToggle = () => dispatch({ type: 'TOGGLE', id });//체크 상태를 발생
    const onRemove = () => dispatch({type: "REMOVE", id});//삭제상태를 발생

    return(
    <TodoItemBlock>
            <CheckCircle done={done} onClick={onToggle}>{done && <MdDone/>}</CheckCircle>
            <Text done={done}>{text}</Text>
            <Remove onClick={onRemove}>
                <MdDelete/>
            </Remove>

    </TodoItemBlock>
    )
}


export default TodoItem;

 

components>todoList.jsx

import React from 'react';
import styled from 'styled-components';
import TodoItem from './todoItem';
import { useTodoState } from '../todoContext';


const TodoListBlock = styled.div`
    flex: 1;
    padding: 20px 32px;
    padding-bottom: 48px;
    overflow-y: auto;
`;


function TodoList() {
  const todos = useTodoState();
    return (
      <TodoListBlock>
        {todos.map(todo => (//배열을 반복해서 출력
                     <TodoItem 
                        key={todo.id}
                        id={todo.id}
                        text={todo.text}
                        done={todo.done}
                     />
             ))}
        {/* <TodoItem text="프로젝트 생성하기" done={true} />
        <TodoItem text="컴포넌트 스타일링 하기" done={true} />
        <TodoItem text="Context 만들기" done={false} />
        <TodoItem text="기능 구현하기" done={false} /> */}
      </TodoListBlock>
    );
}

export default TodoList;

 

components>todoTemplate.jsx

import React from 'react';
import styled from 'styled-components';

const TodoTemplateBlock = styled.div`
    width: 512px;
    height: 768px;

    position: relative;
    background-color: white;
    border-radius: 16px;
    box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);

    margin: 0 auto;
    
    margin-top: 96px;
    margin-bottom: 32px;
    display: flex;
    flex-direction: column;
`;

function TodoTemplate({children}){
 return(
    <TodoTemplateBlock>
     {children}
    </TodoTemplateBlock>
 )

}

export default TodoTemplate;

 

'코리아 IT아카데미 > react.js' 카테고리의 다른 글

6일차 | react_Route  (0) 2022.03.16
5일차 | react4(Icon, styled)  (0) 2022.03.14
3일차 | react2  (0) 2022.03.10
2일차 | react2  (0) 2022.03.07
1일차 | react 기본  (0) 2022.03.03