Coding Heaven logo

Coding Heaven

Tutorial: How to create a To Do app with React framework

    Category: react
Author's Photo

Author: V.

Date: 09/20/2024

The Astro logo on a dark background with a pink glow.

React app

Hello, fellow dev đź‘‹ Today we are going to create a to-do list with React. I use Visual Studio Code and Node.js. So make sure you have those installed, and let's start!


If you’d prefer to skip the details and get a quick overview, check out the video below!


Step 1:

Create a new React App:

Let's create our React app by running the following command:


    npx create-react-app mytodo

After long waiting, we have all our necessary React dependencies installed. So we are ready to code. I am going to clean up and get rid of all unnecessary files from the src folder. After that our project should look like this:


    todo
        -node_modules
        -public
        -src
            -App.js
            -App.css
            -index.js
        -package.json


Step 2:

Create our Components

Since React is component-based, I am going to utilize this feature and create a few components, such as Header, List, and Task. The Header component will be responsible for displaying the time and date, List will handle adding, editing, and deleting logic, and Task will handle marking our task complete.
Also, I want to add a util.js for additional formatting for my header and dates.


Here is the updated file structure for our app:


        -node_modules
        -public
        -src
            -components
                -Header.js
                -List.js
                -Task.js
            -App.js
            -App.css
            -index.js
            -util.js
        -package.json


Step 3: Update Header.js and set up Util.js

First, we need to create a file that will help us to perform date and time formatting, plus additional functionality for our project - util.js

Inside util.js


 //util.js

 //Handle date conversion

 const DateHandling = (month, day) => {

        //Create array that holds 12 months 
    const months = ["January", "February", "March", 
    "April", "May", "June", "July", "August", 
    "September","October", "November", "December"]

    //Create array that holds days of the week
    const weekDay = ["Monday", "Tuesday",
    "Wednesday","Thursday","Friday",
    "Saturday", "Sunday"]

    return [months[month], weekDay[day]]
 }

    //We want to make sure that our task description 
    //will be less than 120 characters, but more than 0

 const ValidateInput = (message) => {
    if(message.length < 120 & message.length > 0){
       return true
    }
    else{
       return false
    }
 }

    export {DateHandling, ValidateInput};



Inside our Header.js we are going to add the following code:



 // Header.js

 import { useEffect, useState } from 'react' 
 import { DateHandling } from '../util' 
 import './styles/Header.css' 

 const Header = () => { 

  /* Creating a new date and using our 
  utilities method to extract our date info */ 
  let todayDate = new Date() 
  let convertedDate = DateHandling(todayDate.getMonth(), todayDate.getDay()) 

  /* We need to store our date state and update when needed, 
  so we use React Hook 'useState'.
  We set our day to today, extract the month, and assign time */ 
  const [today, setToday] = useState({ 
    day: todayDate.getDate(), 
    month: convertedDate[0], 
    time: todayDate.toLocaleTimeString() 
  }) 

  /* Here we handle our formatting and make sure we display 
  our time correctly */ 
  const formatTime = (time) => { 
    let timeFirstHalf = time.split(":") 
    let timePeriod = timeFirstHalf[2].split(" ") 

    let formattedTime = `${timeFirstHalf[0]}: ${timeFirstHalf[1]} ${timePeriod[1]}` 
    return formattedTime 
  } 

  // We create a separate function to update time 
  const updateTime = () => { 
    setToday((prev) => ({ 
      ...prev, 
      time: formatTime(new Date().toLocaleTimeString()) 
    })) 
  } 

  // useEffect hook helps us update our time every 60 seconds 
  useEffect(() => { 

    updateTime() 
    const intervalId = setInterval(updateTime, 60000) 
    return () => clearInterval(intervalId) 

  }, []) 

  // Here is our actual JSX, we display month, day, and time. 
  return ( 
    <header> 
      <h1>Today</h1> 
      <div> 
        <h2>{today.month} {today.day}</h2> 
        <h2>{today.time}</h2> 
      </div> 
    </header> 
  ) 
 } 

 export default Header 



Step 4: Add Task Component

We have to separate our single task from the rest of the application, and we created Task.js for that. This component will accept a few properties, such as task description, whether it is in update mode, and whether it is still relevant.

I am going to use FontAwesome Icons; the easiest way to integrate it is to install it with npm:

 npm install @fortawesome/react-fontawesome 
 npm install @fortawesome/free-solid-svg-icons

After that, we are ready to create our task component.


 // Task.js

 // Import React Hooks, and additional Font Awesome icons
 import { useState } from 'react'
 // Import the library for our Font Awesome
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 
 // Name of the icon
 import { faCheck } from '@fortawesome/free-solid-svg-icons' 

// Define our Task with 3 properties
 const Task = ({ taskDescription, isActive, inUpdateMode }) => {

  // We want to hold state for our task, if it is completed we want 
  // to reflect it
  const [isCompleted, setTaskState] = useState(false)

  const markTask = () => {

    if (!isCompleted) {
      setTaskState(true)
    } else {
      setTaskState(false)
    }
  }

  // When user clicks on the task checkmark 
  // it will mark/unmark the task as completed
  return (
    <div id='single-task-container'>
      
      <div id="task_description_container">
        <div id="task_point" onClick={markTask}>
          {isCompleted ?  
            <FontAwesomeIcon icon={faCheck} id="checkmark"/> : '  '}
        </div>
        {/* If we click on task itself we will move to updateMode
        Where we can edit description and save the new value */}
        <p onClick={inUpdateMode}>{taskDescription}</p>
      </div>

      <span onClick={isActive}>x</span>           
    </div>
  )
}

 export default Task



Step 5: Add List Component

As I mentioned above, we need to handle logic for our list, such as adding, editing, or removing items. And the best place for it is our List.js

First of all, we need to import all necessary functions and methods.


 //List.js

 // We importing our previously created Component
 import Task from './Task';
 import { useEffect, useState } from 'react';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faArrowRight } from '@fortawesome/free-solid-svg-icons';
 import { ValidateInput } from '../util';

After that we can create our List function. We set our initial state for our current list and assign it to our local storage, so we will be able to access our list even after we refresh the page.


 //previous code
 // List.js
 const List = () => {
  // Set state for a new task, for now it is null
  const [newTask, setTask] = useState('')

  // Our initial state will read data with 
  // "tasks" from the browser's local storage
  const [tasks, setTasks] = useState(() => {
    // Getting stored value
    const toDos = localStorage.getItem("tasks")
    const initialValue = JSON.parse(toDos)
    return initialValue || []
  })

  // We will display an error if any, initial state is null
  const [error, setError] = useState(null)

  // Handle Task Update
  const [inUpdateMode, setUpdateMode] = useState(false)

  const [taskforUpdate, setTaskForUpdate] = useState({
    index: null,
    text: null
  })

  // Every time we update our task, we will set 
  // our local storage with new data
  useEffect(() => {
    localStorage.setItem('tasks', JSON.stringify(tasks))
  }, [tasks])
 }


Right now we can add, create, edit, and delete functionality for our tasks.

 // List.js
 const List = () => {

  // ...previous code

  // We are adding a new task, set error to null, 
  // and validate the input.
  // If everything is good we add the task to the list
  // else we display an error
  const addNewTask = (e) => {
    e.preventDefault()
    setError(null)
    if (ValidateInput(newTask)) {
      setTasks(prev => [...prev, newTask])
      setTask('')            
    } else {
      setError("Make sure your input is correct!")
    }
  }

  // We use task index and create a new list 
  // with tasks without the selected one 
  const removeTask = (index) => {
    let newList = tasks.filter((_, i) => i !== index)
    setTasks(newList)
  }

  const handleUpdate = (index, text) => {
    setUpdateMode(true)
    setTaskForUpdate((prevState) => ({
      ...prevState,
      index: index,
      text: text
    }))
  }

  // We use index and new text to change the description
  // If new description passes validation, we reassign the description
  const updateTask = (index, task) => {
    if (ValidateInput(task)) {
      let newArray = tasks
      newArray[index] = task
      setTasks(newArray)
      setUpdateMode(false)
    } else {
      setError("Make sure your input is correct!")
    }
  }
 }

We are done with our main functionality; now let's return the view to the user.

 // List.js
 const List = () => {

  // ...prev code
  // create, edit and delete functionality

  // Here we define whether we need to display "Edit task" or "Add new task"
  return (
    <section id="list_section">
      <p id="error">{error}</p>
      {
        inUpdateMode ? (
          <div>
            <h1>Edit your Task</h1>
            <form className="newTask_container" 
              onSubmit={(e) => {
                e.preventDefault()
                updateTask(taskforUpdate.index, taskforUpdate.text)
              }}>
              <input 
                type='text' 
                value={taskforUpdate.text} 
                onChange={(e) => { 
                  setTaskForUpdate((prevState) => 
                    ({ ...prevState, text: e.target.value })) 
                  setError(null)
                }} 
              />
              <FontAwesomeIcon 
                icon={faArrowRight} 
                className="SubmitBtn" 
                onClick={() => { updateTask(taskforUpdate.index, taskforUpdate.text) }} 
              />
            </form>
          </div>
        ) : (
          <div>
            <form className="newTask_container" onSubmit={addNewTask}>
              <input 
                type='text' 
                placeholder="Add new task..." 
                value={newTask} 
                onChange={(e) => { 
                  setTask(e.target.value) 
                  setError(null) 
                }} 
              />
              <FontAwesomeIcon 
                icon={faArrowRight} 
                className="SubmitBtn"  
                onClick={addNewTask} 
              />
            </form>
            
            {tasks.length === 0 ? <h1>No active tasks</h1> :  
              <h1>Your active tasks</h1>}
            
            {
              tasks.map((task, index) => 
                <Task 
                  taskDescription={task} 
                  key={index} 
                  inUpdateMode={() => handleUpdate(index, task)} 
                  isActive={() => removeTask(index)} 
                />
              )
            }   
          </div>
        )
      }
    </section>
  )
 }

 export default List // remember to export your components



Step 6: Update App.js

We did create our component, but you still can’t see many changes on your page; we have to import it to your App.js. Because this is our root component… for now.

Here is the updated app. js file


 import './App.css';
 import List from './components/List';
 import Header from './components/Header';

    function App() {
    return (
        <div className="App">
            <div id="app_container">
                <Header />
                <List />
            </div>
        </div>
    );
    }

 export default App;



Step 7: Add Styles

For each component, I am going to create its own stylesheet. So inside the component folder, we make a new directory named Styles. Inside the new directory we add 3 stylesheets: Header.css, List.css and Task.css


    /*Header.css*/
    header{
            text-align: left;
            width:700px;
            margin: auto;
            font-family: Roboto;
            padding: 10px 30px 0 30px;
            color:white;
            transition: all 0.5s;
        }
        
        header div{
            display: flex;
            align-items: center;
            margin-top: 0.2em;
            justify-content: space-between;
        }
        
        
        header h1{
            font-weight: 400;
            font-size: 1.6em;
        }
        
        header h2{
            font-weight: 300;
            font-size: 1.4em;
            text-align: center;
        }

Make sure to add this reference to your Header.js.


    //Header.js
    import './styles/Header.css'
    //the rest of the code 


Right now you can create and play around with the style for List.css and Task.css. Or you can look at the whole code in the repo.

Thank you for sticking with me during this tutorial. Happy Coding!

Tags: React React App Frontend Frameworks Tutorial