diff --git a/src/app/App.test.tsx b/src/app/App.test.tsx
index 4dedfe5..4180cf9 100644
--- a/src/app/App.test.tsx
+++ b/src/app/App.test.tsx
@@ -3,7 +3,7 @@ import { createStore } from "redux";
import { Provider } from "react-redux";
import App from './App';
-import {todoReducer} from "../state/todo/reducers";
+import todoReducer from "../features/todo/todosSlice";
test('renders learn react link', () => {
const store = createStore(todoReducer);
diff --git a/src/app/App.tsx b/src/app/App.tsx
index f8ccbe6..97efa11 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -2,23 +2,17 @@ import React, {useState} from 'react';
import { useSelector, useDispatch } from "react-redux";
import Todo from "../components/todo/Todo";
-import { TodoItem } from "../state/todo/types";
-import { ADD_TODO } from "../state/todo/reducers";
+import { addTodo } from "../features/todo/todosSlice";
+import { RootState } from "./store";
function App() {
const [text, setText] = useState("");
- const todos = useSelector((state: { todos: TodoItem[] }) => state.todos);
+ const todos = useSelector((state: RootState) => state.todos.todos);
const dispatch = useDispatch();
const handleAddTodo = () => {
if (text.trim() === "") return;
- const newTodo: TodoItem = {
- id: todos.length + 1,
- text: text,
- completed: false,
- };
-
- dispatch({ type: ADD_TODO, payload: newTodo});
+ dispatch(addTodo(text));
setText("");
}
@@ -33,12 +27,7 @@ function App() {
{todos.map((todo) => (
-
+
))}
diff --git a/src/app/store.ts b/src/app/store.ts
new file mode 100644
index 0000000..baea1e6
--- /dev/null
+++ b/src/app/store.ts
@@ -0,0 +1,11 @@
+import { configureStore } from "@reduxjs/toolkit";
+
+import todosReducer from '../features/todo/todosSlice';
+
+export const store = configureStore({
+ reducer: {
+ todos: todosReducer,
+ },
+});
+
+export type RootState = ReturnType;
diff --git a/src/components/todo/Todo.test.tsx b/src/components/todo/Todo.test.tsx
index 11d5064..3a7fa8a 100644
--- a/src/components/todo/Todo.test.tsx
+++ b/src/components/todo/Todo.test.tsx
@@ -1,53 +1,56 @@
import React from "react";
-import {screen, render, fireEvent, getByRole} from "@testing-library/react"
-import { createStore } from "redux";
+import {render, fireEvent} from "@testing-library/react"
import { Provider } from "react-redux";
import Todo from "./Todo";
-import {todoReducer} from "../../state/todo/reducers";
-import {store} from "../../state/store";
+import todoReducer from '../../features/todo/todosSlice'
+import {configureStore} from "@reduxjs/toolkit";
-describe("Todo", () => {
-
- const props = {
- id: 1,
- text: "Buy milk",
- completed: false,
+const renderWithRedux = (
+ ui: React.ReactElement,
+ { store = configureStore({ reducer: { todos: todoReducer } }) } = {}
+) => {
+ return {
+ ...render({ui}),
+ store,
};
-
- it("should render todo text", () => {
- render(
-
-
-
- )
- expect(screen.getByText(props.text)).toBeInTheDocument();
+};
+describe("Todo", () => {
+ it("renders todo text and toggle button", () => {
+ const todo = {
+ id: 1,
+ text: "Test todo",
+ completed: false,
+ };
+ const { getByText, getByRole } = renderWithRedux();
+ const todoText = getByText("Test todo");
+ expect(todoText).toBeInTheDocument();
+ const toggleButton = getByRole("checkbox");
+ expect(toggleButton).toBeInTheDocument();
});
- it("should call onToggle when checkbox is clicked", () => {
- render(
-
-
-
- )
- fireEvent.click(screen.getByRole("checkbox"));
+ it("toggles todo when toggle button is clicked", () => {
+ const todo = {
+ id: 1,
+ text: "Test todo",
+ completed: false,
+ };
+ const { getByRole, store } = renderWithRedux();
+ const toggleButton = getByRole("checkbox");
+ fireEvent.click(toggleButton);
+ expect(store.getState().todos.todos[0].completed).toBe(true);
});
- it("should call onDelete when delete button is clicked", () => {
- render(
-
-
-
- )
- fireEvent.click(screen.getByRole("button"));
- })
+ it("deletes todo when delete button is clicked", () => {
+ const todo = {
+ id: 1,
+ text: "Test todo",
+ completed: false,
+ };
+ const { getByRole, store } = renderWithRedux();
+ const deleteButton = getByRole("button");
+ fireEvent.click(deleteButton);
+ expect(store.getState().todos.todos).toHaveLength(0);
+ });
- it("should have line-through style when completed is true", () => {
- render(
-
-
-
- )
- expect(screen.getByText(props.text)).toHaveStyle("text-decoration: line-through");
- })
})
diff --git a/src/components/todo/Todo.tsx b/src/components/todo/Todo.tsx
index 7bbc99c..cf91134 100644
--- a/src/components/todo/Todo.tsx
+++ b/src/components/todo/Todo.tsx
@@ -1,28 +1,26 @@
import React from "react";
import {useDispatch} from "react-redux";
-import { DELETE_TODO, TOGGLE_TODO } from "../../state/todo/reducers";
+import { toggleTodo, deleteTodo, TodoItem } from "../../features/todo/todosSlice";
interface TodoProps {
- id: number;
- text: string;
- completed: boolean;
+ todo: TodoItem;
}
-const Todo = ( { id, text, completed }: TodoProps) => {
+const Todo = ( { todo }: TodoProps) => {
const dispatch = useDispatch();
const handleToggle = () => {
- dispatch({ type: TOGGLE_TODO, payload: id });
+ dispatch(toggleTodo(todo.id));
};
const handleDelete = () => {
- dispatch({ type: DELETE_TODO, payload: id });
+ dispatch(deleteTodo(todo.id));
};
return (
-
-
- {text}
+
+
+ {todo.text}
diff --git a/src/features/todo/todosSlice.ts b/src/features/todo/todosSlice.ts
new file mode 100644
index 0000000..2d8e05b
--- /dev/null
+++ b/src/features/todo/todosSlice.ts
@@ -0,0 +1,46 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+export interface TodoItem {
+ id: number;
+ text: string;
+ completed: boolean;
+}
+
+interface TodoState {
+ todos: TodoItem[];
+}
+
+const initialState: TodoState = {
+ todos: []
+}
+
+export const todosSlice = createSlice({
+ name: 'todo',
+ initialState,
+ reducers: {
+ addTodo: {
+ reducer: (state, action: PayloadAction) => {
+ state.todos.push(action.payload);
+ },
+ prepare: (text: string) => ({
+ payload: {
+ id: new Date().getTime(),
+ text,
+ completed: false,
+ },
+ }),
+ },
+ toggleTodo: (state, action: PayloadAction) => {
+ const todo = state.todos.find((todo) => todo.id === action.payload);
+ if (todo) {
+ todo.completed = !todo.completed;
+ }
+ },
+ deleteTodo: (state, action: PayloadAction) => {
+ state.todos = state.todos.filter((todo) => todo.id !== action.payload);
+ },
+ },
+})
+
+export const { addTodo, toggleTodo, deleteTodo } = todosSlice.actions;
+export default todosSlice.reducer;
diff --git a/src/index.tsx b/src/index.tsx
index 93b5708..2a1c996 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,7 +1,8 @@
import React from 'react';
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
-import { store } from "./state/store";
+
+import { store } from "./app/store";
import App from './app/App';
ReactDOM.render(
diff --git a/src/state/store.ts b/src/state/store.ts
deleted file mode 100644
index 348730a..0000000
--- a/src/state/store.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { createStore } from "redux";
-
-import {todoReducer} from "./todo/reducers";
-
-export const store = createStore(todoReducer);
diff --git a/src/state/todo/reducers.ts b/src/state/todo/reducers.ts
deleted file mode 100644
index 187de7a..0000000
--- a/src/state/todo/reducers.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { TodoItem } from "./types";
-
-interface TodoState {
- todos: TodoItem[];
-}
-
-const initialState: TodoState = {
- todos: [],
-}
-
-export const ADD_TODO = "ADD_TODO";
-export const TOGGLE_TODO = "TOGGLE_TODO";
-export const DELETE_TODO = "DELETE_TODO";
-
-interface AddTodoAction {
- type: typeof ADD_TODO;
- payload: TodoItem;
-}
-
-interface ToggleTodoAction {
- type: typeof TOGGLE_TODO;
- payload: number;
-}
-
-interface DeleteTodoAction {
- type: typeof DELETE_TODO;
- payload: number;
-}
-
-type TodoActionTypes = AddTodoAction | ToggleTodoAction | DeleteTodoAction;
-
-export function todoReducer(
- state = initialState,
- action: TodoActionTypes
-): TodoState {
- switch (action.type) {
- case ADD_TODO:
- return {
- ...state,
- todos: [...state.todos, action.payload],
- };
- case TOGGLE_TODO:
- return {
- ...state,
- todos: state.todos.map((todo) => {
- if (todo.id === action.payload) {
- return { ...todo, completed: !todo.completed };
- }
- return todo;
- }),
- };
- case DELETE_TODO:
- return {
- ...state,
- todos: state.todos.filter((todo) => todo.id !== action.payload),
- };
- default:
- return state;
- }
-}
diff --git a/src/state/todo/types.ts b/src/state/todo/types.ts
deleted file mode 100644
index 1161454..0000000
--- a/src/state/todo/types.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export interface TodoItem {
- id: number;
- text: string;
- completed: boolean;
-}