DEV Community

Cover image for Level-up your JavaScript and React by building a Todo app (tutorial)
Amir Ghezala
Amir Ghezala

Posted on • Updated on

Level-up your JavaScript and React by building a Todo app (tutorial)

We will build THE classic React app: a TodoList. It’s a simple app and yet full of interesting React and JavaScript concepts.

alt text

We will cover:

  • Creating functional React components and managing their state with hooks.
  • Handling conditional rendering in React.
  • Using PropTypes for a safer code.
  • JavaScript concepts: the ternary operator, array methods, and the spread operator.
  • Refactoring code to reduce code redundancy and increase code readability.

You can find the final source code here and the live version here.

The app requirements

The app allows us to perform the following operations:

  • Add a todo (it gets added to the Pending section).
  • Complete a todo (it gets added to the Completed section).
  • Delete a todo (whether it’s pending or completed).

User Interface Mock

Here’s a simple sketch of the UI of our application:

https://cdn-images-1.medium.com/max/1600/1*oKILiFhNvhww5fBCBpQQsA.png

As you can see, our UI consists of the following parts:

  • Text input field where the user types a todo.
  • Pending section: contains the todos added by the user that have not been completed yet.
  • Completed section: contains the todos completed by the user.

Project Setup

We use create-react-app CLI tool to bootstrap our project:

npx create-react-app todo-app
Enter fullscreen mode Exit fullscreen mode

This gives us the necessary boilerplate to immediately get productive and not worry about any build tools.

We’re now ready for implementation.

Note: to keep this article short and focused, we will not get into details about the CSS part of the app. To have a similar look to the final result, make sure to copy the same code in App.css file and use the same classNames.

Adding a heading with the App title

Let’s delete the boilerplate code in App.js and add a header that contains the title of our todo app:

import React from "react";

import "./App.css";


function App() {
  return (
    <div className="app">
        <h1>Todo</h1>
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Adding the Input field

Let's add a basic text input element for the user to type a todo.

To keep track of the value in the input field, we need to save and update that value whenever the user types-in something.

In React we store our application data in the app state. To manage our text input state, we use React.useState hook.

Note: You can find a detailed article about handling state with React.useState hook here.

We can import the useState function from React as follows:

import React, { useState } from "react";
Enter fullscreen mode Exit fullscreen mode

The typed-in todo by the user has a text value. Therefore, let’s initialize it with an empty string:

const [typedInTodo, setTypedInTodo] = useState("");
Enter fullscreen mode Exit fullscreen mode

useState provides an array that contains 2 elements:

  • A typedInTodo value that we can use to populate the input field.
  • A setTypedInTodo function to update the todo. We’ll see later how to do that.
import React, { useState } from "react";

import "./App.css";


function App() {
  const [typedInTodo, setTypedInTodo] = useState("");

  return (
    <div className="app">
        <h1>Todo</h1>
        <input type="text" placeholder="Add todo..." value={typedInTodo} />
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Handling changes to the input

If we try to type anything in the input field, we’ll notice that it stays empty. This is because we‘re not updating its value based on what the user is typing.

To react to the user typing, we add an onChange event listener to the input field.

Our event listener receives an event parameter that we can use to extract the typed-in value and update the state with it:

onChange={(event) => setTypedInTodo(event.target.value)}
Enter fullscreen mode Exit fullscreen mode
import React, { useState } from "react";

import "./App.css";


function App() {
  const [typedInTodo, setTypedInTodo] = useState("");

  return (
    <div className="app">
      <h1>Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
      />
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Now if we type something in the input field, it gets updated. We can check if the state is updated by outputting the new value of typedInTodo to the console:

console.log(typedInTodo)
Enter fullscreen mode Exit fullscreen mode

Let’s make it possible to submit a todo

Since the pending section will hold the submitted todos, it has to have its own state to store such data. We define its state, similarly to how we did it for the typed-in todo, using React.useState. Since it’s a list, we need an array to store this data. Initially, it is an empty array:

const [pendingTodos, setPendingTodos] = useState([]);
Enter fullscreen mode Exit fullscreen mode
import React, { useState } from "react";

import "./App.css";

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);

  return (
    <div className="app">
      <h1>Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
      />
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Let’s now make it possible to add a todo to the pending section by hitting the “Enter” key.

We can do so by adding the onKeyDown event listener to the input field. The listener function adds the typed-in todo to the pending section if the following conditions hold:

  • The user pressed the “Enter” key.
  • The typed-in todo is not empty. To remove spaces when checking that, we can use String.prototype.trim() method.

To add the todo to the pendingTodos, we use the Spread operator, which allows us to expand the array into individual elements and add a new todo to it.

We should also not forget to clear the input field once the todo is submitted. We can do so by updating the typedInTodo to an empty string.

import React, { useState } from "react";

import "./App.css";

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);

  function onKeyDown(e) {
    if (e.key === "Enter" && typedInTodo.trim()) {
      setPendingTodos([...pendingTodos, typedInTodo]);
      setTypedInTodo("");
    }
  }

  return (
    <div className="app">
      <h1>Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
        onKeyDown={onKeyDown}
      />
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Let's see if our logic works properly by adding some logs:

console.log(typedInTodo);
console.log(pendingTodos);
Enter fullscreen mode Exit fullscreen mode

Note: if your console is logging values twice then you need to remove the StrictMode element from index.js file.

Showing the Pending todos

The todos submitted by the user are stored in the state, but we can’t see them in the UI yet.

Let’s add a “Pending” section to show these todos.

The pending section has:

  • Title: an 'h2' header named "Pending" that appears dimmed when the section is empty and appears bold whenever a todo is added.
  • List: contains the current pending todos.

We use Array.prototype.map method to map each pending todo in pendingTodos to a div that has:

  • The text of the todo.
  • A button to complete the todo.
  • A button to delete the todo.
import React, { useState } from "react";
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";

import "./App.css";

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);

  function onKeyDown(e) {
    if (e.key === "Enter" && typedInTodo.trim()) {
      setPendingTodos([...pendingTodos, typedInTodo]);
      setTypedInTodo("");
    }
  }

  return (
    <div className="app">
      <h1>Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
        onKeyDown={onKeyDown}
      />
      <div className="sectionsContainer">
        <div className="todoContainer">
          <h2
            className={
              pendingTodos.length > 0
                ? "boldSectionTitle"
                : "dimmedSectiontTitle"
            }
          >
            Pending
          </h2>
          <div>
            {pendingTodos.map((todo, index) => (
              <div key={index} className="todoItem">
                <p>{todo}</p>
                <div className="buttonsSection">
                  <button className="transparent completeButton">
                    <CheckOutlined className="icon" />
                  </button>
                  <button className="transparent deleteButton">
                    <CloseOutlined className="icon" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Note: your console will inform you that every rendered todo task in our components needs its own key attribute to identify it from its siblings and that is why we added such attribute to the todos.

Adding the delete ❌ functionality

Let's make the delete button work. Our delete function:

  • Gets the index of the todo to delete.
  • Filters out the todo from the current list of pending todos, by removing any todo that has the same index as the target one.

Note: the filter function passed to Array.prototype.filter accepts 2 arguments: the todo itself (which we don't need and therefore we labeled it as _ ) and the index of the todo.

import React, { useState } from "react";
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";

import "./App.css";

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);

  function deleteTodo(todoIndex) {
    const filteredTodos = pendingTodos.filter(
      (_, index) => todoIndex !== index
    );
    setPendingTodos(filteredTodos);
  }

  function onKeyDown(e) {
    if (e.key === "Enter" && typedInTodo.trim()) {
      setPendingTodos([...pendingTodos, typedInTodo]);
      setTypedInTodo("");
    }
  }

  return (
    <div className="app">
      <h1>Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
        onKeyDown={onKeyDown}
      />
      <div className="sectionsContainer">
        <div className="todoContainer">
          <h2
            className={
              pendingTodos.length > 0
                ? "boldSectionTitle"
                : "dimmedSectiontTitle"
            }
          >
            Pending
          </h2>
          <div>
            {pendingTodos.map((todo, index) => (
              <div key={index} className="todoItem">
                <p>{todo}</p>
                <div className="buttonsSection">
                  <button className="transparent completeButton">
                    <CheckOutlined className="icon" />
                  </button>
                  <button
                    className="transparent deleteButton"
                    onClick={() => deleteTodo(index)}
                  >
                    <CloseOutlined className="icon" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Adding the complete ✅ functionality

We first need to create a state value for the completed todos:

 const [completedTodos, setCompletedTodos] = useState([]);
Enter fullscreen mode Exit fullscreen mode

The complete function:

  • Gets the pending todo as an argument.
  • Adds the pending todo to completedTodos by using the spread operator [...].
  • Deletes the todo from the pendingTodos array.
import React, { useState } from "react";
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";

import "./App.css";

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);
  const [completedTodos, setCompletedTodos] = useState([]);

  function completeTodo(todoIndex) {
    const pendingTask = pendingTodos[todoIndex];
    setCompletedTodos([...completedTodos, pendingTask]);
    deleteTodo(todoIndex);
  }

  function deleteTodo(todoIndex) {
    const filteredTodos = pendingTodos.filter(
      (_, index) => todoIndex !== index
    );
    setPendingTodos(filteredTodos);
  }

  function onKeyDown(e) {
    if (e.key === "Enter" && typedInTodo.trim()) {
      setPendingTodos([...pendingTodos, typedInTodo]);
      setTypedInTodo("");
    }
  }

  return (
    <div className="app">
      <h1>Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
        onKeyDown={onKeyDown}
      />
      <div className="sectionsContainer">
        <div className="todoContainer">
          <h2
            className={
              pendingTodos.length > 0
                ? "boldSectionTitle"
                : "dimmedSectiontTitle"
            }
          >
            Pending
          </h2>
          <div>
            {pendingTodos.map((todo, index) => (
              <div key={index} className="todoItem">
                <p>{todo}</p>
                <div className="buttonsSection">
                  <button
                    className="transparent completeButton"
                    onClick={() => completeTodo(index)}
                  >
                    <CheckOutlined className="icon" />
                  </button>
                  <button
                    className="transparent deleteButton"
                    onClick={() => deleteTodo(index)}
                  >
                    <CloseOutlined className="icon" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Since the Completed section is exactly the same as the Pending one, let's copy paste the same code and just remove the complete button from it and update the section's heading:

import React, { useState } from "react";
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";

import "./App.css";

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);
  const [completedTodos, setCompletedTodos] = useState([]);

  function completeTodo(todoIndex) {
    const pendingTask = pendingTodos[todoIndex];
    setCompletedTodos([...completedTodos, pendingTask]);
    deleteTodo(todoIndex, "pending");
  }

  function deleteTodo(todoIndex) {
    const filteredTodos = pendingTodos.filter(
      (_, index) => todoIndex !== index
    );
    setPendingTodos(filteredTodos);
  }

  function onKeyDown(e) {
    if (e.key === "Enter" && typedInTodo.trim()) {
      setPendingTodos([...pendingTodos, typedInTodo]);
      setTypedInTodo("");
    }
  }

  return (
    <div className="app">
      <h1>Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
        onKeyDown={onKeyDown}
      />
      <div className="sectionsContainer">
        <div className="todoContainer">
          <h2
            className={
              pendingTodos.length > 0
                ? "boldSectionTitle"
                : "dimmedSectiontTitle"
            }
          >
            Pending
          </h2>
          <div>
            {pendingTodos.map((todo, index) => (
              <div key={index} className="todoItem">
                <p>{todo}</p>
                <div className="buttonsSection">
                  <button
                    className="transparent completeButton"
                    onClick={() => completeTodo(index)}
                  >
                    <CheckOutlined className="icon" />
                  </button>
                  <button
                    className="transparent deleteButton"
                    onClick={() => deleteTodo(index)}
                  >
                    <CloseOutlined className="icon" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
        <div className="todoContainer">
          <h2
            className={
              completedTodos.length > 0
                ? "boldSectionTitle"
                : "dimmedSectiontTitle"
            }
          >
            Completed
          </h2>
          <div>
            {completedTodos.map((todo, index) => (
              <div key={index} className="todoItem">
                <p>{todo}</p>
                <div className="buttonsSection">
                  <button
                    className="transparent deleteButton"
                    onClick={() => deleteTodo(index)}
                  >
                    <CloseOutlined className="icon" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Notice that the deleteTodo function only works with pending todos.

Making the delete ❌ functionality work in the Completed Section

To make our deleteTodo function support both sections, we should provide it a second argument that tells it what the target section is.

Based on that, the deleteTodo function can then know whether to delete a pending todo or a completed one.

After all the only things that change when deleting a completed or a pending todo are the target todo list and its state setter function:

import React, { useState } from "react";
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";

import "./App.css";

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);
  const [completedTodos, setCompletedTodos] = useState([]);

  function completeTodo(todoIndex) {
    const pendingTask = pendingTodos[todoIndex];
    setCompletedTodos([...completedTodos, pendingTask]);
    deleteTodo(todoIndex, "pending");
  }

  function deleteTodo(todoIndex, targetSection) {
    const targetList =
      targetSection === "pending" ? pendingTodos : completedTodos;
    const setter =
      targetSection === "pending" ? setPendingTodos : setCompletedTodos;
    const filteredTodos = targetList.filter((_, index) => todoIndex !== index);
    setter(filteredTodos);
  }

  function onKeyDown(e) {
    if (e.key === "Enter" && typedInTodo.trim()) {
      setPendingTodos([...pendingTodos, typedInTodo]);
      setTypedInTodo("");
    }
  }

  return (
    <div className="app">
      <h1>Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
        onKeyDown={onKeyDown}
      />
      <div className="sectionsContainer">
        <div className="todoContainer">
          <h2
            className={
              pendingTodos.length > 0
                ? "boldSectionTitle"
                : "dimmedSectiontTitle"
            }
          >
            Pending
          </h2>
          <div>
            {pendingTodos.map((todo, index) => (
              <div key={index} className="todoItem">
                <p>{todo}</p>
                <div className="buttonsSection">
                  <button
                    className="transparent completeButton"
                    onClick={() => deleteTodo(index, "pending")}
                  >
                    <CheckOutlined className="icon" />
                  </button>
                  <button
                    className="transparent deleteButton"
                    onClick={() => deleteTodo(index, "completed")}
                  >
                    <CloseOutlined className="icon" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
        <div className="todoContainer">
          <h2
            className={
              completedTodos.length > 0
                ? "boldSectionTitle"
                : "dimmedSectiontTitle"
            }
          >
            Completed
          </h2>
          <div>
            {completedTodos.map((todo, index) => (
              <div key={index} className="todoItem">
                <p>{todo}</p>
                <div className="buttonsSection">
                  <button
                    className="transparent deleteButton"
                    onClick={() => deleteTodo(index)}
                  >
                    <CloseOutlined className="icon" />
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

The todo app is working now, but we should try to make our code more readable by removing some redundancy.

Refactoring

If you look at our rendered sections code you can notice that it is just one list of todos in a section that we duplicated to create another section. So why don’t we just create that list as a single reusable component and render our sections lists Conditionally based on the necessary props we pass to that component.

What are these props that our list component needs in order to render the sections we created in the previous approach?

  • sectionType: either"Pending" or "Completed"
  • todoList: the list of todos to render (either completedTodos or pendingTodos).
  • deleteTodo and completeTodo functions.

Let’s refactor our code following these steps :

  • Create a variable named sectionTypeTitle to store the values of the first prop and avoid potential typos.
  • Create our component, call it TodoList and pass to it the props.
  • Show the appropriate section header based on sectionType.
  • Only render the complete button if the sectionType is "pending".
  • Map through the passed todoList and render the todos.
import React, { useState } from "react";
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";

import "./App.css";

const sectionTypeTitle = {
  completed: "Completed",
  pending: "Pending",
};

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);
  const [completedTodos, setcompletedTodos] = useState([]);

  function completeTodo(todoIndex) {
    const pendingTask = pendingTodos[todoIndex];
    setcompletedTodos([...completedTodos, pendingTask]);
    deleteTodo(todoIndex, "pending");
  }

  function deleteTodo(todoIndex, todoSection) {
    const targetList =
      todoSection === "pending" ? pendingTodos : completedTodos;
    const setter =
      targetList === pendingTodos ? setPendingTodos : setcompletedTodos;
    const filteredTodos = targetList.filter((_, index) => todoIndex !== index);
    setter(filteredTodos);
  }

  function onKeyDown(e) {
    if (e.key === "Enter" && typedInTodo.trim()) {
      setPendingTodos([...pendingTodos, typedInTodo]);
      setTypedInTodo("");
    }
  }

  return (
    <div className="app">
      <h1 className="title">Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
        onKeyDown={onKeyDown}
      />
      <div className="sectionsContainer">
        <TodoList
          sectionTitle="pending"
          completeTodo={completeTodo}
          deleteTodo={deleteTodo}
          todoList={pendingTodos}
        />
        <TodoList
          sectionTitle="completed"
          todoList={completedTodos}
          deleteTodo={deleteTodo}
        />
      </div>
    </div>
  );
}

export default App;

function TodoList({ sectionTitle, completeTodo, deleteTodo, todoList }) {
  return (
    <div className="todoContainer">
      <h2
        className={
          todoList.length > 0 ? "boldSectionTitle" : "dimmedSectiontTitle"
        }
      >
        {sectionTypeTitle[sectionTitle]}
      </h2>
      <div>
        {todoList.map((todo, index) => (
          <div className="todoItem" key={index}>
            <span>{todo}</span>
            <div className="buttonsSection">
              {sectionTitle === "pending" && (
                <button
                  className="transparent completeButton"
                  onClick={() => completeTodo(index)}
                >
                  <CheckOutlined className="icon" />
                </button>
              )}
              <button
                className="transparent deleteButton"
                onClick={() => deleteTodo(index, sectionTitle)}
              >
                <CloseOutlined className="icon" />
              </button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Adding validation to our code

One last thing we can add to our components is props validation using prop-types.
Our passed in props need to be of certain types and we need to enforce that in our code to avoid catching bugs.

First, we import PropTypes from 'prop-types' at the top of our app code.

import PropTypes from 'prop-types';
Enter fullscreen mode Exit fullscreen mode

Then use the validators that come with it to validate the data our TodoList component receives.

TodoList.propTypes = {
  sectionTitle: PropTypes.oneOf(["pending", "completed"]).isRequired,
  completeTodo: PropTypes.func,
  deleteTodo: PropTypes.func.isRequired,
  todoList: PropTypes.arrayOf(PropTypes.string),
};
Enter fullscreen mode Exit fullscreen mode

Let’s add it to our final app code :

import React, { useState } from "react";
import PropTypes from "prop-types";
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";

import "./App.css";

const sectionTypeTitle = {
  completed: "Completed",
  pending: "Pending",
};

function App() {
  const [typedInTodo, setTypedInTodo] = useState("");
  const [pendingTodos, setPendingTodos] = useState([]);
  const [completedTodos, setcompletedTodos] = useState([]);

  function completeTodo(todoIndex) {
    const pendingTask = pendingTodos[todoIndex];
    setcompletedTodos([...completedTodos, pendingTask]);
    deleteTodo(todoIndex, "pending");
  }

  function deleteTodo(todoIndex, todoSection) {
    const targetList =
      todoSection === "pending" ? pendingTodos : completedTodos;
    const setter =
      targetList === pendingTodos ? setPendingTodos : setcompletedTodos;
    const filteredTodos = targetList.filter((_, index) => todoIndex !== index);
    setter(filteredTodos);
  }

  function onKeyDown(e) {
    if (e.key === "Enter" && typedInTodo.trim()) {
      setPendingTodos([...pendingTodos, typedInTodo]);
      setTypedInTodo("");
    }
  }

  return (
    <div className="app">
      <h1 className="title">Todo</h1>
      <input
        type="text"
        placeholder="Add todo..."
        value={typedInTodo}
        onChange={(event) => setTypedInTodo(event.target.value)}
        onKeyDown={onKeyDown}
      />
      <div className="sectionsContainer">
        <TodoList
          sectionTitle="pending"
          completeTodo={completeTodo}
          deleteTodo={deleteTodo}
          todoList={pendingTodos}
        />
        <TodoList
          sectionTitle="completed"
          todoList={completedTodos}
          deleteTodo={deleteTodo}
        />
      </div>
    </div>
  );
}

export default App;

function TodoList({ sectionTitle, completeTodo, deleteTodo, todoList }) {
  return (
    <div className="todoContainer">
      <h2
        className={
          todoList.length > 0 ? "boldSectionTitle" : "dimmedSectiontTitle"
        }
      >
        {sectionTypeTitle[sectionTitle]}
      </h2>
      <div>
        {todoList.map((todo, index) => (
          <div className="todoItem" key={index}>
            <span>{todo}</span>
            <div className="buttonsSection">
              {sectionTitle === "pending" && (
                <button
                  className="transparent completeButton"
                  onClick={() => completeTodo(index)}
                >
                  <CheckOutlined className="icon" />
                </button>
              )}
              <button
                className="transparent deleteButton"
                onClick={() => deleteTodo(index, sectionTitle)}
              >
                <CloseOutlined className="icon" />
              </button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

TodoList.propTypes = {
  sectionTitle: PropTypes.oneOf(["pending", "completed"]).isRequired,
  completeTodo: PropTypes.func,
  deleteTodo: PropTypes.func.isRequired,
  todoList: PropTypes.arrayOf(PropTypes.string),
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

I publish articles monthly and I'm currently looking for my first Frontend Developer job in either Europe or Canada.

Stay tuned by following me on Twitter (@amir_ghezala) or checking my portfolio.

Top comments (0)