Redux 를 이용한 Todo App CRUD 구현

Hyun·2021년 10월 10일
0

리액트 리덕스

목록 보기
11/14

Redux 를 이용하여 Todo App 을 만들어보았다.

구현 기능

  • 기본적인 CRUD 기능
  • 현재 시간을 보여주는 시계 기능
  • 제목을 클릭했을 때 내용이 보이는 기능

    핵심
    CRUD 를 구현함에 있어서 title 과 content 는 하위 컴포넌트에서 useState 를 이용해 관리하고, 데이터만 action 으로 받아와서 처리한다. (input state 를 스토어에서 관리해보니 가독성도 떨어지고 코드만 복잡했었다..)

    스토어에서 관리하는 건 새로운 객체를 생성할 때 주는 id 값과, 수정할 때 받아오는 updateId 값, 제목을 클릭했을 때 내용을 보이게 할 active 값, 할 일 객체가 담긴 배열 todos 가 전부이다.

※ 기본 사항

  • state 의 todos 배열에 새로운 객체를 추가할때는 push 가 아니라 concat 을 쓴다
    push => array.push(값) 를 하면 값이 추가된 array 배열의 길이를 반환한다.
    concat => array.concat(값) 를 하면 값이 추가된 새로운 배열을 반환한다.

TodoModules.jsx (리듀서 모듈)

/* src/modules/TodoModules.jsx */
import React, { useState, useEffect, useRef } from "react";

const padNumber = (num, length) => {
  return String(num).padStart(length, "0");
}

const Time = () => {
  let now = new Date();
  const interval = useRef(null);
  const [time, setTime] = useState({
    hour: padNumber(now.getHours(),2),
    min: padNumber(now.getMinutes(),2),
    sec: padNumber(now.getSeconds(),2)
  });//초기 state 설정

  const {hour, min, sec} = time;

  useEffect(()=> {
    interval.current = setInterval(()=> {
      now = new Date();
      setTime({
        hour: padNumber(now.getHours(),2),
        min: padNumber(now.getMinutes(),2),
        sec: padNumber(now.getSeconds(),2)
      })
    }, 1000)

    return () => clearInterval(interval.current);
  }, [])

  return(
    <div style={{
      textAlign:"center",
      fontSize: "50px",
      marginBottom:"30px"
      }}>
      {hour} : {min} : {sec}
    </div>
  )
}

const TodoItem = ({todo,onDelete, inputModify, onToggle}) => { //{todo}= props.todo
  return(
    <div>
      <p onClick={() => onToggle(todo.id)}>{todo.title}</p> 
      {todo.active ? <p>{todo.content}</p> : null}
      <button onClick={() => {
        inputModify(todo);
      }}>MODIFY</button>
      <button onClick={() => onDelete(todo.id)}>DELETE</button>
    </div>
  )
}

const TodoList = ({todos, onDelete, inputModify, onToggle}) => {//{todos} = props.todos
  return(
    <div>
      <ul>
        {todos.map((todo, index) => <TodoItem onDelete={onDelete} key={index} todo={todo} inputModify={inputModify} onToggle={onToggle}/>)}
      </ul>
    </div>
  )
} 

const Todos = ({onCreate, todos, onDelete, onModify, onUpdate, onToggle}) => {
  const [inputs, setInput] = useState({
    title: '',
    content: ''
  });
  const {title, content} = inputs;
  const inputModify = (todo) => {
    //수정버튼을 눌렀을 때 input에 해당 글이 뜨고, 해당 글의 id를 dispatch한다
    setInput({
      title: todo.title,
      content: todo.content
    });
    onModify(todo.id);
  };
  const inputChange = (e) => {
      setInput({
      ...inputs,
      [e.target.name]: e.target.value
    })
  };
  const inputReset = () => {
    setInput({
      title:'',
      content:''
    })
  }
  return(
    <div>
      <Time/>
      <input onChange={(e) => inputChange(e)} name="title" value={title} placeholder="제목을 입력하세요"/>
      <input onChange={(e) => inputChange(e)} name="content" value={content} placeholder="내용을 입력하세요"/>
      <button onClick={(e) => {
        e.preventDefault();
        onCreate(title,content);
        inputReset();
        }}>ADD</button>
      <button onClick={(e) => {
        e.preventDefault();
        onUpdate(title,content);
        }}>Update</button>  
      <TodoList todos={todos} onDelete={onDelete} inputModify={inputModify} onToggle={onToggle}/>
    </div>
  )
}

export default Todos;

TodoContainer.jsx (컨테이너 컴포넌트)

/* src/containers/TodoContainer.jsx */
import React from "react";
import { useSelector, useDispatch } from "react-redux";//둘다 store의 내장 함수이다.
import Todos from "../components/Todos";
import { create, ddelete, modify, update, toggle } from '../modules/TodoModule';


function TodoContainer() {
  const todos = useSelector(state => state.todos);

  const dispatch = useDispatch();

  const onCreate = (title, content) => dispatch(create(title, content));
  const onDelete = (id) => dispatch(ddelete(id));
  const onModify = (id) => dispatch(modify(id));
  const onUpdate = (title,content) => dispatch(update(title,content));
  const onToggle = (id) => dispatch(toggle(id));

  return(
      <Todos todos={todos} onCreate={onCreate} onDelete={onDelete} onModify={onModify} onUpdate={onUpdate} onToggle={onToggle}/>
  )
}

export default TodoContainer;

Todos.jsx (프레젠테이셔널 컴포넌트)

/* src/components/Todos.jsx */
import React, { useState, useEffect, useRef } from "react";

const padNumber = (num, length) => {
  return String(num).padStart(length, "0");
}

const Time = () => {
  let now = new Date();
  const interval = useRef(null);
  const [time, setTime] = useState({
    hour: padNumber(now.getHours(),2),
    min: padNumber(now.getMinutes(),2),
    sec: padNumber(now.getSeconds(),2)
  });//초기 state 설정

  const {hour, min, sec} = time;

  useEffect(()=> {
    interval.current = setInterval(()=> {
      now = new Date();
      setTime({
        hour: padNumber(now.getHours(),2),
        min: padNumber(now.getMinutes(),2),
        sec: padNumber(now.getSeconds(),2)
      })
    }, 1000)

    return () => clearInterval(interval.current);
  }, [])

  return(
    <div style={{
      textAlign:"center",
      fontSize: "50px",
      marginBottom:"30px"
      }}>
      {hour} : {min} : {sec}
    </div>
  )
}

const TodoItem = ({todo,onDelete, inputModify, onToggle}) => { //{todo}= props.todo
  return(
    <div>
      <p onClick={() => onToggle(todo.id)}>{todo.title}</p> 
      {todo.active ? <p>{todo.content}</p> : null}
      <button onClick={() => {
        inputModify(todo);
      }}>MODIFY</button>
      <button onClick={() => onDelete(todo.id)}>DELETE</button>
    </div>
  )
}

const TodoList = ({todos, onDelete, inputModify, onToggle}) => {//{todos} = props.todos
  return(
    <div>
      <ul>
        {todos.map((todo, index) => <TodoItem onDelete={onDelete} key={index} todo={todo} inputModify={inputModify} onToggle={onToggle}/>)}
      </ul>
    </div>
  )
} 

const Todos = ({onCreate, todos, onDelete, onModify, onUpdate, onToggle}) => {
  const [inputs, setInput] = useState({
    title: '',
    content: ''
  });
  const {title, content} = inputs;
  const inputModify = (todo) => {
    //수정버튼을 눌렀을 때 input에 해당 글이 뜨고, 해당 글의 id를 dispatch한다
    setInput({
      title: todo.title,
      content: todo.content
    });
    onModify(todo.id);
  };
  const inputChange = (e) => {
      setInput({
      ...inputs,
      [e.target.name]: e.target.value
    })
  };
  const inputReset = () => {
    setInput({
      title:'',
      content:''
    })
  }
  return(
    <div>
      <Time/>
      <input onChange={(e) => inputChange(e)} name="title" value={title} placeholder="제목을 입력하세요"/>
      <input onChange={(e) => inputChange(e)} name="content" value={content} placeholder="내용을 입력하세요"/>
      <button onClick={(e) => {
        e.preventDefault();
        onCreate(title,content);
        inputReset();
        }}>ADD</button>
      <button onClick={(e) => {
        e.preventDefault();
        onUpdate(title,content);
        }}>Update</button>  
      <TodoList todos={todos} onDelete={onDelete} inputModify={inputModify} onToggle={onToggle}/>
    </div>
  )
}

export default Todos;


후기

처음에는 input 값을 스토어에 관리하도록 구현했었는데 코드의 가독성, 복잡성만 늘어나고 에러가 발생했었다.

그래서 useState 를 이용해 하위 컴포넌트에서 input 값을 관리하니 코드가 깔끔해졌다. redux 를 필요한 부분에만 사용하는 것이 좋다고 공부할 때 배웠었는데, 이번 기회에 그것을 느낄 수 있었던 것 같다. 앞으로도 코드를 깔끔하게 구현하도록 노력해야겠다.

시계의 시간을 구현할때 그냥 사용하면 오전 5시면 5, 8분이면 8 이렇게 나오는데 padStart() 메서드를 사용하니 05시 08분 처럼 변경하기 매우 유용했다.

profile
better than yesterday

0개의 댓글