All'alba vincerò

At dawn, I will win!

React/한입 리액트

[React] To-do List: 일정 추가하기 / 검색하기

나디아 Nadia 2024. 3. 27. 00:16

 

 

 

 

 

To-do List: 일정 추가하기 / 검색하기

 

 

 

📌 전체 컴포넌트 구조

 

🔺 App - 전체

 

 

 

 

🔺 Header - 제목, 날짜 표시

 

 

 

 

🔺 Editor - Todo 입력칸

 

 

 

 

🔺 List - 전체 Todo (TodoItem의 모음)

 

 

 

🔺 TodoItem - 개별 Todo

  • 일정 추가, 수정, 삭제가 일어나는 컴포넌트
  • props를 바꿔가며 반복적으로 렌더링

 

 

 


 

 

📌 일정 추가하기 (+ 렌더링)

  • 내용을 입력하고 추가 버튼을 누르면 todos state값을 변경해줘야 한다.

 

 

🔺 App.jsx

 

임시 데이터로 데이터 모델링 

  • 데이터가 어떤 형태로 있어야 하는지 설정
  • 임시 데이터는 계속 렌더링할 필요 없으니 App 컴포넌트 밖에 선언
// 임시 데이터 (기본 데이터) - 계속 렌더링할 필요 없으니 App 컴포넌트 밖에 선언
const mockDate = [
  {
    id: 0, // index
    isDone: false, // 체크박스
    content: "React 공부하기", // 글 내용
    date: new Date().getTime() // 글 발행 시간
  },
  {
    id: 1, 
    isDone: false, 
    content: "자기소개서 작성하기",
    date: new Date().getTime() 
  },
  {
    id: 2, 
    isDone: false,
    content: "포르폴리오 수정하기", 
    date: new Date().getTime() 
  },
];

 

 

 

 

* App 컴포넌트 내부

 

Todo-Item을 저장할 state 선언

  • 임시 데이터를 todos state초기값으로 설정
  const [todos, setTodos] = useState(mockDate);

 

 

 

 

Todo의 index 역할을 할 useRef 선언

👉 중복 글을 구분하기 위해

  • 임시 데이터 id와 겹치지 않도록 설정
const idRef = useRef(3);

 

 

 

 

(Editor.jsx에서) Todo 입력값을 가져와서(props) 기존의 데이터 객체(임시 데이터)로 만드는 함수 생성

  • 새로 부여한 index를 증가시켜서 id에 부여
  • props로 받은 Todo 입력값(=content)을 키 content에 부여

 

state값 업데이트 하기

  • spread 연산자(...)로 기존의 todos state 배열의 값들을 풀어헤치고 👉 위에서 받은 Todo 입력값 객체(newTodo)를 넣는다.
  const onCreate = (content) => {
    const newTodo = { // 이 데이터 객체를 todos state 배열에 추가해줘야 함
      id: idRef.current++, // 새로 부여한 index를 증가시켜서 id에 부여
      isDone: false,
      content: content, // props로 받은 Todo 입력값을 content에 부여
      date: new Date().getTime()
    }
    
    // state값 업데이트 하기
    // spread 연산자(...)로 기존의 todos state 배열의 값들을 풀어헤치고 -> 추가할 newTodo 데이터를 넣는다.
    setTodos([newTodo, ...todos])
  }

 

 

 

 

return문

  • Editor.jsx에 props로 onCreate() 함수 전달
  • List.jsx에 props로 todos state 전달
  return (
    <div className='App'>
      <Header/>
      <Editor onCreate={onCreate}/> 
      {/* Editor.jsx에 props로 onCreate함수 전달 */}
      <List todos={todos}/> 
      {/* List.jsx에 props로 todos state 전달 */}
    </div>
  )

 

 

 


 

 

🔺 Editor.jsx 

  • Todo 입력칸

 

App.jsx에서 onCreat 함수 받아오기(구조분해할당)

  • onSubmit() 함수에서 실행됨

<input> 입력값을 저장할 state 선언

  • 초기값은 빈 배열("")

 

<input> 입력값을 content state에 저장하기 위한 변경 함수 생성

  • content의 state값을 <input> 입력값으로 변경하기 위해 변경 함수 이용
const Editor = ({onCreate}) => {

    // <input> 입력값을 저장할 state 선언
    const [content, setContent] = useState(""); 
    
    // <input> 입력값을 content state에 저장하기 위한 변경 함수
    const onChangeContent = (e) => {
        setContent(e.target.value)
    }




 

입력칸이 공백일 때 추가 버튼을 누르면 입력칸에 포커싱을 주기 위한 ref 선언

const contentRef = useRef();




 

Enter를 치면 Todo 등록하는 함수

  • Enter 실행: e.keyCode === 13
  • Enter를 누르면 onSubmit() 함수 실행

 

Enter를 치면 실행되는 함수

  • 입력칸이 공백인데 '추가' 버튼을 누르면 입력칸 포커싱 & 추가 막기
  • App.jsx의 onCreate() 함수에 인자값으로 content 전달하여 실행
    (위에서 props로 onCreate 함수를 받아왔기 때문에 가능)
  • Todo 추가 후 content의 state값 초기화 (안하면 입력칸에 문자 남아있음)
    // Enter를 치면 Todo 등록하는 함수
    const onKeyDown = (e) => {
        if(e.keyCode === 13) {
            onSubmit();
        }
    }

    // Enter를 치면 실행되는 함수
    const onSubmit = () => {

        // 입력칸이 공백인데 '추가' 버튼을 누르면 입력칸 포커싱 & 추가 막기
        if (content === "") {
            contentRef.current.focus();
            return;
        }

        // App.jsx의 onCreate 함수에 인자값으로 content 전달하여 실행
        // 위에서 props로 onCreate 함수를 받아왔기 때문에 가능
        onCreate(content);

        // Todo 추가 후 content state 초기화 (안하면 입력칸에 문자 남아있음) 
        setContent("");
    }

 

 

 

 

return문

  • <input> 입력값의 초기값(value)을 content("")로 설정
  • 입력칸 포커싱을 위한 ref 연결(useRef)
  • 값을 입력했을 때(onChange), <input> 입력값을 content state에 저장하기 위한 변경 함수 실행
  • 사용자가 키를 눌렀을 때(onKeyDown), Enter를 치면 Todo를 등록하는 함수 실행
    return (
        <div className="Editor">
            <input 
                ref={contentRef} // 입력칸 포커싱을 위한 ref 연결
                onKeyDown={onKeyDown} // 사용자가 키를 눌렀을 때 동작
                value={content} // <input> 입력값의 초기값을 content("")로 설정해둠
                onChange={onChangeContent} 
                type="text" 
                placeholder="새로운 Todo..." 
            />
            <button onClick={onSubmit}>추가</button>
        </div>
    )

 

 

 


 

 

🔺 List.jsx

  • 전체 Todo (TodoItem의 모음)

 

App.jsx에서 todos state 받아오기(구조분해할당)

 

map( ) 메서드를 통해 todos state 배열에 담긴 데이터를 리스트로 렌더링

  • map( ) 메서드의 매개변수로 todo 전달 (todo엔 하나의 TodoItem 객체가 들어있음) 
  • 매개변수 todo를 spread 연산자(...)로 풀어헤쳐서 TodoItem.jsx에 props로 전달
  • 컴포넌트 렌더링 시 모든 아이템 컴포넌트에 반드시 key props를 고유한 값으로 전달해줘야 한다. (key={})

 

✨ map( )

: 배열의 모든 요소에 콜백 함수 수행 👉 콜백 함수가 리턴한 반환값들을 모아서 새로운 배열로 반환 & 렌더링
- map() 메서드를 JSX에서 사용할 때는 콜백 함수가 HTML 요소를 반환하도록 설정할 수 있음

// App.jsx에서 todos state 받아오기(구조분해할당)
const List = ({todos}) => {

    return (
    <div className="List">
        <h1>Todo List 🌱</h1>
        <input type="text" placeholder="검색어를 입력하세요" />

        <div className="Todos_wrapper">
            <!-- map() 메서드를 통해 todos state 배열에 담긴 데이터를 리스트로 렌더링 -->
            {todos.map((todo)=> {
                return <TodoItem key={todo.id} {...todo}/>;
                <!-- 매개변수 todo의 모든 데이터가 props로 TodoItem.jsx에 전달됨 -->
            })}
        </div>
    </div>
    )
}

export default List;

 

 

 


 

 

🔺 TodoItem.jsx

  • 개별 Todo

 

List.jsx에서 todo 객체 안의 내용들 받아오기(구조분해할당)

  • id, 체크박스(boolean), 내용, 날짜

props로 받아온 todo의 내용물 적용시키기

  • 체크 박스 (checked={})
  • 내용(content) 적용
  • 날짜(date)를 Date 객체로 받아와서 Local Date로 변환(toLocaleDateString())
// List.jsx에서 todo 객체 안의 내용들 받아오기
const TodoItem = ({id, isDone, content, date}) => {

    return (
        // props로 받아온 todo의 내용물 적용시키기
        <div className="TodoItem">
            <input checked={isDone} type="checkbox" />
            <div className="content">{content}</div>
            <div className="date">{new Date(date).toLocaleDateString()}</div>
            <button>삭제</button>
        </div>
    )
}

export default TodoItem;

 

 

 


 

 

📌 일정 검색하기 

  • 검색어 입력, 변경이 되면 리스트 컴포넌트가 리렌더링 되어야 한다. 👉 현재 검색어를 state로 보관

 

 

 

 

🔺 TodoItem.jsx

 

 

검색어를 저장할 state 선언

const [search, setSearch] = useState("");

 

 



검색어 입력 시 입력된 검색어로 state값을 변경하는 변경 함수 실행

const onChangeSearch = (e) => {
        setSearch(e.target.value)
};

 




todos state 배열에서 현재 검색 결과의 값들만 필터링하기

  • 검색어가 공백이면 전체 리스트(todo) 반환
  • 검색어가 공백이 아니면 todo를 매개변수로 받아서(todo를 모두 순회) todo의 내용(content)에 검색어(search)가 포함되어있는 일정만 필터링해서 반환
  • 대소문자 구분을 없애기 위해 검색어를 소문자로 변환해서 검색
const getFilteredData = () => {
    // 공백이면 전체 리스트 반환
    if (search === "") { 
        return todos;
    } 

    // 공백이 아니면
    // todo를 매개변수로 받아서(todo를 모두 순회) todo 내용에 검색어(search)가 포함되어있는 일정만 필터링해서 반환
    return todos.filter((todo) =>
        todo.content.toLowerCase().includes(search.toLowerCase())
        // 대소문자 구분 X: 검색어를 소문자로 변환해서 검색
  );
};

 




검색 필터링 함수를 변수에 담기 + 리렌더링 될때마다 호출

  • 컴포넌트 내부에 선언
const filteredTodos = getFilteredData();





return문

  • 필터링 된 일정을 렌더링할 수 있도록 map() 메서드를 검색 필터링 함수를 저장한 변수로 바꾼다.
    return (
        <div className="List">
            <h1>Todo List 🌱</h1>
            <input 
                placeholder="검색어를 입력하세요"
                value={search} 
                onChange={onChangeSearch}
            />
            
            <div className="Todos_wrapper">
                {filteredTodos.map((todo) => { // 필터링 된 todos를 렌더링
                    return <TodoItem key={todo.id} {...todo} />;
                })}
            </div>
        </div>
    )

 

 

 


 

App.jsx
Editor.jsx
List.jsx
TodoItem.jsx

 

 

 


 

 

출처

한입 크기로 잘라먹는 리액트