일단 삭제를 했지만, 다른 액션에 문제가 발생한 상황...
그래서 좀 더 잘하는 친구에게 이 에러를 맡기기로 했고...
나는 일단 redux에 대해서 자료를 올려놓고, 저 에러가 해결이 된다면, 그때 다시 수정해서 올리겠다.
{filteredTodos.map((todo) => (
<TodoItem
key={todo.id}
completed={todo.completed}
onClick={() => handleTodoClick(todo)}
>
<span className='todotext'>{todo.text}</span>
<span>{todo.completed ? "✅" : "❌"}</span>
<span onClick={() => handleDeleteClick(todo.id)}>🗑️</span>
</TodoItem>
))}
{filteredTodos.map((todo) => (
<TodoItem
key={todo.id}
completed={todo.completed}
onClick={() => handleTodoClick(todo)}
>
<span className='todotext'>{todo.text}</span>
<span>{todo.completed ? "✅" : "❌"}</span>
<span onClick={(e) => { e.stopPropagation(); handleDeleteClick(todo.id)}}>🗑️</span>
</TodoItem>
))}
현재 이벤트 버블링이 걸려있었던 부분이라고 한다. 그래서
(e) => { e.stopPropagation();
} 를 걸어야 이벤트 버블링에서 빠져나올 수 있다고 함.
난 항상 로직도 문제지만, redux를 어떻게 작성순서를 생각해야할까에 대한 고민을 많이했다. redux-saga를 하기 앞서 아직도 고민 중이다. 그래서 글을 올릴꺼다 (... 일단 밥부터 먹고 해볼가 ㅎ )
//index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
제일 root에 위치한 컴포넌트라고 생각한다.
그래서 세팅의 범위라고 생각하자.
//App.js
import React from 'react';
import styled from '@emotion/styled';
import { Provider } from 'react-redux';
import TodoList from "./TodoList";
import store from './store';
const AppWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
`
const AppContent = styled.div`
display: flex;
flex-direction: column;
align-items: center;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
padding: 20px;
`
const AppTitle = styled.h1`
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
`
function App() {
return (
<Provider store={store}>
<AppWrapper>
<AppContent>
<AppTitle>Todo List</AppTitle>
<TodoList />
</AppContent>
</AppWrapper>
</Provider>
);
}
해당 컴포넌트는 사실 BrowserRouter, routes, route를 위치시키는 곳이다. 근데 일단 URL을 나눌 필요가 없어서 여기에서 Provider 를 사용했다. ( 사실 여기서 계속 써도 되는지는 나도 잘모름 )
//store.js
import { configureStore } from "@reduxjs/toolkit";
import { useDispatch } from "react-redux";
// 일단 이 파일 없음
import rootReducer from './rootReducer';
const store = configureStore({
reducer: rootReducer,
});
export const useAppDispatch = () => useDispatch();
export default store;
Redux의 상태를 저장하고 상태가 변경될 때마다 구독된 모든 컴포넌트에 상태 변경 사항을 전달. 그리고 store는 Provider를 통해서 App으로 전달됨
//rootReducer.js
import { combineReducers } from "redux";
import todosReducer from './todosSlice';
import visibilityFilterReducer from './visibilityFilterSlice';
const rootReducer = combineReducers({
todos: todosReducer,
visibilityFilter: visibilityFilterReducer,
})
export default rootReducer;
나는 rootReducer 파일을 만드는것도 일종의 세팅이라고 생각한다. 그래서 나는 미리 만들어 놓을 예정이다.
// TodoList-components
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
// 이 코드도 문제가 있어보임. => 알고보니 @emotion/react로 설치하고 바꿔야 함
// import { css } from "@emotion/core";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { fetchTodos } from "./todosSlice";
const TodoListWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
`;
const TodoItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 300px;
padding: 10px;
background-color: #f2f2f2;
border-radius: 5px;
margin-bottom: 10px;
cursor: pointer;
.todotext {
/* 여기 뭔가 에러 같음 */
${({ completed }) =>
completed &&
css`
text-decoration: line-through;
`}
}
`;
const TodoList = () => {
const dispatch = useDispatch();
const todos = useSelector((state) => state.todos.items);
useEffect(() => {
dispatch(fetchTodos())
}, [dispatch])
return (
<TodoListWrapper>
{todos.map((todo) => (
<TodoItem
key={todo.id}
completed={todo.completed}
onClick={() => handleTodoClick(todo)}
>
<span className='todotext'>{todo.text}</span>
</TodoItem>
))}
</TodoListWrapper>
)
}
export default TodoList;
- 컴포넌트에서 먼저 작성하는 이유는 일단 어느정도의 틀이 보여야 기능을 구현할 수 있다고 생각해서 그렇다.
- 물론 map을 이용해서 list를 보기 위해서는 slice(action)과 reducer가 필요하다.
- 그래서 이 역시도, 다른 자료들을 참고하거나, 마치 이미 기능이 구현되어있다는 것을 가정하면서 코드를 짜야한다.
//todoSlice.js
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios'
const initialState = {
items: [],
isLoading: false,
error: null,
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
fetchTodosStart: (state) => {
state.isLoading = true;
state.error = null;
},
fetchTodosSuccess: (state, action) => {
state.isLoading = false;
state.items = action.payload;
},
fetchTodosFailure: (state, action) => {
state.isLoading = false;
state.error = action.payload;
},
},
})
export const fetchTodos = () => async (dispatch) => {
// 원래는 reducers가 actions로 되어있었음 일단 수정해본거
dispatch(todosSlice.actions.fetchTodosStart());
try {
const response = await axios({
method: 'GET',
url: 'http://localhost:3001/todos'
})
dispatch(todosSlice.actions.fetchTodosSuccess(response.data));
} catch (error) {
dispatch(todosSlice.actions.fetchTodosFailure(error.message));
}
}
// 왜 reducer라고 하는지는 모르겠음
export default todosSlice.reducer;
- redux-toolkit의 createSlice 라이브러리를 통해서 액션과 액션타입을 굳이 작성하지 않고도, redux를 쓸수 있게 하는 것이다.
- 일단 내 생각에는 reducer부터 작성하는 게 맞다고 생각한다. 그래서 fetchTodos를 작성한다.
- try-catch를 통해서 api 호출 및 에러를 구현하고, 그 다음 액션을 호출하는 것을 구현한다. (dispatch)
- dispatch 인자는 slice에서 가져온것이고, 보통은 'slice.actions.reducer` 형태라고 생각하면 된다.
- fetch는 Start, Success, Failure
- slice 작성 시에, state는 initialState를 생각하면서 작성한다
- 로직은 해당 코드를 참고한다.
//addTodo-component
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
// 이 코드도 문제가 있어보임. => 알고보니 @emotion/react로 설치하고 바꿔야 함
// import { css } from "@emotion/core";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { fetchTodos, addTodo } from "./todosSlice";
const TodoListWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
`;
const TodoForm = styled.form`
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
`;
const TodoInput = styled.input`
padding: 10px;
margin-right: 10px;
border-radius: 5px;
border: none;
outline: none;
`;
const TodoButton = styled.button`
padding: 10px;
background-color: #4caf50;
border: none;
border-radius: 5px;
color: #fff;
cursor: pointer;
`;
const TodoItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 300px;
padding: 10px;
background-color: #f2f2f2;
border-radius: 5px;
margin-bottom: 10px;
cursor: pointer;
.todotext {
/* 여기 뭔가 에러 같음 */
${({ completed }) =>
completed &&
css`
text-decoration: line-through;
`}
}
`;
const TodoList = () => {
const dispatch = useDispatch();
const todos = useSelector((state) => state.todos.items);
useEffect(() => {
dispatch(fetchTodos())
}, [dispatch])
const [todoText, setTodoText] = useState("");
const handleTodoSubmit = (e) => {
e.preventDefault();
if (todoText.trim() !== "") {
dispatch(addTodo(todoText.trim()));
setTodoText("");
}
}
return (
<TodoListWrapper>
<TodoForm onSubmit={handleTodoSubmit}>
<TodoInput
type="text"
placeholder='새로운 할일을 추가해줘'
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
<TodoButton type='submit'>추가</TodoButton>
</TodoForm>
{todos.map((todo) => (
<TodoItem
key={todo.id}
completed={todo.completed}
>
<span className='todotext'>{todo.text}</span>
</TodoItem>
))}
</TodoListWrapper>
)
}
export default TodoList;
- 컴포넌트에서 먼저 작성하는 이유는 일단 어느정도의 틀이 보여야 기능을 구현할 수 있다고 생각해서 그렇다.
- 물론 map을 이용해서 list를 보기 위해서는 slice(action)과 reducer가 필요하다.
- 그래서 이 역시도, 다른 자료들을 참고하거나, 마치 이미 기능이 구현되어있다는 것을 가정하면서 코드를 짜야한다.
- 항상 어떤 데이터를 post를 통해서 추가해야할 때는, form 태그를 써야한다고 생각하는게 좋다.
- 로직은 위의 코드를 참고하자
//addTodo-slice,reducer
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios'
const initialState = {
items: [],
isLoading: false,
error: null,
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
fetchTodosStart: (state) => {
state.isLoading = true;
state.error = null;
},
fetchTodosSuccess: (state, action) => {
state.isLoading = false;
state.items = action.payload;
},
fetchTodosFailure: (state, action) => {
state.isLoading = false;
state.error = action.payload;
},
addTodoSuccess: (state, action) => {
state.items.push(action.payload);
},
},
})
export const fetchTodos = () => async (dispatch) => {
// 원래는 reducers가 actions로 되어있었음 일단 수정해본거
dispatch(todosSlice.actions.fetchTodosStart());
try {
const response = await axios({
method: 'GET',
url: 'http://localhost:3001/todos'
})
dispatch(todosSlice.actions.fetchTodosSuccess(response.data));
} catch (error) {
dispatch(todosSlice.actions.fetchTodosFailure(error.message));
}
}
export const addTodo = (text) => async (dispatch) => {
try {
const response = await axios({
method: 'POST',
url: 'http://localhost:3001/todos',
data: {
text,
completed: false,
}
});
dispatch(todosSlice.actions.addTodoSuccess(response.data));
} catch (error) {
console.log(error.message)
}
};
// 왜 reducer라고 하는지는 모르겠음 (그냥 약속인듯)
export default todosSlice.reducer;
- redux-toolkit의 createSlice 라이브러리를 통해서 액션과 액션타입을 굳이 작성하지 않고도, redux를 쓸수 있게 하는 것이다.
- 일단 내 생각에는 reducer부터 작성하는 게 맞다고 생각한다. 그래서 addTodos를 작성한다.
- try-catch를 통해서 api 호출 및 에러를 구현하고, 그 다음 액션을 호출하는 것을 구현한다. (dispatch)
- dispatch 인자는 slice에서 가져온것이고, 보통은 'slice.actions.reducer` 형태라고 생각하면 된다.
- add는 Success
- slice 작성 시에, state는 initialState를 생각하면서 작성한다
- 로직은 해당 코드를 참고한다.
//toggleTodo-component
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
// 이 코드도 문제가 있어보임. => 알고보니 @emotion/react로 설치하고 바꿔야 함
// import { css } from "@emotion/core";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { fetchTodos, addTodo, toggleTodo} from "./todosSlice";
import FilterLink from './FilterLink';
const TodoListWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
`;
const TodoForm = styled.form`
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
`;
const TodoInput = styled.input`
padding: 10px;
margin-right: 10px;
border-radius: 5px;
border: none;
outline: none;
`;
const TodoButton = styled.button`
padding: 10px;
background-color: #4caf50;
border: none;
border-radius: 5px;
color: #fff;
cursor: pointer;
`;
const TodoItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 300px;
padding: 10px;
background-color: #f2f2f2;
border-radius: 5px;
margin-bottom: 10px;
cursor: pointer;
.todotext {
/* 여기 뭔가 에러 같음 */
${({ completed }) =>
completed &&
css`
text-decoration: line-through;
`}
}
`;
const TodoList = () => {
const dispatch = useDispatch();
const todos = useSelector((state) => state.todos.items);
useEffect(() => {
dispatch(fetchTodos())
}, [dispatch])
const [todoText, setTodoText] = useState("");
const handleTodoSubmit = (e) => {
e.preventDefault();
if (todoText.trim() !== "") {
dispatch(addTodo(todoText.trim()));
setTodoText("");
}
}
const handleTodoClick = (todo) => {
dispatch(toggleTodo(todo));
};
return (
<TodoListWrapper>
<TodoForm onSubmit={handleTodoSubmit}>
<TodoInput
type="text"
placeholder='새로운 할일을 추가해줘'
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
<TodoButton type='submit'>추가</TodoButton>
</TodoForm>
{todos.map((todo) => (
<TodoItem
key={todo.id}
completed={todo.completed}
onClick={() => handleTodoClick(todo)}
>
<span className='todotext'>{todo.text}</span>
<span>{todo.completed ? "✅" : "❌"}</span>
</TodoItem>
))}
</TodoListWrapper>
)
}
export default TodoList;
- 일단 todo.completed가 true인지, false인지에 따라서 아이콘이 바뀐다. 그리고 추가적으로 todo.text에 취소선이 생긴다(true).
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios'
const initialState = {
items: [],
isLoading: false,
error: null,
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
fetchTodosStart: (state) => {
state.isLoading = true;
state.error = null;
},
fetchTodosSuccess: (state, action) => {
state.isLoading = false;
state.items = action.payload;
},
fetchTodosFailure: (state, action) => {
state.isLoading = false;
state.error = action.payload;
},
addTodoSuccess: (state, action) => {
state.items.push(action.payload);
},
toggleTodoSuccess: (state, action) => {
const todo = state.items.find((todo) => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
},
})
export const fetchTodos = () => async (dispatch) => {
// 원래는 reducers가 actions로 되어있었음 일단 수정해본거
dispatch(todosSlice.actions.fetchTodosStart());
try {
const response = await axios({
method: 'GET',
url: 'http://localhost:3001/todos'
})
dispatch(todosSlice.actions.fetchTodosSuccess(response.data));
} catch (error) {
dispatch(todosSlice.actions.fetchTodosFailure(error.message));
}
}
export const addTodo = (text) => async (dispatch) => {
try {
const response = await axios({
method: 'POST',
url: 'http://localhost:3001/todos',
data: {
text,
completed: false,
}
});
dispatch(todosSlice.actions.addTodoSuccess(response.data));
} catch (error) {
console.log(error.message)
}
};
export const toggleTodo = (todo) => async (dispatch) => {
try {
await axios({
method: 'PUT',
url: `http://localhost:3001/todos/${todo.id}`,
data: {
completed: !todo.completed,
},
});
dispatch(todosSlice.actions.toggleTodoSuccess(todo.id));
} catch (error) {
console.log(error.message);
}
};
// 왜 reducer라고 하는지는 모르겠음
export default todosSlice.reducer;
사실 삭제하면 해당 toggleTodo에서 에러가 발생하는데, 이걸 해결해야할 것 같다. 친구한테 맡겨놨으니... 해결하면 해당글의 일부를 수정할것 같다.
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
// 이 코드도 문제가 있어보임. => 알고보니 @emotion/react로 설치하고 바꿔야 함
// import { css } from "@emotion/core";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { fetchTodos, addTodo, toggleTodo, deleteTodo} from "./todosSlice";
const TodoListWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
`;
const TodoForm = styled.form`
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
`;
const TodoInput = styled.input`
padding: 10px;
margin-right: 10px;
border-radius: 5px;
border: none;
outline: none;
`;
const TodoButton = styled.button`
padding: 10px;
background-color: #4caf50;
border: none;
border-radius: 5px;
color: #fff;
cursor: pointer;
`;
const TodoItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 300px;
padding: 10px;
background-color: #f2f2f2;
border-radius: 5px;
margin-bottom: 10px;
cursor: pointer;
.todotext {
/* 여기 뭔가 에러 같음 */
${({ completed }) =>
completed &&
css`
text-decoration: line-through;
`}
}
`;
const TodoList = () => {
const dispatch = useDispatch();
const todos = useSelector((state) => state.todos.items);
useEffect(() => {
dispatch(fetchTodos())
}, [dispatch])
const [todoText, setTodoText] = useState("");
const handleTodoSubmit = (e) => {
e.preventDefault();
if (todoText.trim() !== "") {
dispatch(addTodo(todoText.trim()));
setTodoText("");
}
}
const handleTodoClick = (todo) => {
dispatch(toggleTodo(todo));
};
const handleDeleteClick = (id) => {
dispatch(deleteTodo(id));
};
return (
<TodoListWrapper>
<TodoForm onSubmit={handleTodoSubmit}>
<TodoInput
type="text"
placeholder='새로운 할일을 추가해줘'
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
<TodoButton type='submit'>추가</TodoButton>
</TodoForm>
{filteredTodos.map((todo) => (
<TodoItem
key={todo.id}
completed={todo.completed}
onClick={() => handleTodoClick(todo)}
>
<span className='todotext'>{todo.text}</span>
<span>{todo.completed ? "✅" : "❌"}</span>
<span onClick={() => handleDeleteClick(todo.id)}>🗑️</span>
</TodoItem>
))}
</TodoListWrapper>
)
}
export default TodoList;
- 휴지통을 클릭하면 해당 id를 가진 todo가 지워진다. 어찌 생각해보면 reducer랑 액션은 크게 다르지 않은것 같다.
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios'
const initialState = {
items: [],
isLoading: false,
error: null,
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
fetchTodosStart: (state) => {
state.isLoading = true;
state.error = null;
},
fetchTodosSuccess: (state, action) => {
state.isLoading = false;
state.items = action.payload;
},
fetchTodosFailure: (state, action) => {
state.isLoading = false;
state.error = action.payload;
},
addTodoSuccess: (state, action) => {
state.items.push(action.payload);
},
toggleTodoSuccess: (state, action) => {
const todo = state.items.find((todo) => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
deleteTodoSuccess: (state, action) => {
state.items = state.items.filter((todo) => todo.id !== action.payload);
},
},
})
export const fetchTodos = () => async (dispatch) => {
// 원래는 reducers가 actions로 되어있었음 일단 수정해본거
dispatch(todosSlice.actions.fetchTodosStart());
try {
const response = await axios({
method: 'GET',
url: 'http://localhost:3001/todos'
})
dispatch(todosSlice.actions.fetchTodosSuccess(response.data));
} catch (error) {
dispatch(todosSlice.actions.fetchTodosFailure(error.message));
}
}
export const addTodo = (text) => async (dispatch) => {
try {
const response = await axios({
method: 'POST',
url: 'http://localhost:3001/todos',
data: {
text,
completed: false,
}
});
dispatch(todosSlice.actions.addTodoSuccess(response.data));
} catch (error) {
console.log(error.message)
}
};
export const toggleTodo = (todo) => async (dispatch) => {
try {
await axios({
method: 'PUT',
url: `http://localhost:3001/todos/${todo.id}`,
data: {
completed: !todo.completed,
},
});
dispatch(todosSlice.actions.toggleTodoSuccess(todo.id));
} catch (error) {
console.log(error.message);
}
};
export const deleteTodo = (id) => async (dispatch) => {
try {
await axios({
method: 'DELETE',
url: `http://localhost:3001/todos/${id}`
});
dispatch(todosSlice.actions.deleteTodoSuccess(id))
} catch (error) {
console.log(error.message)
}
};
// 왜 reducer라고 하는지는 모르겠음
export default todosSlice.reducer;
toggle의 원리는 특정 id만을 찾아야하고, delete는 특정 id 빼고 다 화면에 보여주면 된다. 그 로직을 기억하자. ( 몰라도 다시 와서 보면됨 )
import React from 'react';
import styled from "@emotion/styled";
import { useSelector, useDispatch } from "react-redux";
// 이 파일 없음 => 다시 만듬
import { setVisibilityFilter } from './visibilityFilterSlice';
const Link = styled.span`
padding: 5px;
cursor: pointer;
/* 이것도 에러같음 */
text-decoration: ${({ active }) => (active ? "underline" : "none")};
`
const FilterLink = ({filter, children}) => {
const dispatch = useDispatch();
const activeFilter = useSelector((state) => state.visibilityFilter);
const isActive = activeFilter === filter;
return (
<Link
active={isActive}
onClick={() => dispatch(setVisibilityFilter(filter))}
>
{children}
</Link>
);
};
export default FilterLink;
- 사실 여기서는 children 인자가 필요없었는데, 일단은 나중에 쓸일이 있지않을까 해서 넣어놨다.
- 일단 실행할 필터를 가지고와서 실행할 필터와 내가 클릭한 필터가 동일하다면, 해당 링크로 가서 화면을 보여준다. (필터를 거는 방식)
import { createSlice } from "@reduxjs/toolkit";
const visibilityFilterSlice = createSlice({
name: "visibilityFilter",
initialState: "SHOW_ALL",
reducers: {
setVisibilityFilter: (state, action) => action.payload,
}
})
export const { setVisibilityFilter } = visibilityFilterSlice.actions;
export default visibilityFilterSlice.reducer;
- 여기도 slice와 reducer가 있는데, slice에서는 initialState가 약간 액션 타입 지정하는 느낌으로 걸려있다. 왜냐하면 todoList 메인 화면에서 case 별로 필터를 거는 것이 다르기 때문이다. 일단은 모든 todolist를 보여주는 'SHOW_ALL'로 시작한다.
- api 요청을 제외하고, reducers 속성에는 setVisibilityFilter라는 reducer 함수가 정의되어 있습니다. 이 함수는 인자로 받은 action.payload 값으로 state를 업데이트합니다. setVisibilityFilter 액션은 visibility filter를 설정할 때 사용됩니다.
- 마지막으로, visibilityFilterSlice.actions 객체에서는 setVisibilityFilter 액션을 가져올 수 있습니다. 사실 로직을 이해하기는 어려워서 만약에 필터를 적용하는 화면을 구현한다면, 해당 포스팅으로 자주 올듯 하다.
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
// 이 코드도 문제가 있어보임. => 알고보니 @emotion/react로 설치하고 바꿔야 함
// import { css } from "@emotion/core";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { fetchTodos, addTodo, toggleTodo, deleteTodo} from "./todosSlice";
import FilterLink from './FilterLink';
const TodoListWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
`;
const TodoForm = styled.form`
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
`;
const TodoInput = styled.input`
padding: 10px;
margin-right: 10px;
border-radius: 5px;
border: none;
outline: none;
`;
const TodoButton = styled.button`
padding: 10px;
background-color: #4caf50;
border: none;
border-radius: 5px;
color: #fff;
cursor: pointer;
`;
const TodoItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
width: 300px;
padding: 10px;
background-color: #f2f2f2;
border-radius: 5px;
margin-bottom: 10px;
cursor: pointer;
.todotext {
/* 여기 뭔가 에러 같음 */
${({ completed }) =>
completed &&
css`
text-decoration: line-through;
`}
}
`;
const FilterWrapper = styled.div`
margin-top: 10px;
`;
const TodoList = () => {
const dispatch = useDispatch();
const todos = useSelector((state) => state.todos.items);
const visibilityFilter = useSelector((state) => state.visibilityFilter);
useEffect(() => {
dispatch(fetchTodos())
}, [dispatch])
const [todoText, setTodoText] = useState("");
const handleTodoSubmit = (e) => {
e.preventDefault();
if (todoText.trim() !== "") {
dispatch(addTodo(todoText.trim()));
setTodoText("");
}
}
const handleTodoClick = (todo) => {
dispatch(toggleTodo(todo));
};
const handleDeleteClick = (id) => {
dispatch(deleteTodo(id));
};
const filteredTodos = todos.filter((todo) => {
switch (visibilityFilter) {
case "SHOW_ALL":
return true;
case "SHOW_COMPLETED":
return todo.completed;
case "SHOW_ACTIVE":
return !todo.completed;
default:
return true;
}
});
return (
<TodoListWrapper>
<TodoForm onSubmit={handleTodoSubmit}>
<TodoInput
type="text"
placeholder='새로운 할일을 추가해줘'
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
<TodoButton type='submit'>추가</TodoButton>
</TodoForm>
{filteredTodos.map((todo) => (
<TodoItem
key={todo.id}
completed={todo.completed}
onClick={() => handleTodoClick(todo)}
>
<span className='todotext'>{todo.text}</span>
<span>{todo.completed ? "✅" : "❌"}</span>
<span onClick={() => handleDeleteClick(todo.id)}>🗑️</span>
</TodoItem>
))}
<FilterWrapper>
<FilterLink filter="SHOW_ALL">모든 리스트</FilterLink>
<FilterLink filter="SHOW_ACTIVE">진행중인 리스트</FilterLink>
<FilterLink filter="SHOW_COMPLETED">완료된 리스트</FilterLink>
</FilterWrapper>
</TodoListWrapper>
)
}
export default TodoList;
- todos를 filteredTodos로 바꾸었다. name으로만 가지고오면, state 자체를 가지고 오는 것이다. 참고하여 case를 구분해준다.
- FilterLink 컴포넌트는 버튼이라고 생각하고 filter를 지정해주는 식으로 filter의 타입을 지정해준다. 그래야 관련 액션이 호출되면서, 필터가 걸린다.
역시 나는 이해를 못했기 때문에 다시 찾아와야지 헤헿