JS Library/React

TodoList 기능 구현

원2 2022. 8. 3. 17:33
728x90
반응형

TodoHead

남은 할일 > done이 false 인것의 갯수를 출력

import React from "react";
import styled from "styled-components";
import {useTodoState} from "./TodoContext";

const TodoHeadBlock = styled.div`
    padding-top: 48px;
    padding-left: 32px;
    padding-right: 24px;
    padding-left: 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);

    return (
        <TodoHeadBlock>
            <h1>2022년 08월 03일</h1>
            <div className="day">수요일</div>
            <div className="tasks-left">할일이 {undoneTasks.length}개 남았소</div>
        </TodoHeadBlock>
    );
}

export default TodoHead;

2개나 남았소?

today 설정

Date()와 toLocalDateSring을 사용해보자

import React from "react";
import styled from "styled-components";
import {useTodoState} from "./TodoContext";

const TodoHeadBlock = styled.div`
    padding-top: 48px;
    padding-left: 32px;
    padding-right: 24px;
    padding-left: 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);

    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;

ㅇㅇ 잘 되죠?

 

TodoList

state 를 조회, 렌더링

import React from "react";
import styled from "styled-components";
import TodoItem from "./TodoItem";
import {useTodoState} from "./TodoContext";

const TodoListBlock = styled.div`
    // flex: 1; 자신의 영역을 꽉채우도록 설정
    flex: 1; 
    padding-top: 20px;
    padding-left: 32px;
    padding-bottom: 48px;
    padding-right: 32px;
    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}
            />
            )}
        </TodoListBlock>
    );
}

export default TodoList;

하드코딩 버리고 map()

 

TodoItem

dispatch 사용, 토글 / 삭제 기능 구현

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

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

const TodoItemBlock = styled.div`
    display: flex;
    align-items: center;
    padding-top: 12px;
    padding-bottom: 12px;
    &:hover {
        ${Remove} {
            display: initial;
        }
    }
`;

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;
            color: #38d9a9;
        `}
`;

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

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;

어쨰 화질이 점점 구려지냐..

export default React.memo(TodoItem);

잘 작동되면 React.memo 사용하자, 불필요한 렌더링 방지.

 

TodoCreate

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.div`
    background: #38d9a9;
    &:hover {
        background: #63e6be;
    }
    
    &:active {
        background: #20c997;
    }
    
    z-index: 5;
    cursor: pointer;
    width: 80px;
    height: 80px;
    display: block;
    align-items: center;
    justify-content: center;
    font-size: 60px;
    position: absolute;
    left: 50%;
    bottom: 0px;
    transform: translate(-50%, 50%);
    color: white;
    border-radius: 50%;
    border: none;
    outline: none;
    display: flex;
    align-items: center;
    justify-content: center;
    
    transition: 0.125s all ease-in;
    ${props => 
        props.open &&
        css`
            background: #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.div`
    background: #f8f9fa;
    padding-top: 32px;
    padding-right: 32px;
    padding-bottom: 72px;
    padding-left: 32px;
    
    border-bottom-right-radius: 16px;
    border-bottom-left-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);
    const [value, setValue] = useState('');

    const dispatch = useTodoDispatch();
    const nextId = useTodoNextId();

    const onToggle = () => setOpen(!open);
    const onChange = e => setValue(e.target.value);
    const onSubmit = e => {
        // 새로고침 방지
        e.preventDefault();
        dispatch({
            type: 'CREATE',
            todo: {
                id: nextId.current,
                text: value,
                done: false
            }
        });
        setValue('');
        setOpen(false);
        nextId.current += 1;
    };

    return (
        <>
            {open && (
                <InsertFormPositioner>
                    <InsertForm >
                        <Input
                            autoFocus
                            placeholder="할 일을 입력 후, Enter 를 누르시게"
                            onChange={onChange}
                            value={value}
                        />
                        <button type={"submit"} onClick={onSubmit}>매우당황</button>
                    </InsertForm>
                </InsertFormPositioner>
            )}
            <CircleButton onClick={onToggle} open={open}>
                <MdAdd />
            </CircleButton>
        </>
    );
}

export default React.memo(TodoCreate);

원래라면 엔터를 누르면 ? 입력이 되도록해야하는데 

무엇인가 잘못됐나보다..

당황하지 않고? 걍 간단하게 버튼을 누르면 입력되도록 바꾸자

화질은 역시나 거지다.

 

728x90
반응형

'JS Library > React' 카테고리의 다른 글

React Router  (0) 2022.08.04
Context API 활용 상태 관리  (0) 2022.08.03
todolist 만들기  (0) 2022.08.03
styled-components  (0) 2022.08.02
react-icons link  (0) 2022.08.01
CSS Module  (0) 2022.08.01