저번주에 만들었던 TodoList를 리덕스를 이용하여 만들어보았다.
폴더랑 파일이 몇 개 없어서 리덕스를 사용하지 않아도 됐지만 리덕스를 공부하기 위해 이번에 숙제로 내주신 것 같다.
물론 많이 어려웠고, 내일이나 주말에 한 번 더 복습해 볼 예정이다.
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux";
import store from "./redux/config/configStore";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
reportWebVitals();
import React from "react";
import Router from "./shared/Router";
const App = () => {
return <Router />;
};
export default App;
react-router-dom
을 사용해서 페이지마다 주소를 주었다."detail/:id"
가 있는데, useParms() 를 사용하기 위함이다.react-router-dom
에 대해서는 공부를 더 해야 할 것 같다. 아직까지 이해를 다 하지 못했다..import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import Detail from "../pages/Detail";
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="detail" element={<Detail />} />
<Route path="detail/:id" element={<Detail />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
import React from "react";
import styled from "styled-components";
import TodoBoard from "../components/TodoBoard";
import TodoHeader from "../components/TodoHeader";
const Wrap = styled.div`
max-width: 1200px;
min-height: 800px;
margin: 0 auto;
`;
export default function Home() {
return (
<Wrap>
<TodoHeader />
<TodoBoard />
</Wrap>
);
}
import React from "react";
import styled from "styled-components";
import DetailsItem from "../components/DetailsItem";
const Wrap = styled.div`
border: 2px solid rgb(238, 238, 238);
width: 100%;
height: 100vh;
display: flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
`;
export default function Detail() {
return (
<Wrap>
<DetailsItem />
</Wrap>
);
}
import { createStore } from "redux";
import { combineReducers } from "redux";
import todos from "../modules/todos";
// store와 module 연결해주기
const rootReducer = combineReducers({
todos,
});
const store = createStore(rootReducer);
export default store;
// Action Value
const ADD_TODO = "ADD_TODO";
const DELETED_TODO = "DELETED_TODO";
const CHANGED_TODO = "CHANGED_TODO";
// Action Creator
// 추가
export const addItem = (payload) => {
return {
type: ADD_TODO,
payload,
};
};
// 삭제
export const deletedItem = (payload) => {
return {
type: DELETED_TODO,
payload,
};
};
// 바꾸기
export const changedItem = (payload) => {
return {
type: CHANGED_TODO,
payload,
};
};
// Initial State
const initialState = [
{ id: "1", title: "리액트 복습하기", text: "열심히 하자", isDone: false },
{ id: "2", title: "TodoList 복습하기", text: "가즈아", isDone: true },
];
// Reducer
const todos = (state = initialState, action) => {
switch (action.type) {
case ADD_TODO:
return [...state, action.payload];
case DELETED_TODO:
return state.filter((item) => item.id !== action.payload);
case CHANGED_TODO:
return state.map((item) => {
if (item.id === action.payload)
return { ...item, isDone: !item.isDone };
else return item;
});
default:
return state;
}
};
export default todos;
import React from "react";
import styled from "styled-components";
const Header = styled.div`
padding: 0 10px;
display: flex;
justify-content: space-between;
border: 1px solid black;
`;
const HeaderTitle = styled.h3`
font-weight: 800;
`;
const TodoHeader = () => {
return (
<Header>
<HeaderTitle>My Todo List</HeaderTitle>
<HeaderTitle>React</HeaderTitle>
</Header>
);
};
export default TodoHeader;
import TodoInput from "./TodoInput";
import TodoList from "./TodoList";
export default function TodoBoard() {
return (
<div>
<TodoInput />
<TodoList isWorking={true} />
<TodoList isWorking={false} />
</div>
);
}
dispatch(addItem(newTodos))
: newTodos 는 payload 의 값으로 전송된다.import React, { useState } from "react";
import styled from "styled-components";
import { v4 as uuidv4 } from "uuid";
import { useDispatch } from "react-redux";
import { addItem } from "../redux/modules/todos";
const Form = styled.form`
display: flex;
background-color: #eee;
border-radius: 12px;
justify-content: space-between;
margin: 0 auto;
padding: 30px;
`;
const FormInputBox = styled.div`
align-items: center;
display: flex;
gap: 20px;
`;
const FormLabel = styled.label`
font-size: 16px;
font-weight: 700;
`;
const FormInput = styled.input.attrs({ type: "text" })`
border: none;
border-radius: 12px;
height: 40px;
padding: 0 12px;
width: 240px;
`;
const FormButton = styled.button`
background-color: teal;
border: none;
border-radius: 10px;
color: #fff;
font-weight: 700;
height: 40px;
width: 140px;
cursor: pointer;
`;
export default function TodoInput() {
const [titleValue, setTitleValue] = useState("");
const [textValue, setTextValue] = useState("");
const dispatch = useDispatch();
// 제목 입력값을 변경해주는 이벤트
const onChangeTitleHandler = (event) => {
setTitleValue(event.target.value);
};
// 내용 입력값을 변경해주는 이벤트
const onChangeTextHandler = (event) => {
setTextValue(event.target.value);
};
// 추가하기 이벤트
const onSubmitHandler = (event) => {
event.preventDefault();
const newTodos = {
id: uuidv4(),
title: titleValue,
text: textValue,
isDone: false,
};
if (!titleValue || !textValue)
return alert("제목이나 내용을 입력해주세요.");
dispatch(addItem(newTodos));
setTitleValue("");
setTextValue("");
};
return (
<Form onSubmit={onSubmitHandler}>
<FormInputBox>
<FormLabel htmlFor="title">제목</FormLabel>
<FormInput
id="title"
value={titleValue}
onChange={onChangeTitleHandler}
/>
<FormLabel htmlFor="text">내용</FormLabel>
<FormInput id="text" value={textValue} onChange={onChangeTextHandler} />
</FormInputBox>
<FormButton>추가하기</FormButton>
</Form>
);
}
import React from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import TodoItem from "./TodoItem";
const TodoListBox = styled.div`
display: flex;
flex-wrap: wrap;
gap: 12px;
`;
export default function TodoList({ isWorking }) {
const todos = useSelector((state) => state.todos);
return (
<div>
<h1>{isWorking ? "Working" : "isDone"}</h1>
<TodoListBox>
{todos
.filter((todo) => (todo.isDone ? !isWorking : isWorking))
.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</TodoListBox>
</div>
);
}
import React from "react";
import styled from "styled-components";
import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import { deletedItem, changedItem } from "../redux/modules/todos";
const TodoItemBox = styled.div`
border-radius: 12px;
padding: 12px 24px 24px;
border: 4px solid lightblue;
width: 270px;
`;
const BtnBox = styled.div`
display: flex;
gap: 10px;
margin-top: 24px;
`;
const TodoBtn = styled.button`
cursor: pointer;
height: 40px;
width: 50%;
border-radius: 12px;
background-color: #fff;
border: 2px solid ${(props) => props.borderColor};
`;
export default function TodoItem({ todo }) {
const dispatch = useDispatch();
// 삭제 기능
const onClickDeleteHandler = () => {
dispatch(deletedItem(todo.id));
};
// isDone 바꾸기 기능
const onClickChangeHandler = () => {
dispatch(changedItem(todo.id));
};
return (
<TodoItemBox>
<Link to={`/detail/${todo.id}`}>상세정보</Link>
<h1>{todo.title}</h1>
<p>{todo.text}</p>
<BtnBox>
<TodoBtn borderColor="red" onClick={onClickDeleteHandler}>
삭제
</TodoBtn>
<TodoBtn borderColor="green" onClick={onClickChangeHandler}>
{todo.isDone ? "취소" : "확인"}
</TodoBtn>
</BtnBox>
</TodoItemBox>
);
}
import React from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
const Wrap = styled.div`
width: 600px;
height: 400px;
border: 1px solid rgb(238, 238, 238);
display: flex;
flex-direction: column;
-webkit-box-pack: justify;
`;
const HeaderWrap = styled.div`
display: flex;
height: 80px;
-webkit-box-pack: justify;
justify-content: space-between;
padding: 0px 24px;
-webkit-box-align: center;
align-items: center;
`;
const BackBtn = styled.button`
border: 1px solid rgb(221, 221, 221);
height: 40px;
width: 120px;
background-color: rgb(255, 255, 255);
border-radius: 12px;
cursor: pointer;
`;
const BodyWrap = styled.div`
padding: 24px;
`;
export default function DetailsItem() {
const items = useSelector((state) => state.todos);
const param = useParams();
const item = items.find((item) => item.id === param.id);
return (
<Wrap>
<HeaderWrap>
<h3>ID : {item.id.slice(0, 5)}</h3>
<Link to="/">
<BackBtn>이전으로</BackBtn>
</Link>
</HeaderWrap>
<BodyWrap>
<h1>{item.title}</h1>
<h2>{item.text}</h2>
</BodyWrap>
</Wrap>
);
}
Error
리덕스를 어떻게 사용해야 하는지 ?