React To-Do List

2024. 12. 19. 14:25웹 개발

App.jsx

import React, { useState, useRef, useCallback } from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';

const App = () => {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: '기초 알아보기',
      checked: true,
    },
    {
      id: 2,
      text: '컴포넌트 스타일링해 보기',
      checked: true,
    },
    {
      id: 3,
      text: '일정 관리 앱 만들어 보기',
      checked: false,
    },
  ]);

  const nextId = useRef(todos.length + 1); // 다음 추가될 할 일의 id (4부터 시작)

  const onInsert = useCallback(
    // 할 일 추가
    (text) => {
      const todo = {
        id: nextId.current,
        text,
        checked: false,
      };
      setTodos(todos.concat(todo));
      nextId.current += 1; // nextId 1 씩 더하기
    },
    [todos]
  );

  const onRemove = useCallback(
    // 할 일 삭제
    (id) => {
      setTodos(todos.filter((todo) => todo.id !== id)); // id가 일치하지 않는 todo만 남긴 배열로 상태 갱신
    },
    [todos] // todos 상태가 변경될 때마다 함수가 재실행되도록 설정
  );

  const onToggle = useCallback(
    // 상태 토글
    (id) => {
      setTodos(
        todos.map(
          (todo) =>
            todo.id === id
              ? { ...todo, checked: !todo.checked } // 해당 id의 todo를 찾아 체크 상태를 반전
              : todo // 그 외의 todo는 그대로 반환
        )
      );
    },
    [todos]
  );

  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  );
};

export default App;

 

 

TodoTemplate.jsx

import React from 'react';
import '../styles/TodoTemplate.scss';

const TodoTemplate = ({ children }) => {
  return (
    <div className="TodoTemplate">
      <div className="app-title">일정 관리</div>
      <div className="content">{children}</div>
    </div>
  );
};

export default TodoTemplate;

 

 

TodoInsert.jsx

import React, { useState, useCallback } from 'react';
import { MdAdd } from 'react-icons/md'; // 아이콘 라이브러리에서 MdAdd 아이콘 가져오기
import '../styles/TodoInsert.scss'; // 스타일 시트 임포트

const TodoInsert = ({ onInsert }) => {
  // 입력값을 관리하는 state
  const [value, setValue] = useState('');

  const onChange = useCallback((e) => {
    setValue(e.target.value); // input의 값이 변경되면 value state 업데이트
  }, []); // 컴포넌트가 처음 렌더링될 때만 실행됨 (의존성 배열이 비어있으므로)

  const onSubmit = useCallback(
    (e) => {
      e.preventDefault();
      onInsert(value);
      setValue(''); // 입력값 초기화
    },
    [onInsert, value]
  ); // onInsert와 value가 변경될 때마다 실행됨

  return (
    <form className="TodoInsert" onSubmit={onSubmit}>
      <input
        value={value} // input 값은 state value와 연결
        onChange={onChange}
        placeholder="할 일을 입력하세요"
      />
      <button type="submit">
        <MdAdd />
      </button>
    </form>
  );
};

export default TodoInsert;

 

 

TodoList.jsx

import React from 'react';
import TodoListItem from './TodoListItem';
import '../styles/TodoList.scss';

const TodoList = ({ todos, onRemove, onToggle }) => {
  return (
    <div className="TodoList">
      {todos.map((todo) => (
        <TodoListItem
          todo={todo}
          key={todo.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </div>
  );
};

export default TodoList;

 

 

TodoListItem.jsx

import React from 'react';
import classnames from 'classnames';
import '../styles/TodoListItem.scss';

import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';

const TodoListItem = ({ todo, onRemove, onToggle }) => {
  // todo 객체에서 id, text, checked 값 추출
  const { id, text, checked } = todo;

  // style
  const linkStyle = {
    userSelect: 'none',
  };

  return (
    <div className="TodoListItem">
      <span
        style={linkStyle}
        className={classnames('checkbox', { checked })} // checked가 true이면 'checked' 클래스를 추가
        onClick={() => onToggle(id)} // 클릭 시 onToggle 함수 호출하여 체크 상태 변경
      >
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <span className="text">{text}</span>
      </span>
      <div className="remove" onClick={() => onRemove(id)}>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;

 

https://stackblitz.com/edit/vitejs-vite-251wr6js?embed=1&file=src%2FApp.jsx

'웹 개발' 카테고리의 다른 글

React Router DOM  (1) 2024.12.20
React Immer  (0) 2024.12.19
React CSS 스타일링 방법 정리  (0) 2024.12.19
React-icons  (0) 2024.12.18
React Playground / playcode.io  (0) 2024.12.18