Before the journey begins. (Part 3)

A "simple" TODO list.

Before the journey begins. (Part 3)

Now that we have the graphic draft, it's time to "breathe life" into our app by starting to divide the components and inserting a first interaction between them so we have a working list.

Before starting a note: all the code of part 2 you can find in this github repository.

git clone -b v1.0 https://github.com/MaxLau88/fsd-todo-app.git

Let's start by refactoring the components!

The goal is to have each component divided into its own file. Let's create a directory in "src" and name it "components" . Here all components will go:

  • TodoListTitle.js
  • EntryInput.js
  • ListFilters.js
  • EntriesList.js
  • EntryItem.js

Nothing more simple but necessary for the order that will become indispensable.

It's time to make sense of each component!

Wanting to start adding interaction to this draft graphic, let's imagine what we want it to do. It's a classic TODO list, so we should be able to:

  • Adding an element via text input
  • Mark as completed or not an item in the list
  • Delete an item from the list
  • Modifying a list item

But first things first: where do we get this list? This is where one of the cornerstones of ReactJs, the "useState" hook, comes into play.

What is a hook and why to use it. Essentially a hook is a function that allows us to "anchor" to the states and lifecycle of React (since, as we know, react renders each component every time the state of the application changes; if we used simple variables, these would be "reset" at each render cycle).

In particular "useState" allows us to have an internal state of the component that will be stable and will not change at render time if not for our action. The useState hook accepts an argument (representing the internal state) and returns an array with two elements, the internal state (immutable) and a function to change the state.

import {useState} from "react";

We open App.js and in our "App" component we use the hook to create us "list" and "setList", the list and the function to edit it.

const [list, setList] = useState([])

"list" will be an array of objects and each object will have a "title" index for the description of the item, a "taken" index to mark it as completed and an "archived" index to mark it as deleted. We could initialize it with the same ones we used for the sketch.

const [list, setList] = useState([
    {
        title: "Beautiful UI",
        taken: true,
        archived: false,
    },
    {
        title: "Fantastic Library",
        taken: false,
        archived: false,
    },
    {
        title: "Awesome Instrument",
        taken: true,
        archived: false,
    },
]);

We can now pass it to the component that manages the list, as a property of the component, after adapting the component to print the list it will receive.

Let's open the EntriesList.js file and as first thing let it accept the list specifying that the function will receive an object with an index "list" (by default an empty array).

export const EntriesList = ({list = []})

At this point we remove all instances of EntryItem and perform a loop on list that will return for each element an EntryItem with the properties of the element itself.

map(list,
    ({archived, taken, title}, index) =>
        (
            !archived &&
            <EntryItem
                key={index}
                title={title}
                completed={taken}
            />
        )
)

We use as key the position of the element so that we have the uniqueness of the key. We just have to go back to the App component and pass the list to the EntriesList instance.

<EntriesList list={list}/>

Now that the list is fully viewable, it's time to give us the ability to add new items. Let's create our EntryInput component. This has only one purpose: to return the new element to be inserted; for this purpose we only need to pass one property: a function that accepts the element to be inserted, let's call it "handleNewElementInput".

export const EntryInput = ({handleNewElementInput})

Before we move on to using this property we need to modify the component slightly:

  • Let's put the whole component inside a form
  • We give to the key the type "submit"
  • We create a reference to the input so that we can extract the inserted text
  • We recall handleNewElementInput to the submit of the form

Once the first two points are done, the third one aims to "capture" the textual input: this is where another hook, useRef, comes in. This is useful when you want to have a reference to a DOM node.

After creating the reference:

const textInputRef = useRef();

We are going to assign it to the input via the "ref" property. In this way we will be able to recall the node via the " current" property of the created reference and thus its value.

textInputRef.current.value

This will allow us to pass the value of the input to the handleNewElementInput function and clean up the input to prepare it for a new element.

const submitElement = e => {
    e.preventDefault();
    handleNewElementInput(textInputRef.current.value);
    textInputRef.current.value = '';
};

Let's go back to the App component where we are going to create the function that will receive the item from the input and insert it as a new item in the list.

const insertNewListElement = text => {
    setList([
        ...list,
        {
            title: text,
            taken: false,
            archived: false
        }
    ]);
}

With that done we have taken another step towards the beginning of this journey.

In part 4 we will conclude the list operations by adding editing, deleting, filtering and the ability to mark items as "taken".

As we go along, we will be able to delve more deeply into the concepts of hooks and functional components.

To download entire code use the v2.0 tag of repository.

git clone -b v2.0 https://github.com/MaxLau88/fsd-todo-app.git

Between functional components and class components which do you prefer or which would you recommend?

See you next time!