Create a Todo app using react fundamentals.

Create a Todo app using react fundamentals.

Nothing can ever be frustrating as learning a concept and being stuck when trying to reproduce. However, the best way to make sure the concept sticks is by effecting it!

Introduction

In this tutorial we will be building a todo application; this is a way to solidify some of the fundamental principles in react. At the end of this tutorial, we will be building a clone of the image below.

todolist image.PNG

Target Audience

This tutorial is aimed at those who have basic knowledge of React JS. If you are just getting started, check out the documentation reactJS , then you can come back to this tutorial when you have a basic grasp of it.

What will you learn?

It's not just about building a todo about but the things you get to learn at the end. Which are

React Hooks - They are a new addition in React 16.8. They let you use state and other React features without writing a class.

Styled components is a utility library that allows you to use tagged template literals to style your components. It allows you to define your styles in form of component.

Prerequisite

To complete this tutorial, you will need:

  • Node.js installed locally, which you can do by following the steps here
  • Styled components installed - you can do that by running npm install --save styled-components

  • Familiarity with React functional component will be beneficial. Please take a look at the React documentation here.

Before we continue, some acronyms or terms you might come across include

  • CD - Change Directory

  • NPM - Node package manager

  • && - AND

  • vs code - Visual Studio code (A text editor)

  • bootstrap - It simply means the initialization of a program

Getting Started

Open your terminal and run this command

npx create-react-app todo-app && cd todo-app && code .

Here, we are specifying 3 things

  • Create the react app
  • change the directory into the todo-app we are creating

  • Open with your code editor (Here I am assuming you are using vs code. If you are not, just run npx create-react-app todo-app && cd todo-app then open it with your preferred text editor.

You will see something like this on your text editor

├── README.md
├── node_modules
├── package.json
├── .gitignore
├── build
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── serviceWorker.js

Building the Todo Application

After your project has been bootstrapped, run npm start to start the project. Now go to the following URL localhost:3000 in your browser. Then this screen should pop up.

welcome-to-react.png

To be more explicit, we will need 3 components for this app. Which are

  1. TextField - This is where the user inputs their todo

  2. Button - This triggers the submit of the user input.

  3. Card - This where the inputs are being populated after submission.

Go back to your project directory, navigate into the src folder then create a components folder. Create three more folders for all our components which are TextField, Button, Card Like so

srcDirectory.PNG

As you can see I created an index.js file in every components folder, it is my preference, you can decide to leave yours as index.jsx either way works fine.

Building the TextField component

import React, { Fragment } from "react";
import styled from "styled-components";

const Input = styled.input`
  height: 50px;
  width: 100%;
  padding-left: 20px;
  font-size: 15px;
  border: 1px solid #ebeaeb;
  transition: 300ms all ease-in-out;
  box-shadow: 2px 2px 10px 1px rgba(0, 0, 0, 0.31);
  background: white;
  margin-bottom: 20px;

  &:focus {
    border: 1px solid#eee;
    font-size: 17px;
  }
`;
function TextField({ type, handleOnChange, placeholder, value }) {
    return (
      <Fragment>
        <Input
          type={type}
          onChange={handleOnChange}
          placeholder={placeholder}
          value={value}
        />
      </Fragment>
    );
}

export default TextField;

Here, I imported Fragment from react. According to React docs A common pattern in React is for a component to return multiple elements. Fragments let you group a list of children without adding extra nodes to the DOM. So I don't need to wrap my dom elements in extra <div> tag

Also, I imported styled from 'styled-components- This allows us to have access to the styled component library.

({ type, handleOnChange, placeholder, value }) What I'm doing here is just passing a props but in a destructured way. Instead of having something like props.type, props.handleOnChange and so on, I just destructured them to be more explicit. here's a link to destructuring if you need to recap.

Building the Button component

import React, { Fragment } from "react";
import styled from "styled-components";

const Btn = styled.button`
  position: relative;
  width: 150px;
  height: 40px;
  border: 1px solid #999999;
  background: white;
  margin-bottom: 20px;
  color: #8b7575;
  cursor: pointer;

  &:hover::before {
    content: "";
    position: absolute;
    top: 10px;
    left: 10px;
    background: rgba(0, 0, 0, 0.2);
    width: 100%;
    height: 100%;
    z-index: -1;
  }
`;
function Button({ children, type }) {
  return (
    <Fragment>
      <Btn type={type}>{children}</Btn>
    </Fragment>
  );
}

export default Button;

I was only repeating the process just for a button component, Here I am making it more dynamic where I used type So it can be any type where It's going to be used.

Building the Card component

Don't forget, this is where our inputs will be populated after the user submits their input

import React, { Fragment } from "react";
import styled from "styled-components";

const CardList = styled.li`
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
  list-style-type: none;
  padding: 10px;
  width: 100%;
  background: white;
  border: 1px solid #ddcfcf;
  border-radius: 4px;
  font-size: 20px;
  word-break: break-all;
`;

function Card({ children, handleDelete }) {
  return (
    <Fragment>
      <CardList>
        {children}
        <span
          style={{ cursor: "pointer" }}
          onClick={handleDelete}
          role="img"
          aria-label="delete"
        ></span>
      </CardList>
    </Fragment>
  );
}

export default Card;

Here, I am doing two things which are basically

  • Populate the card

  • Add a delete button in case the user wants to delete the input

So I passed a onClick function as a prop with the name handleDelete, I also added role="img" and aria-label="delete" Just a little bit of accessibility. If we don't add that it's going to throw an error in the console, even though our app will not crash we still have to make it a priority because we are building our products for human rather than machines.


Now that we are done with creating our components, Let's go on to finish up our app in App.js

So, Navigate to App.js directory and write some code.

import React, { Fragment, useState } from "react";
import styled from "styled-components";
import TextField from "./components/TextField";
import Card from "./components/Card";
import Button from "./components/Button";

I am breaking this section down so that we will not be too overwhelmed, cause there's a lot going on.

What we are doing here is very simple, we are only importing styled-components and all of our components into App.js file. Also, we are importing useState from react to allow us to use state in a functional component.

Add styling

const AppContainer = styled.div`
  position: relative;
  display: flex;
  flex-flow: column wrap;
  min-height: 100vh;
  max-width: 100vw;
  justify-content: center;
  align-items: center;
`;

const Container = styled.div`
  width: 50%;
  height: auto;

  @media screen and (max-width: 992px) {
    width: 90%;
  }
`;

const Heading = styled.h1`
  position: relative;
`;

Very simple stuff, but let me explain the Container Styled component, we are nesting a media query for the width to be 90% on the screen size that is less than 992px.

Note that, this kind of nesting is only possible in sass, less or styled component, It won't work in regular CSS.

Add functionality

function App() {
  let [value, setValue] = useState("");
  const [todo, setTodo] = useState([]);

  function handleSubmit(e) {
    e.preventDefault();
    // Check if the input field is empty
    if (value === "") {
      return false;
    }
    // Push values into the todo array []
    setTodo([...todo, value]);

    // Clear the input value
    setValue("");
  }

  function handleDelete(value) {
    const newList = todo.filter(item => item !== value)
    setTodo(newList);
  }
}

Here, the first thing I am doing is e.preventDefault() which means to prevent the browser from reloading, Also, I am setting the initial state of value to be an empty string, Lastly, I set the initial state for todo to be an empty array so that we can push values as much as we want.

Moving forward to the handleSubmit function let me break it down #

  • First thing here is I am checking if the value is empty then return false; meaning don't perform any action
 setTodo([...todo, value]);

What I am doing here is make a copy of the todo array then push the value to it. There are numerous ways to achieve this, but this is the one I prefer. If you have other preference you can drop it in the comment session below. Also, notice where I am using ...todo; this is an ES6 syntax that allows us to make a copy of our array.

You can read more on a spread operator here

Then finally set the input value to empty after all the action has been performed

setValue("");

Finally for the handleDelete function, according to the name we want to perform a delete action there.
The Breakdown

function handleDelete(value) {
    const newList = todo.filter(item => item !== value)
    setTodo(newList);
  }

What I am basically doing here is filter out the todo array and check if the item is not equalled to the value which I am passing in from the function. So this value will be a parameter that the function can receive. Don't worry we will see it in action soon. Note: filter is not in any way a react superpower but rather a javascript array method. You can read more on filter here

Concluding the App component

  return (
    <Fragment>
      <AppContainer>
        <Container>
          <Heading> TODOList.</Heading>
          <form onSubmit={handleSubmit}>
            <TextField
              type="text"
              placeholder="What is your main focus today?"
              value={value}
              handleOnChange={(e) => setValue(e.target.value)}
            />
            {
              // Show the button if the input value is populated
              value !== "" ? <Button type="submit">ADD</Button> : ""
            }
          </form>
          {
            // Display all the card component
            todo.length !== 0
              ? todo.map((item, index) => (
                  <Card key={index} handleDelete={() => handleDelete(item)}>
                    {item}
                  </Card>
                ))
              : null
          }
        </Container>
      </AppContainer>
    </Fragment>
  );

Here, I am attaching a onSubmit to the handleSubmit function to the input this works in case the user uses the enter key to submit.

On the TextField component I am getting the value assigning the handleOnChange</> that is being passed a prop to the the setValue(e.target.value) function. This is to get the current value the user enters. Then I am signing the {value} to the value attribute

Also, I am setting a conditional button to render if the input has been populated and vice versa if it wasn't.

Now to display all the todo items in the card component, I used a ternary operator to check if the it is not empty then map through the todo array. Here, don't be confused be 2 handleDelete that I have. The handleDelete on the card is an onClick prop that was passed from the component then the other handleDelete function is the function to perform the filtering and remove the item.. As you can see I am passing the item (value input) as a parameter to delete. Then lastly display the item in the card.

Output

Save your app. Navigate to localhost:3000 on your browser, you should be able to achieve this

working now.PNG

working now output.PNG

You have come a long way!

Thank you for seeing this through to the end. I believe you have learnt a lot from the little I could explain. Never forget to keep building!