This article is translated from upmostly.com/tutorials/b…

PC Version Preview

Build a simple todoList using React and React hooks. This is a good tutorial for beginners and intermediate developers.

I’ll walk you through how to build a simple Todo list with React, using only functional components and the new useState React Hook

Tips: useState Hook will enable us to store internal functional components of state. 👋Goodbye too messy class component, hello hook! 🎣

Create a new React project

We’ll skip all manual build configuration so we can get down to business quickly

npx create-react-app todo-app
Copy the code

Then we develop the todo-app folder using the IDE

Write HTML, CSS styles

When I create a new React component, I like to write the HTML and CSS first. We don’t normally add the Render method to the inner class component. Instead, we return the HTML function component directly and replace app.js with the code below

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="app">
      <div className="header">
        <img src={logo} className="logo" alt="logo" />
      </div>
      <form className="todo-list">
        <ul>
          <div className="todo">
            <div className="checkbox" />
            <input type="text" value="Todo one" />
          </div>
        </ul>
      </form>
    </div>
  );
}

export default App;
Copy the code

Paste the following CSS into app.css and then you can modify it as you like.

App.css
body {
  background-color: #282c34;
  min-height: 100vh;
}

.app {
  padding-top: 10rem;
}

.header {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.logo {
  animation: App-logo-spin infinite 20s linear;
  height: 20vmin;
  pointer-events: none;
}

.todo-list {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
}

.todo {
  display: flex;
  align-items: center;
  margin: 1rem 0;
}

.todo-is-completed .checkbox {
  color: # 000;
  background: #fff;
}

.todo-is-completed input {
  text-decoration: line-through;
}

.checkbox {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  margin-right: 10px;
  cursor: pointer;
  font-size: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  transition: background-color .2s ease-in-out;
  border: 1px solid #fff;
}

.checkbox:hover {
  background: rgba(255, 255, 255, 0.25);
  border: 1px solid rgba(255, 255, 255, 0);
}

.checkbox-checked {
  color: # 000;
  background: #fff;} ul { list-style: none; padding: 0; line-height: 2rem; width: 500px; } input { border: none; background: transparent; color: white; The font - size: 1.4 rem; outline: none; width: 100%; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); }}Copy the code

Add State with useState Hook

Now that we have a nice Todo app, let’s hook on.

Why do we need State?

State allows us to track changes in the React component. A todo list changes frequently. Such as:

  • Add a new to-do list
  • Change your existing to-do list
  • Delete the to-do list
  • Finish your to-do list
  • Not completing the to-do list next import useState hook at the top of the app.js file
import React, { useState } from 'react';
Copy the code

Finally we can initialize our component’s state property like this

App.js
...

function App() {
  const [todos, setTodos] = useState([
    {
      content: 'Pickup dry cleaning',
      isCompleted: true,
    },
    {
      content: 'Get haircut',
      isCompleted: false,
    },
    {
      content: 'Build a todo app in React',
      isCompleted: false,}]); .Copy the code

Tips: Remember that hooks must initialize the react component internally. You can’t initialize it outside of the function.

When you use useState hook, you add two values :getter and setter. In the above code, todos is state itself, and setTodos is the method that updates the value of state. We initialize todos to set a default object, which is populated with an array. Why do we use objects instead of simple strings? Because we need to store two pieces of information for each:

  • To-do list content
  • Whether the to-do list has been completed

Displays to-do items

Save the project and jump to your application running in the browser (execute NPM start) and you will see a Todo item.. 🤔 is so strange. We have three to-do events in our State, why do we only see one? We set the initial status value, but have not yet returned the statement TODO status value to display the to-do list, we need to loop through the TODOS array and render an array of toDO items in each action plan. To do this, we will use the map function

. <form className="todo-list">
  <ul>
    {todos.map((todo, i) => (
      <div className="todo">
        <div className="checkbox" />
        <input type="text" value={todo.content} />
      </div>
    ))}
  </ul>
</form>
...
Copy the code

Map is a very common function. You may encounter it many times, so it’s important that you understand how it works. The above code iterates through the Todos array and renders the HTML in brackets as each item in the array. Then save the code and you’ll see…

Create a new to-do event

Now that we can display the to-do list, let’s add the ability to create a new TODO item. Start by adding an input field for the onKeyDown event handler:

. <div className="todo">
  <div className="checkbox" />
  <input
    type="text"
    value={todo.content}
    onKeyDown={e => handleKeyDown(e, i)}
  />
</div>
...
Copy the code

OnKeyDown calls a function called handleKeyDown. By entering the name and index of the to-do list. Inside handleKeyDown, we find that if the return key is pressed. If it is, we call it createTodoAtIndex. Let’s add these two functions to the return statement, as follows:

.function handleKeyDown(e, i) {
  if (e.key === 'Enter') { createTodoAtIndex(e, i); }}function createTodoAtIndex(e, i) {
  const newTodos = [...todos];
  newTodos.splice(i + 1, 0, {
    content: ' ',
    isCompleted: false});setTodos(newTodos);
  setTimeout(() => { document.forms[0].elements[i + 1].focus(); }, 0); }...Copy the code

The createTodoAtIndex function looks complicated, but it’s actually quite simple. Let me break it down:

  • We check the value of event.key by listening for the return key to be pressed
  • Next, we create a replica toDOS status array. We do this because state should not be changed directly
  • Using the copied to-do event, we insert a new empty to-do item after it is currently selected. That’s why we need to pass the index of the current todo to this function, right
  • After inserting the new toDOS copy, we update the copy of the original array
  • Finally, we focus to Input

You may have noticed that the component that updates its internal state 0 milliseconds after the timeout is triggered in line Focus to Input does not react instantaneously. Sometimes it takes time, especially when we update a lot of data. So we add a delay to get rid of the newly rendered input while waiting for state to complete the update. [image-7255Ee-1561032624160]

Update a to-do list

Now that we can create a new task, let’s add a value that is actually filled in. The text box has an onChange event that is triggered when the value of the field changes. Be careful that while Input Values itself does not provide an update binding function, instead, an event object allows you to find the updated values via event.target.value and add the following methods after the createTodoAtIndex method

.function updateTodoAtIndex(e, i) {
  const newTodos = [...todos];
  newTodos[i].content = e.target.value;
  setTodos(newTodos); }...Copy the code

Much like the createTodoAtIndex method, updateTodoAtIndex takes two parameters: Event and to-do index. Again, we copy the Todos array to avoid direct state. In this copied array, we add a Content key to todos, which is the values in the Event. Finally, we update the todos state with the copied array.

Delete todos

If todos values is empty, press backspace to delete the todos function. We’re calling it a new feature called removeTodoAtIndex

function handleKeyDown(e, i) {
  if (e.key === 'Enter') {
    createTodoAtIndex(e, i);
  }
  if (e.key === 'Backspace' && todos[i].content === ' ') {
    e.preventDefault();
    returnremoveTodoAtIndex(i); }}function removeTodoAtIndex(i) {
  if (i === 0 && todos.length === 1) return;
  setTodos(todos => todos.slice(0, i).concat(todos.slice(i + 1, todos.length)));
  setTimeout(() => {
    document.forms[0].elements[i - 1].focus();
  }, 0);
}
Copy the code

Save the component and return it to your browser so you can try out the functionality for yourself

Perfect todos

We discussed creating, updating, and deleting a Todo. Now let’s perfect it. Now I’ve added the hover effect to the circle, but clicking on it doesn’t have any effect. Let’s change it by adding an onClick binding event and the todo-IS-Completed style name

. <div className={`todo${todo.isCompleted && 'todo-is-completed'}`}>
  <div className={'checkbox'} onClick={() => toggleTodoCompleteAtIndex(i)}>
    {todo.isCompleted && (
      <span>&#x2714; 
    )}
  </div>
  <input
    type="text"
    value={todo.content}
    onKeyDown={e => handleKeyDown(e, i)}
    onChange={e => updateTodoAtIndex(e, i)}
  />
</div>

...
Copy the code

Finally, add toggleTodoCompletedAtIndex function

functiontoggleTodoCompleteAtIndex(index) { const temporaryTodos = [...todos]; temporaryTodos[index].isCompleted = ! temporaryTodos[index].isCompleted;setTodos(temporaryTodos);
}
Copy the code

After saving, let’s have a look at the final result! [image-93efad-1561032624160] [image-93efad-1561032624160]

Completed source code

I’ve provided the complete source code below so you can see our reaction todo component in all its uses.

import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  const [todos, setTodos] = useState([
    {
      content: 'Pickup dry cleaning',
      isCompleted: true,
    },
    {
      content: 'Get haircut',
      isCompleted: false,
    },
    {
      content: 'Build a todo app in React',
      isCompleted: false,}]);function handleKeyDown(e, i) {
    if (e.key === 'Enter') {
      createTodoAtIndex(e, i);
    }
    if (e.key === 'Backspace' && todos[i].content === ' ') {
      e.preventDefault();
      returnremoveTodoAtIndex(i); }}function createTodoAtIndex(e, i) {
    const newTodos = [...todos];
    newTodos.splice(i + 1, 0, {
      content: ' ',
      isCompleted: false});setTodos(newTodos);
    setTimeout(() => {
      document.forms[0].elements[i + 1].focus();
    }, 0);
  }

  function updateTodoAtIndex(e, i) {
    const newTodos = [...todos];
    newTodos[i].content = e.target.value;
    setTodos(newTodos);
  }

  function removeTodoAtIndex(i) {
    if (i === 0 && todos.length === 1) return;
    setTodos(todos => todos.slice(0, i).concat(todos.slice(i + 1, todos.length)));
    setTimeout(() => {
      document.forms[0].elements[i - 1].focus();
    }, 0);
  }

  functiontoggleTodoCompleteAtIndex(index) { const temporaryTodos = [...todos]; temporaryTodos[index].isCompleted = ! temporaryTodos[index].isCompleted;setTodos(temporaryTodos);
  }

  return (
    <div className="app">
      <div className="header">
        <img src={logo} className="logo" alt="logo" />
      </div>
      <form className="todo-list">
        <ul>
          {todos.map((todo, i) => (
            <div className={`todo ${todo.isCompleted && 'todo-is-completed'}`}>
              <div className={'checkbox'} onClick={() => toggleTodoCompleteAtIndex(i)}>
                {todo.isCompleted && (
                  <span>&#x2714; 
                )}
              </div>
              <input
                type="text"
                value={todo.content}
                onKeyDown={e => handleKeyDown(e, i)}
                onChange={e => updateTodoAtIndex(e, i)}
              />
            </div>
          ))}
        </ul>
      </form>
    </div>
  );
}

export default App;
Copy the code