본문 바로가기
Web Study

리액트와 리덕스로 TodoList 만들기 (삭제, 완료, 추가)

by 쿠리의일상 2023. 4. 9.

리액트, 리덕스 연습 겸 TodoList 를 만들어보았다.

 

완성작

디자인은 styled components로 간단하게... 만들었다.

배운 내용을 토대로 id를 nextId 로 따로 만들어서 state 로 빼줘서 처리해줬다.

삭제와 완료는 모두 해당 id 를 활용했으며 filter 메서드를 사용했다.

 

// 초기 상태
const initState = {
  todoList: [],
};

// id 제공을 위한 길이
let count = initState.todoList.length;
initState['nextId'] = count;

// 액션 타입 선언
const ADD = 'todo/ADD';
const REMOVE = 'todo/REMOVE';
const DONE = 'todo/DONE';

// 액션 함수 선언
export function add(todo) {
  return {
    type: ADD,
    payload: todo,
  };
}

export function remove(id) {
  return {
    type: REMOVE,
    id,
  };
}

export function done(id) {
  return {
    type: DONE,
    id,
  };
}

// 리듀서
export default function todo(state = initState, action) {
  switch (action.type) {
  // 추가 액션
    case ADD:
      return {
        ...state,
        todoList: [
          ...state.todoList,
          {
            id: action.payload.id,
            text: action.payload.text,
            done: false,
          },
        ],
        nextId: action.payload.id + 1,
      };
  // 완료 액션
    case DONE:
      return {
        ...state,
        todoList: state.todoList.map((e) => {
          if (action.id === e.id) {
            return {
              ...e,
              done: true,
            };
          } else {
            return e;
          }
        }),
      };
  // 삭제 액션
    case REMOVE:
      return {
        ...state,
        todoList: state.todoList.filter((e) => {
          if (action.id !== e.id) return { ...e };
        }),
      };
    default:
      return state;
  }
}

 

스토어 설정이 위처럼 완료되면 cpnfigureStore() 와 Provider 로 지정해준다.

const reduxDevTool =
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = configureStore({ reducer: rootReducer }, reduxDevTool);

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <GlobalStyle />
    <App />
  </Provider>,
);

 

// ListContainer

const MyListContainer = styled.div`
  max-width: 1100px;
  margin: auto;
`;

const MyContainer = styled.div`
  display: flex;
  justify-content: space-around;
  margin-top: 30px;
`;

export default function ListContainer() {
  return (
    <MyListContainer>
      <TodoInput />
      <MyContainer>
        <TodoList />
        <DoneList />
      </MyContainer>
    </MyListContainer>
  );
}

 

// TodoInput

const MyContainer = styled.div`
  text-align: center;
  margin-top: 50px;
`;

const MyTitle = styled.h2`
  font-size: 2rem;
  font-weight: 800;
  margin-bottom: 10px;
`;

const MyButton = styled.button`
  width: 70px;
  height: 30px;
  box-sizing: border-box;
  font-size: 1.1rem;
  border-radius: 6px;

  background-color: #ffdb29;
  border-radius: 6px;
  border: none;
  cursor: pointer;

  font-weight: 700;
`;

const MyInput = styled.input`
  width: 240px;
  height: 30px;
  box-sizing: border-box;
  border: 1px solid #777;
  padding: 2px 6px;
  border-radius: 6px;
  margin-right: 4px;
  outline: none;
  &:focus {
    border-color: #ffdb29;
  }
`;

export default function TodoInput() {
  const nextId = useSelector((state) => state.todo.nextId);
  const dispatch = useDispatch();
  const inputValue = useRef();

  const addTodo = () => {
    dispatch(
      add({
        id: nextId,
        text: inputValue.current.value,
        done: false,
      }),
    );
    inputValue.current.value = '';
  };

  return (
    <MyContainer>
      <MyTitle>TodoList</MyTitle>
      <MyInput
        required
        type="text"
        ref={inputValue}
        placeholder="할일을 입력하세요."
      />
      <MyButton onClick={addTodo}>추가</MyButton>
    </MyContainer>
  );
}
// TodoList

const MyH4 = styled.h4`
  font-size: 1.5rem;
  font-weight: 600;
  margin-bottom: 25px;
  text-align: center;
`;

const MyContainer = styled.div`
  width: 300px;

  border: 1px solid #777;
  border-radius: 9px;
  padding: 25px;
`;

const MyLi = styled.li`
  background-color: #e7f3fe;
  padding: 0 8px;
  height: 32px;
  display: flex;
  position: relative;
  margin-bottom: 10px;

  box-shadow: 1px 1px 1px #aaa;
`;

const MyContent = styled.p`
  line-height: 32px;
`;

const MyDeleteButton = styled.button`
  position: absolute;
  width: 50px;
  height: 25px;

  right: 5px;
  top: 50%;
  transform: translateY(-50%);

  background-color: #7ea9d2;
  border-radius: 6px;
  border: none;
  cursor: pointer;

  font-weight: 700;
`;

const MyDoneButton = styled.button`
  position: absolute;
  width: 50px;
  height: 25px;

  right: 60px;
  top: 50%;
  transform: translateY(-50%);

  background-color: #7ed28d;
  border-radius: 6px;
  border: none;
  cursor: pointer;

  font-weight: 700;
`;

export default function TodoList() {
  const list = useSelector((state) => state.todo.todoList).filter(
    (e) => !e.done,
  );
  const dispatch = useDispatch();

  return (
    <MyContainer>
      <MyH4>🔨Todo</MyH4>
      {list.length === 0 ? (
        <div>할일을 입력해주세요 🥺</div>
      ) : (
        <ul>
          {list.map((e) => (
            <MyLi key={e.id}>
              <MyContent>{'📌 ' + e.text}</MyContent>
              <MyDeleteButton onClick={() => dispatch(remove(e.id))}>
                삭제
              </MyDeleteButton>
              <MyDoneButton onClick={() => dispatch(done(e.id))}>
                완료
              </MyDoneButton>
            </MyLi>
          ))}
        </ul>
      )}
    </MyContainer>
  );
}
// DoneList

const MyH4 = styled.h4`
  font-size: 1.5rem;
  font-weight: 600;
  margin-bottom: 25px;
  text-align: center;
`;

const MyContainer = styled.div`
  width: 300px;

  border: 1px solid #777;
  border-radius: 9px;
  padding: 25px;
`;

const MyLi = styled.li`
  background-color: #e7f3fe;
  padding: 0 8px;
  height: 32px;
  display: flex;
  position: relative;
  margin-bottom: 10px;

  box-shadow: 1px 1px 1px #aaa;
`;

const MyContent = styled.p`
  line-height: 32px;
  text-decoration: line-through;
`;

const MyDeleteButton = styled.button`
  position: absolute;
  width: 50px;
  height: 25px;

  right: 5px;
  top: 50%;
  transform: translateY(-50%);

  background-color: #7ea9d2;
  border-radius: 6px;
  border: none;
  cursor: pointer;

  font-weight: 700;
`;

export default function DoneList() {
  const doneList = useSelector((state) => state.todo.todoList).filter(
    (e) => e.done,
  );
  const dispatch = useDispatch();

  return (
    <MyContainer>
      <MyH4>✨Done</MyH4>
      {doneList.length === 0 ? (
        <div>모두 완료! 🎉</div>
      ) : (
        <ul>
          {doneList.map((e) => (
            <MyLi key={e.id}>
              <MyContent>{e.text}</MyContent>
              <MyDeleteButton onClick={() => dispatch(remove(e.id))}>
                삭제
              </MyDeleteButton>
            </MyLi>
          ))}
        </ul>
      )}
    </MyContainer>
  );
}

'Web Study' 카테고리의 다른 글

useSWR 정리  (0) 2023.04.23
리덕스 정리 - 1  (0) 2023.04.10
리덕스 사용을 위한.. useReducer 개념  (0) 2023.04.07
useContext ?  (0) 2023.04.04
React.memo()에 대해  (0) 2023.04.03