본문 바로가기

공부일지/React

비전공자의 공부일지 - Recoil TodoList, 패스트캠퍼스

728x90
반응형
반응형

TodoList 만들기

일정을 등록하고 수정하고 삭제, 완료 기능 있는 todoList를 만들 계획이다.

이번에는 App.js에 atom을 만들지 않고 todoAtom.js 파일을 만들었다.

 

todoAtom.js

import { atom } from "recoil";

export const todoListState = atom({
    key:'todoListState',
    default:[]
})

 

그리고 components 폴더에 TodoItemCreator.js를 만들어서 작업하였다.

 

TodoItemCreator.js

import React, { useState } from 'react'
import { useSetRecoilState } from 'recoil';
import { todoListState } from '../todoAtom';

const TodoItemCreator = () => {
    const [inputValue, setInputValue] = useState("");
    const setTodoList = useSetRecoilState(todoListState)
    const handleChange = (e) =>{
        setInputValue(e.target.value);
    }
    const addItem = () =>{
        setTodoList((oldTodoList) => [
            ...oldTodoList,
            {
                id: getId(),
                text: inputValue,
                isComplate:false
            }
        ])
        setInputValue('')
        console.log(setTodoList);
    }
    return (
        <div>
            <input type='text' value={inputValue} onChange={handleChange} />
            <button onClick={addItem}>Add</button>
        </div>
    )
}

let id = 0;
function getId (){
    return id++
}

export default TodoItemCreator

이번에는 저번과 다르게 useSetRecoilState을 이용해서 작업하였다.

useSetRecoilState란 useRecoilState와 다르게 setter만 가능하다고 한다. setter가 정확히 몰랐는데 update만 되는 거라고 하던데 궁금해서 console.log를 찍어보니

newValueOrUpdater => {
    setRecoilValue$2(storeRef.current, recoilState, newValueOrUpdater);
  }

 

이렇게 나왔다. 그냥 useRecoilState를 cnosole.log를 찍어보면

[
    [],
    null
]

이렇게 value가 나오는 거랑 차이가 확실히 있는 거 같다.

 

id는 getId라는 함수를 만들어서 하나씩 증가되게 만들며 중복으로 겹쳐지지 않게 했다.

 

리스트 출력 및 수정, 완료, 삭제 작업

일단 todo에 적었던 Item을 출력하기 위해 TodoItem.js 컴포넌트를 생성하였다.

 

TodoItem.js

import React from 'react'
import { useRecoilState } from 'recoil'
import { todoListState } from '../todoAtom'

const TodoItem = ({item}) => {

    const [todoList, setTodoList] = useRecoilState(todoListState);

	return (
        <div>
            <input type="text" value={item.text} />
            <input type='checkbox' checked={item.isComplete} />
            <button>X</button>
        </div>
    )
}

export default TodoItem;

todoAtom에서 만들었던 todoListState를 useRecoilState로 불러왔다.

input과 button을 만들어서 value를 출력하게 했다.

 

그리고 App.js에서 배열로 만들었던 default를 map으로 하나씩 출력하게 만들었다.

 

App.js

import { useRecoilValue } from "recoil";
import "./App.css";
import TodoItemCreator from "./components/TodoItemCreator";
import { todoListState } from "./todoAtom";



function App() {
	const todoList = useRecoilValue(todoListState);
    
	return (
		<div className="App">
			<TodoItemCreator />
			{
				todoList.map((todoItem)=>(
					<TodoItem key={todoItem.id} item={todoItem} />
				))
			}
		</div>
	);
}

export default App;

 

 

이제 수정, 삭제, 완료 기능을 만들어 볼까 한다.

 

TodoItme.js

import React from 'react'
import { useRecoilState } from 'recoil'
import { todoListState } from '../todoAtom'

const TodoItem = ({item}) => {

    const [todoList, setTodoList] = useRecoilState(todoListState);
    const index = todoList.findIndex((listItem) => listItem === item)
    const editItemText = ({target:{value}}) => {
        const newList = replaceItemAtIndex(todoList, index,{
            ...item,
            text:value
        });
        setTodoList(newList);
    }

    const toggleItemCompletion = () =>{
        const newList = replaceItemAtIndex(todoList, index,{
            ...item,
            isComplete:!item.isComplete
        });
        setTodoList(newList)
    }
    const deleteItem = () => {
        const newList  = removeItemAtIndex(todoList, index);
        setTodoList(newList)
    }


    return (
        <div>
            <input type="text" value={item.text} onChange={editItemText} />
            <input type='checkbox' checked={item.isComplete} onChange={toggleItemCompletion} />
            <button onClick={deleteItem}>X</button>
        </div>
    )
}

export default TodoItem;

function replaceItemAtIndex(arr, index, newValue){
    return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)]
}

function removeItemAtIndex (arr, index){
    return [...arr.slice(0,index), ...arr.slice(index + 1)]
}

일단 완성시킨 코드를 보며 하나씩 정리해야 좋을 것 같다.

 

const index = todoList.findIndex((listItem) => listItem === item)
const editItemText = ({target:{value}}) => {
    const newList = replaceItemAtIndex(todoList, index,{
        ...item,
        text:value
    });
    setTodoList(newList);
}

return (
    <div>
        <input type="text" value={item.text} onChange={editItemText} />
        <input type='checkbox' checked={item.isComplete} onChange={toggleItemCompletion} />
        <button onClick={deleteItem}>X</button>
    </div>
)

function replaceItemAtIndex(arr, index, newValue){
    return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)]
}

수정기능 부분인데 무슨 todoList에 findIndex를 사용하여 전달받은 item과 같은 게 있는지 있으면 몇 번째인지 반환할 수 있도록 작업하였다. 

input은 onChange에 editItemText이라는 이벤트를 만들어서 value값을 바로 가져온 후 newList를 만들어 replaceItemAtIndex이라는 함수를 만들어 이쪽으로 todoList, index와 새롭게 바뀔 데이터를 전달했다.

replaceItemAtIndex는 slice를 이용해 내가 지금 수정하고 있는 배열의 index전 값과, 후의 값을 잘라서 그래도 붙이고 바뀌는 값도 그대로 추가하는 방식으로 새로운 배열을 만들어 그걸 setTodoList에 전달하였다.

newValue는 text를 제외한 나머지 값은 그대로 사용하기 위해... item을 썼다.

 

수정 부분은 저렇게 코드를 작성해서 진행하였고 완료는 수정과 비슷하였다.

어떻게 보면 완료도 수정이라고 생각해도 될 것 같다. 글자를 바꾸는 것도 complete의 값을 바꾸는 것이니

const toggleItemCompletion = () =>{
    const newList = replaceItemAtIndex(todoList, index,{
        ...item,
        isComplete:!item.isComplete
    });
    setTodoList(newList)
}

수정에서 사용했던 index와 replaceItemAtIndex 그래도 사용하였고 수정과 다른 점은 text가 아니가 isComplete:! item.isComplete를 사용했다는 점이다.

 

이제 삭제는 조금 다른데 코드의 결은 비슷하다.

const deleteItem = () => {
    const newList  = removeItemAtIndex(todoList, index);
    setTodoList(newList)
}

function removeItemAtIndex (arr, index){
    return [...arr.slice(0,index), ...arr.slice(index + 1)]
}

index값을 removeItemAtIndex에 전달해 줘서 해당 index 전과 후의 값만 그대로 적어서 새로운 배열을 만든다는 것이다. 수정과 다른 점은 newValue가 없다는 것이다.

 

Filter 기능

list에서 완료한 항목, 완료하지 못한 항목, 전체 항목 선택해서 볼 수 있게 만든 filter 기능이다.

 

우선  todoAtom에서 filter기능을 사용하기 위한 atom과 selector을 만들어야 된다.

 

todoAtom.js

export const todoListFilterState = atom({
    key:'todoListFilterState',
    default:'Show All'
});

export const filteredTodoListState = selector({
    key: 'filteredTodoListState',
    get: ({ get }) => {
        const filter = get(todoListFilterState);
        const list = get(todoListState);

        switch (filter) {
            case 'Show Completed':
                return list.filter((item) => item.isComplete);
            case 'Show Uncompleted':
                return list.filter((item) => !item.isComplete);
            default:
                return list;
        }
    }
})

우선 todoListFilterState를 만들어 key와 기본 defalut인 show all을 넣었다.

밑에 진짜 filter기능을 작동시킬 selector이다.

filter에 서 todoListFilterState를 지정해 주고 list는 todoListState를 지정해 준다.

그리고 filter 값이 들어올 때마다 그에 맞는 결과를 return 해준다.

 

TodoListfilter.js

import React from 'react'
import { useRecoilState } from 'recoil'
import { todoListFilterState } from '../todoAtom'

const TodoListFilter = () => {

    const [filter, setFilter] = useRecoilState(todoListFilterState)
    const updateFilter = ({target:{value}}) => {
        setFilter(value);
    }

    return (
        <div>
            Filter:
            <select value={filter} onChange={updateFilter}>
                <option value="Show All">All</option>
                <option value="Show Completed">Completed</option>
                <option value="Show Uncompleted">Uncompleted</option>
            </select>
        </div>
    )
}

export default TodoListFilter

recoilState로 filterState를 불러와 select에 있는 값이 바뀔 때마다 업데이트해준다.

따로 글게 설명해야 될 코드는 없는 거 같다. 

 

App.js

import { useRecoilValue } from "recoil";
import "./App.css";
import TodoItemCreator from "./components/TodoItemCreator";
import { todoListState, filteredTodoListState } from "./todoAtom";
import TodoItem from "./components/TodoItem";
import TodoListFilter from './components/TodoListFilter';

function App() {
	const filteredTodoList = useRecoilValue(filteredTodoListState);
	return (
		<div className="App">
			<TodoListFilter />
			<TodoItemCreator />
			{
				filteredTodoList.map((todoItem)=>(
					<TodoItem key={todoItem.id} item={todoItem} />
				))
			}
		</div>
	);
}

export default App;

이젠 todoList의 배열로 출력했던 부분을 filterTodoList로 변경해 준다.

 

 

Stats 기능

todoList에서 내가 작성한 리스트는 몇 개인지 완료한건 몇개인지 총개수에서 몇 퍼센트 완료 했는지 출력하는 기능을 만들 예정이다.

 

todoAtom.js

export const todoListStatsState = selector({
    key: 'todoListStatsState',
    get: ({ get }) => {
        const todoList = get(todoListState);
        const totalNum = todoList.length;
        const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
        const totalUncompletedNum = totalNum - totalCompletedNum;
        const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum;

        return {
            totalNum,
            totalCompletedNum,
            totalUncompletedNum,
            percentCompleted
        }
    }
})

selector을 활용해 listState에서 총 개수 완료 개수 완료하지 못한 개수 퍼센트를 구한다.

 

TodoListFilter.js

import React from 'react'
import { useRecoilValue } from 'recoil'
import { todoListStatsState } from '../todoAtom'

const TodoListStats = () => {
    const {
        totalNum,
        totalCompletedNum,
        totalUncompletedNum,
        percentCompleted
    } = useRecoilValue(todoListStatsState);

    const formattedPercentCompleted = Math.round(percentCompleted * 100)
    return (
        <ul>
            <li>Totla items : {totalNum}</li>
            <li>Items completed : {totalCompletedNum}</li>
            <li>Itmes not completed : {totalUncompletedNum}</li>
            <li>Percent not completed : {formattedPercentCompleted}</li>
        </ul>
    )
}

export default TodoListStats

여기선 별 다른 건 없고 위에서 작성한 내용 그래도 recoilValue로 들고 와서 뿌려주면 된다. 여기서 퍼센트만 Math로 작업해 주면 된다. 

 

이걸로 Recoil을 이용해서 todoList를 만들어 보았다. 

 

요즘따라 내가 잘하고 있는 건가 싶은 생각이 많이 든다 회사를 다니면서 공부를 한다는 게 집중이 잘 안 되는 거 같다.

우리 회사가 업무가 많이 없어 업무시간에 인강을 들으며 공부를 하고 있지만 한 번씩 집중이 안되고 퇴사를 하고 공부를 해야 되나 이런 생각도 많이 든다. 

하지만 그럴 때  주변 친구들한테 물어보면 다들 배부른 소리라고 하고 더 열심히 공부하라고 한다. 그런 말들을 채찍 삼아 오늘도 공부일 지을 작성 한다.

728x90
반응형