Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 27, 2022 02:36 pm GMT

Todo with React Redux Typescript

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

mkdir todo-crudcd todo-crud// this creates typescript app with all necessary configurationnpx create-react-app todo --template typescriptcd todonpm i react-icons react-redux redux-persist @reduxjs/toolkit uuidcode .

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.

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.

// Exampleconst 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.

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 reusedexport 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

npm start

It should display the following UI if everything is working on your side.
Image description

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

import {createSlice} from "@reduxjs/toolkit";import {TodoInterface} from "../App";// shape of todos arrayinterface TodosListInterface {    todos: TodoInterface[]}// initial todos stateconst 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 stateexport const {addTodo, deleteTodo, editTodo, toggleTodo} = todoSlice.actions;// reducer to change the stateexport 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

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 storageexport 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

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 stateroot.render(  <React.StrictMode>    <Provider store={store}>      <PersistGate persistor={persistor}>        <App />      </PersistGate>    </Provider>  </React.StrictMode>);

10. Populate App.tsx file with the following code

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

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

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

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

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

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

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.

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 offconst handleToggleTodoChange = () =>    dispatch(toggleTodo({ todoId: todo.id }));/** This event handler deletes current todo on delete button clickIt 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.

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

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

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. 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

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


Original Link: https://dev.to/johongirr/todo-with-react-redux-typescript-4n6i

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To