React Todo CRUD App with Redux and Typescript
Tags: react, webdev, beginners, redux
Published: 2022-10-30 17:09:07

 Hi guys, In today's post we are going to be building a todo application with CRUD operations using React, Redux-Toolkit, and Typescript. Once we'd complete building the app, we're going to be hosting it as well on netlify to share it with others. And I'm also going to give a challenge to you guys at the end of the post. So let's start.

### 1. Create the folder
```js
mkdir todo-crud
cd todo-crud

// this creates typescript app with all necessary configuration
npx create-react-app todo --template typescript
cd todo
npm i react-icons react-redux redux-persist @reduxjs/toolkit uuid
code .
```
### 2. Clean up the unecessary folders for our app
Only the following files must stay within the src folder. Once you've deleted all other files, also delete them wherever they'r used
1. index.tsx
2. App.tsx
3. index.css
 
### 3. Create the components and redux folder and the files. 
```js
src
  /components
    /AddTodo
      AddTodo.tsx
    /EditTodo
      EditTodo.tsx
    /FilterTodo
      FilterTodo.tsx
    /TodoList
      /TodoItem
        TodoItem.tsx
      TodoList.tsx
  /redux
    todo.ts
    store.ts
```

### 4. Populate components folder files with functional components
On each created file within components folder in previous step, type "rafce" or create functional component manually. 
```js
// Example
const AddTodo = ()=>{
  return <div>AddTodo</div>
}
```

### 5. Import all of them to the App file and paste the following code
In this component and in all other components, we're going to be adding appropriate classnames but we're only going to be styling our components once all the logic is written and application works bug free. 
```js
import React from "react";
import AddTodo from "./components/AddTodo/AddTodo";
import EditTodo from "./components/EditTodo/EditTodo";
import FilterTodo from "./components/FilterTodo/FilterTodo";
import TodoList from "./components/TodoList/TodoList";

// define the shape of the todo object and export it so that it'd be reused
export interface TodoInterface {
  id: string;
  task: string;
  completed: boolean;
}

const App = () => {
  const [editTodo, setEditTodo] = useState<TodoInterface | null>(null);
  
 
  return (
    <main className="app">
      <div className="app__wrapper">
        <div className="app__header">
          <h1 className="app__title">Todo App</h1>
        </div>
        <div className="app__inputs-box">
// display edit todo when todo is being edited else display add todo form
          {editTodo?.id ? <EditTodo /> : <AddTodo />}
          <FilterTodo/>
        </div>
        <TodoList/>
      </div>
    </main>
  );
};

export default App;
```

### 6. Type below command
Type npm start it should open a new browser window with the UI we've created so far
```js
npm start
```
It should display the following UI if everything is working on your side.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8qg6tcvqvj73yabwc9v.png)

### 7. Create a reducer by pasting the following code in the "src/redux/todo.ts" file
Now we create reducer to handle state of our application
```js
import {createSlice} from "@reduxjs/toolkit";
import {TodoInterface} from "../App";

// shape of todos array
interface TodosListInterface {
    todos: TodoInterface[]
}


// initial todos state
const initialState: TodosListInterface = {
    todos: []
}


// todo slice with intial state and reducers to mutate state. They perform CRUD and also toggle todo. Redux-Toolkit uses Immutable.js which allows us to mutate state but on the background everything works as immutated state.
export const todoSlice = createSlice({
    name: "todo",
    initialState,
    reducers: {
        addTodo: (state, {payload: {task, id, completed}})=>{       
            state.todos.push({id, task, completed})
        },
        deleteTodo: (state, {payload: {todoId}})=>{
            state.todos = state.todos.filter(todo=> todo.id !== todoId)
        },
        editTodo: (state, {payload: {editedTodo}})=>{
            console.log(editedTodo)
            state.todos = state.todos.map(todo => todo.id === editedTodo.id ? editedTodo : todo);
        },
        toggleTodo: (state, {payload: {todoId}})=>{
            state.todos = state.todos.map(todo => todo.id === todoId ? {...todo, completed: !todo.completed} : todo);
        },
    }
})

// actions for telling reducer what to do with state, they can also include payload for changing state
export const {addTodo, deleteTodo, editTodo, toggleTodo} = todoSlice.actions;

// reducer to change the state
export default todoSlice.reducer;
```

### 8. Set up store with the reducer and paste the below code
Basically we're using redux-persist to persist the state in localstorage. redux-persist has options like only persisting allowed states and not storing not-allowed states. you can look at it in [here](https://www.npmjs.com/package/redux-persist)
```js
import { configureStore } from '@reduxjs/toolkit'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import todoReducer from "./todo";


const persistConfig = {
  key: 'root',
  storage,
}

const persistedReducer = persistReducer(persistConfig, todoReducer)

// we are persisting todos on the local storage
export const store = configureStore({
  reducer: {
    todos: persistedReducer
  },
})

const persistor = persistStore(store)

export {persistor};
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
```

### 9. Open index.tsx file and paste the following code
In this file, we are connecting react and redux and  we are providing all the tree of state of objects as global state and persisting the specified state in store.ts file in the local storage
```js
import React from "react";
import ReactDOM from "react-dom/client";
import { PersistGate } from "redux-persist/integration/react";
import { persistor } from "./redux/store";
import "./index.css";
import App from "./App";
import { store } from "./redux/store";
import { Provider } from "react-redux";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
// we are providing state as global and persisting specified state
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <PersistGate persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>
);
```

### 10. Populate App.tsx file with the following code
```js
import React, { useState } from "react";
import { useSelector } from "react-redux";
import type { RootState } from "./redux/store";
import AddTodo from "./components/AddTodo/AddTodo";
import EditTodo from "./components/EditTodo/EditTodo";
import FilterTodo from "./components/FilterTodo/FilterTodo";
import TodoList from "./components/TodoList/TodoList";

export interface TodoInterface {
  id: string;
  task: string;
  completed: boolean;
}

const App = () => {
// here we are subsribed to todos state and read it on each time it changes
  const todos = useSelector((state: RootState) => state.todos.todos);
// editTodo used to get todo that to be edited
  const [editTodo, setEditTodo] = useState<TodoInterface | null>(null);
// todoFilterValue is used to filter out todos on select
  const [todoFilterValue, setTodoFilterValue] = useState("all");


// gets filterValue from select and sets it in the state
  const getTodoFilterValue = (filterValue: string) =>
    setTodoFilterValue(filterValue);

// gets todo that to be edited and sets it in the state
  const getEditTodo = (editTodo: TodoInterface) => setEditTodo(editTodo);


  return (
    <main className="app">
      <div className="app__wrapper">
        <div className="app__header">
          <h1 className="app__title">Todo App</h1>
        </div>
        <div className="app__inputs-box">
          {editTodo?.id ? (
            <EditTodo editTodo={editTodo} setEditTodo={setEditTodo} />
          ) : (
            <AddTodo />
          )}
          <FilterTodo getTodoFilterValue={getTodoFilterValue} />
        </div>
        <TodoList
          todos={todos}
          todoFilterValue={todoFilterValue}
          getEditTodo={getEditTodo}
          setEditTodo={setEditTodo}
          editTodo={editTodo}
        />
      </div>
    </main>
  );
};
export default App;
```

### 11. Open Todolist component and set types for each property passed to it
```js
import React from "react";
import TodoItem from "./TodoItem/TodoItem";
import { TodoInterface } from "../../App";

type TodoListProps = {
  todos: TodoInterface[];
  todoFilterValue: string;
  getEditTodo: (editTodo: TodoInterface) => void;
  setEditTodo: (editTodo: TodoInterface) => void;
  editTodo: TodoInterface | null;
};

const TodoList = ({
  todos,
  todoFilterValue,
  editTodo,
  getEditTodo,
  setEditTodo,
}: TodoListProps) => {
  return (
    <ul className="todo-list">
      {todos
        .filter((todo) => (todoFilterValue === "all" ? true : todo.completed))
        .map((todo) => (
          <TodoItem
            key={todo.id}
            todo={todo}
            editTodo={editTodo}
            getEditTodo={getEditTodo}
            setEditTodo={setEditTodo}
          />
        ))}
    </ul>
  );
};

export default TodoList;

```

### 12. Open TodoItem component and set types for each property passed to it
```js
import React from "react";
import { TodoInterface } from "../../../App";

type TodoItemProps = {
  todo: TodoInterface;
  editTodo: TodoInterface | null;
  getEditTodo: (editTodo: TodoInterface) => void;
  setEditTodo: (editTodo: TodoInterface) => void;
};

const TodoItem = ({
  todo,
  editTodo,
  getEditTodo,
  setEditTodo,
}: TodoItemProps) => {
  return <li>TodoItem</li>
};

export default TodoItem;

```

### 13. Open FilterTodo component and set types for each property
```js
import React from "react";

type FilterTodoProps = {
  getTodoFilterValue: (filterValue: string) => void;
};

const FilterTodo = ({ getTodoFilterValue }: FilterTodoProps) => {
  return <div>FilterTodo</div>;
};

export default FilterTodo;

```

### 14. Open EditTodo component and set types for each property
```js
import React from "react";
import { TodoInterface } from "../../App";

type EditTodoProps = {
  editTodo: TodoInterface;
  setEditTodo: (editTodo: TodoInterface);
};

const EditTodo = ({ editTodo, setEditTodo }: EditTodoProps) => {
  return <div>EditTodo</div>;
};

export default EditTodo;
```


### 15. Add form to AddTodo component with todo adding logic
```js
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import { addTodo } from "../../redux/todo";

const AddTodo = () => {
  const dispatch = useDispatch();
  const [task, setTask] = useState("");
  const [error, setError] = useState("");

/** 
this function prevents default behaviour page refresh on form submit and sets error to state if length of characters either less than 5 or greater than 50. 
Else if there'r no errors than it dispatches action to the reducer to add new task with unique id. And sets input to empty ""
*/
  const handleAddTaskSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (task.trim().length < 5) {
      setError("Minimum allowed task length is 5");
    } else if (task.trim().length > 50) {
      setError("Maximum allowed task length is 50");
    } else {
      dispatch(addTodo({ task, id: uuidv4(), completed: false }));
      setTask("");
    }
  };

/**
 this function removes error from the state if character length is greater than 5 and less than 50
*/
  const handleUpdateTodoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTask(e.target.value);
    if (task.trim().length > 5 && task.trim().length < 50) {
      setError("");
    }
  };

  return (
    <form onSubmit={handleAddTaskSubmit} className="form">
      <div className="form__control">
        <input
          onChange={handleUpdateTodoChange}
          value={task}
          type="text"
          className="form__input"
          placeholder="Add todo..."
        />
        {error && <p className="form__error-text">{error}</p>}
      </div>
      <button className="btn form__btn">Add Todo</button>
    </form>
  );
};

export default AddTodo;

```

### 16. Test: Add new todos
If you've been following right and you can add new todos and they persist in the store. 
Add Todo sample
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qo06ymq0ahf9cdme2qz4.png)

### 17. Add the logic to delete, toggle todo. And also ability to get current todo
We've also included icons to indicate delete and edit todo. Now only focus on handleDeleteTodoClick which dispatches action to delete todo by it's id. Other handlers are used soon. It should be able to delete todo now. 

```js
import React from "react";
import { MdModeEditOutline } from "react-icons/md";
import { FaTrashAlt } from "react-icons/fa";
import { useDispatch } from "react-redux";
import { deleteTodo, toggleTodo } from "../../../redux/todo";
import { TodoInterface } from "../../../App";

type TodoItemProps = {
  todo: TodoInterface;
  editTodo: TodoInterface | null;
  getEditTodo: (editTodo: TodoInterface) => void;
  setEditTodo: (editTodo: TodoInterface) => void;
};

const TodoItem = ({
  todo,
  editTodo,
  getEditTodo,
  setEditTodo,
}: TodoItemProps) => {
  const dispatch = useDispatch();

// This event handler toggles checkbox on and off
const handleToggleTodoChange = () =>
    dispatch(toggleTodo({ todoId: todo.id }));
  
/** This event handler deletes current todo on delete button click
It also resets editTodo state if it's deleted
*/
  const handleDeleteTodoClick = () => {
    dispatch(deleteTodo({ todoId: todo.id }));
    if (todo.id === editTodo?.id) {
      setEditTodo({ id: "", task: "", completed: false });
    }
  };

// This event handler gets current todo which is to be edited
  const handleGetEditTodoClick = () => getEditTodo(todo);


  return (
    <li className="todo-list__item">
      <label
        htmlFor={todo.id}
        style={
          todo.completed
            ? { textDecoration: "line-through" }
            : { textDecoration: "none" }
        }
        className="todo-list__label"
      >
        <input
          onChange={handleToggleTodoChange}
          checked={todo.completed ? true : false}
          type="checkbox"
          id={todo.id}
          className="todo-list__checkbox"
        />
        {todo.task}
      </label>
      <div className="todo-list__btns-box">
        <button 
onClick={handleGetEditTodoClick}
className="todo-list__btn todo-list__edit-btn">
<MdModeEditOutline />
</button>
        <button
          onClick={handleDeleteTodoClick}
          className="todo-list__btn todo-list__delete-btn"
        >
<FaTrashAlt />
</button>
      </div>
    </li>
  );
};

export default TodoItem;

```
 
### 18. Open EditTodo component to add edit todo logic
It's very similar to AddTodo component but we're using useEffect. Once you paste the code test it in the browser. If everything is working right, then it should be able to edit todo now.
```js
import React, { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import { editTodo as updateTodo } from "../../redux/todo";
import { TodoInterface } from "../../App";

type EditTodoProps = {
  editTodo: TodoInterface;
};

const EditTodo = ({ editTodo }: EditTodoProps) => {
  const dispatch = useDispatch();
  const [task, setTask] = useState("");
  const [error, setError] = useState("");

// effect hook is going to set new task on each time user click todo edit button
  useEffect(() => {
    setTask(editTodo.task);
  }, [editTodo]);

// This event handler dispatches action to update edited todo and resets editTodo state so that form switches from edit todo to add todo 
  const handleEditTaskSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (task.trim().length < 5) {
      setError("Minimum allowed task length is 5");
    } else if (task.trim().length > 50) {
      setError("Maximum allowed task length is 50");
    } else {
      dispatch(updateTodo({ editedTodo: { ...editTodo, task } }));
      setEditTodo({ id: "", task: "", completed: false });
      setTask("");
    }
  };

// this event handler removes error if character length greater than 5 and less than 50
  const handleUpdateTodoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTask(e.target.value);
    if (task.trim().length > 5 && task.trim().length < 50) {
      setError("");
    }
  };

  console.log(editTodo);
  return (
    <form onSubmit={handleEditTaskSubmit} className="form">
      <div className="form__control">
        <input
          onChange={handleUpdateTodoChange}
          value={task}
          type="text"
          className="form__input"
          placeholder="Edit todo..."
        />
        {error && <p className="form__error-text">{error}</p>}
      </div>
      <button className="btn form__btn">Edit Todo</button>
    </form>
  );
};

export default EditTodo;

```

### 19. Add todo filter functionality
Open FilterTodo component and add the following code to enable filter to do functionality. As we've already added logic in TodoList component, we should be able to filter todos based on completed and not completed. Now go and test by adding 2 completed and 2 uncompleted todos, and filter select to all and completed. All should render all 4 and completed should render only 2 items
```js
import React, { useState } from "react";

type FilterTodoProps = {
  getTodoFilterValue: (filterValue: string) => void;
};

const FilterTodo = ({ getTodoFilterValue }: FilterTodoProps) => {
  const [filterTodoVal, setFilterTodoVal] = useState("all");

// This event handler updates current select option and passes currently selected option value to App component  
const handleFilterTodoChanges = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setFilterTodoVal(e.target.value);
    getTodoFilterValue(e.target.value);
  };
  return (
    <select
      onChange={handleFilterTodoChanges}
      value={filterTodoVal}
      className="filter-todo"
    >
      <option value="all">All</option>
      <option value="completed">Completed</option>
    </select>
  );
};

export default FilterTodo;

```

### 20. Apply styling.
Finally we've finished building the application. Now include following code in index.css and after that your application should look like this.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5kh9q5hzuwh0xo94fi9.png)

### 21. Hosting
1. Log in to github and create new repository 
2. In your terminal type following in the  "todo" directory 
   1. git add .
   2. git commit -m "init: initial commit"
   3. Add remote origin of the repository you've created which looks like this, for instance: git remote add origin https://github.com/YourGithubUsername/created-repository-name.git
   4. git push -u origin master
3. This pushes the react code to Github which you should be able to see in your github account
4. Login to Netlify if you've one else create new account which is very straightforward to do
5. Do followings
   1. Click "Add new site" button which opens menu with 3 options
   2. Chose "Import an existing project" option
   3. Connect Netlify to your Github account by clicking Github button
   4. Click above the todo repository that you created
   5. Type "CI= npm run build" in the Build command input
   6. Click "Deploy site" button which deploys the website. It's going to take a few minutes. If everything goes well than application is hosted on the internet.
   7. It's live now. Share it with your friends or even better make it better


### 22. Bonus: Challenge
1. Add pagination with [react-paginate](https://www.npmjs.com/package/react-paginate). There should be select for displaying 5/10/20/50 todos per page
2. Add start date and end date for each todo and it should be styled differently when the task is overdue
3. Add todo search option
4. If you're mern stack developer add authentication: Register, Login and social sign ins with google, linkedin, facebook so that ea. Todos must not be shared with other authenticated users


### [Source Code](https://github.com/JaxongirRahimov/todo-crud-tutorial)

#### Summary
So kudos to all of you who've completed the project with me, I'm sure that you've gained some useful knowledge by completing this tutorial. If you're ambitious and have completed the project please share it with me, I'd like to see what you've done to make it better