프론트엔드/react.js

[React.js] Form, State, CSS 이용한 To-Do-List

개발자딥게 2022. 11. 21. 11:20
반응형

Form 개발

 

React를 이용해 사용자로부터 데이터를 입력받아 처리하기 위한 관문인 Form

이번 실습에서는 State와 이벤트를 활용해 개발을 진행

 

구현할 Form의 형태

  1. 컴포넌트 형태로 개발
  2. Form 컴포넌트 내부에서 State를 이용해 사용자의 입력을 저장/관리
  3. Form Submit 이벤트가 발생되면 부모 컴포넌트로 그 데이터를 전달
import React, { useState, useCallback } from "react";

const InsertForm = ({ onInsert }) => {
    const [inputValue, setInputValue] = useState("");
    
    const handleSubmit = useCallback((event) => {
        // 기본적인 HTML 동작으로 인해 페이지가 새로고침 되는 것을 방지
        event.preventDefault(); 
        // props로 넘어온 onInsert가 정상적인 함수인 지 확인하여 에러 방지
        if(typeof onInsert === "function" && inputValue) { 
            onInsert(inputValue);
        }
        // state 초기화
        setInputValue("");
    },[onInsert, inputValue])

    return (
        <form onSubmit={handleSubmit}>
            <input value={inputValue} onChange={(event) => {
                setInputValue(event.target.value);
            }} />
            <button type="submit">등록</button>
        </form>
    )

}

export default InsertForm;

 

 


List 표현

 

Form으로부터 전달받은 값들을 리스트에 저장 후, 리스트의 값을 Array 메소드 이용하여 순차적으로 화면에 출력

 

구현할 List의 형태

  1. Form으로부터 전달받은 값을 todoList array에 push하는 로직
//src/App.js


import React, { useState } from 'react';
import InsertForm from "./components/InsertForm";
import ListView from "./components/ListView";

function App() {
  const [todoList, setTodoList] = useState([]);
  
  const handleInsert = (value) => {
    setTodoList((current) => {
      const newList = [...current];
      newList.push({
        key: new Date().getTime(), // JSX에서 Array의 값을 표현할 때 각 요소를 구분하기 위한 값
        value: value, // onInsert로부터 전달받은 값,
        isCompleted: false, // 완료 처리를 위한 flag
      });
      return newList;
    })
  }
  
  const handleComplete = (index) => {
    setTodoList((current) => {
      const newList = [...current];
      newList[index].isCompleted = true;
      return newList;
    })
  }
  
  const handleRemove = (index) => {
    setTodoList((current) => {
      const newList = [...current];
      newList.splice(index, 1);
      return newList;
    })
  }
  
  return (
    <div className="App">
        <ListView todoList={todoList} onComplete={handleComplete} onRemove={handleRemove} />
        <InsertForm onInsert={handleInsert} />
    </div>
  );
}

export default App;
  1. todoList와 핸들링함수를 props로 전달받아 화면에 출력하는 ListView 컴포넌트
// src/component/ListView.js


import React from "react";

const ListView = ({todoList, onComplete, onRemove}) => {
  return (
    <div>
      <ol>
        {todoList.map((item, index) => {
          return (
            <li key={item.key}>
              <span>{item.value}</span>
              <button type="button" onClick={() => {
                if(typeof onComplete === "function") {
                  onComplete(index);
                }
              }}>완료</button>
              <button type="button" onClick={() => {
                if(typeof onRemove === "function") {
                  onRemove(index);
                }
              }}>삭제</button>
            </li>
          );
        })}
      </ol>
    </div>
  )

}

export default ListView;

 

 


CSS 꾸미기

 

1. CSS 불러오기

별도의 CSS 파일을 작성 후 프로젝트에 적용하고 싶은 경우 import해서 스타일을 적용한다.

import "./App.css";

 

2. jsx 내에서 inline style로 적용하기

스타일은 반드시 object로 적는다. {{ }}

(jsx에서 자바스크립트 표현을 사용하겠다는 의미의 괄호 + object라는 의미의 괄호)

* 주의) Property name은 camel case로 적는다.

 

return (
    <form
      onSubmit={handleSubmit}
      style={{
        backgroundColor: '#ffffff',
        borderRadius: '16px',
        marginBottom: '16px',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
      }}
    >
      <input
        value={inputValue}
        onChange={event => {
          setInputValue(event.target.value);
        }}
        style={{
          flex: 1,
          border: 'none',
          color: '#000000',
          padding: '6px, 12px',
          backgroundColor: 'transparent',
        }}
      />
      <button
        type="submit"
        style={{
          border: 'none',
          borderRadius: 16,
          backgroundColor: '#3ab6bc',
          color: '#ffffff',
          cursor: 'pointer',
          padding: '8px 16px',
        }}
      >
        등록
      </button>
    </form>
  );

jsx에서 스타일은 반드시 camel case로 선언해야 함!!

background-color 대신 backgroundColor
border-radius 대신 borderRadius

 

 


부가 기능 구현

 

  • 완료버튼 클릭시 이펙트(애니메이션) 추가하기
.listview ol li span {
    ...
    position: relative;
}
.listview ol li span::after {
   content: "";
   position: absolute;
   left: 0;
   top: 50%;
   transition: width 0.2s ease-in-out;
   width: 0%;
   height: 3px;
   background-color: #524FA1;
}
.listview ol li.completed span::after {
   width: 100%;
}

// 제거
.listview ol li.completed span

 

  • todolist 개수를 제한하기

list가 10개 이상이면 제한하는 기능

  const isLimitReached = useMemo(() => {
    return todoList.length >= 10;
  }, [todoList]);
  
  
  return (
    <div className="App">
      <ListView
        todoList={todoList}
        onComplete={handleComplete}
        onRemove={handleRemove}
      />

      {isLimitReached && (
        <div
          style={{
            padding: '8px 16px',
            border: '1px solid #FA466A',
            backgroundColor: '#feecf0',
            color: '#FA466A',
            marginBottom: 16,
          }}
        >
          ※ 할일 목록이 너무 많습니다.
        </div>
      )}
      <InsertForm onInsert={handleInsert} disabled={isLimitReached} />
    </div>
  );
반응형