testing-react-hooks-with-enzyme-and-react-testing-library

As you begin to make use of React hooks in your applications, you’ll want to be certain the code you write is nothing short of solid. There’s nothing like shipping buggy code. One way to be certain your code is bug-free is to write tests. And testing React hooks is not much different from how React applications are tested in general.

In this tutorial, we will look at how to do that by making use of a to-do application built with hooks. We’ll cover writing of tests using Ezyme and React Testing Library, both of which are able to do just that. If you’re new to Enzyme, we actually posted about it a little while back showing how it can be used with Jest in React applications. It’s not a bad idea to check that as we dig into testing React hooks.

Here’s what we want to test

A pretty standard to-do component looks something like this:

import React, { useState, useRef } from "react";
const Todo = () => {
  const [todos, setTodos] = useState([
    { id: 1, item: "Fix bugs" },
    { id: 2, item: "Take out the trash" }
  ]);
  const todoRef = useRef();
  const removeTodo = id => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
  const addTodo = data => {
    let id = todos.length   1;
    setTodos([
      ...todos,
      {
        id,
        item: data
      }
    ]);
  };
  const handleNewTodo = e => {
    e.preventDefault();
    const item = todoRef.current;
    addTodo(item.value);
    item.value = "";
  };
  return (
    

Add Todo

Lists

{!todos.length ? (
No task!
) : (
    {todos.map(todo => { return (
  • {todo.item}
  • ); })}
)}
); }; export default Todo;

Testing with Enzyme

We need to install the packages before we can start testing. Time to fire up the terminal!

npm install --save-dev enzyme enzyme-adapter-16 

Inside the src directory, create a file called setupTests.js. This is what we’ll use to configure Enzyme’s adapter.

import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() }); 

Now we can start writing our tests! We want to test four things:

  1. That the component renders
  2. That the initial to-dos get displayed when it renders
  3. That we can create a new to-do and get back three others
  4. That we can delete one of the initial to-dos and have only one to-do left

In your src directory, create a folder called __tests__ and create the file where you’ll write your Todo component’s tests in it. Let’s call that file Todo.test.js.

With that done, we can import the packages we need and create a describe block where we’ll fill in our tests.

import React from "react";
import { shallow, mount } from "enzyme";
import Todo from "../Todo";

describe("Todo", () => {
  // Tests will go here using `it` blocks
});

Test 1: The component renders

For this, we’ll make use of shallow render. Shallow rendering allows us to check if the render method of the component gets called — that’s what we want to confirm here because that’s the proof we need that the component renders.

it("renders", () => {
  shallow();
});

Test 2: Initial to-dos get displayed

Here is where we’ll make use of the mount method, which allows us to go deeper than what shallow gives us. That way, we can check the length of the to-do items.

it("displays initial to-dos", () => {
  const wrapper = mount();
  expect(wrapper.find("li")).toHaveLength(2);
});

Test 3: We can create a new to-do and get back three others

Let’s think about the process involved in creating a new to-do:

  1. The user enters a value into the input field.
  2. The user clicks the submit button.
  3. We get a total of three to-do items, where the third is the newly created one.
it("adds a new item", () => {
  const wrapper = mount();
  wrapper.find("input").instance().value = "Fix failing test";
  expect(wrapper.find("input").instance().value).toEqual("Fix failing test");
  wrapper.find('[type="submit"]').simulate("click");
  expect(wrapper.find("li")).toHaveLength(3);
  expect(
    wrapper
      .find("li div span")
      .last()
      .text()
  ).toEqual("Fix failing test");
});

We mount the component then we make use of find() and instance() methods to set the value of the input field. We assert that the value of the input field is set to “Fix failing test” before going further to simulate a click event, which should add the new item to the to-do list.

We finally assert that we have three items on the list and that the third item is equal to the one we created.

Test 4: We can delete one of the initial to-dos and have only one to-do left

it("removes an item", () => {
  const wrapper = mount();
  wrapper
    .find("li button")
    .first()
    .simulate("click");
  expect(wrapper.find("li")).toHaveLength(1);
  expect(wrapper.find("li span").map(item => item.text())).toEqual([
    "Take out the trash"
  ]);
});

In this scenario, we return the to-do with a simulated click event on the first item. It’s expected that this will call the removeTodo() method, which should delete the item that was clicked. Then we’re checking the numbers of items we have, and the value of the one that gets returned.

The source code for these four tests are here on GitHub for you to check out.

Testing With react-testing-library

We’ll write three tests for this:

  1. That the initial to-do renders
  2. That we can add a new to-do
  3. That we can delete a to-do

Let’s start by installing the packages we need:

npm install --save-dev @testing-library/jest-dom @testing-library/react

Next, we can import the packages and files:

import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Todo from "../Todo";
import "@testing-library/jest-dom/extend-expect";

test("Todo", () => {
  // Tests go here
}

Test 1: The initial to-do renders

We’ll write our tests in a test block. The first test will look like this:

it("displays initial to-dos", () => {
  const { getByTestId } = render();
  const todos = getByTestId("todos");
  expect(todos.children.length).toBe(2);
});

What’s happening here? We’re making use of getTestId to return the node of the element where data-testid matches the one that was passed to the method. That’s the

    element in this case. Then, we’re checking that it has a total of two children (each child being a

  • element inside the unordered list). This will pass as the initial to-do is equal to two.

    Test 2: We can add a new to-do

    We’re also making use of getTestById here to return the node that matches the argument we’re passing in.

    it("adds a new to-do", () => {
      const { getByTestId, getByText } = render();
      const input = getByTestId("input");
      const todos = getByTestId("todos");
      input.value = "Fix failing tests";
      fireEvent.click(getByText("Add Task"));
      expect(todos.children.length).toBe(3);
    });

    We use getByTestId to return the input field and the ul element like we did before. To simulate a click event that adds a new to-do item, we’re using fireEvent.click() and passing in the getByText() method, which returns the node whose text matches the argument we passed. From there, we can then check to see the length of the to-dos by checking the length of the children array.

    Test 3: We can delete a to-do

    This will look a little like what we did a little earlier:

    it("deletes a to-do", () => {
      const { getAllByTestId, getByTestId } = render();
      const todos = getByTestId("todos");
      const deleteButton = getAllByTestId("delete-button");
      const first = deleteButton[0];
      fireEvent.click(first);
      expect(todos.children.length).toBe(1);
    });

    We’re making use of getAllByTestId to return the nodes of the delete button. Since we only want to delete one item, we fire a click event on the first item in the collection, which should delete the first to-do. This should then make the length of todos children equal to one.

    These tests are also available on GitHub.

    Linting

    There are two lint rules to abide by when working with hooks:

    Rule 1: Call hooks at the top level

    …as opposed to inside conditionals, loops or nested functions.

    // Don't do this!
    if (Math.random() > 0.5) {
      const [invalid, updateInvalid] = useState(false);
    }

    This goes against the first rule. According to the official documentation, React depends on the order in which hooks are called to associate state and the corresponding useState call. This code breaks the order as the hook will only be called if the conditions are true.

    This also applies to useEffect and other hooks. Check out the documentation for more details.

    Rule 2: Call hooks from React functional components

    Hooks are meant to be used in React functional components — not in React’s class component or a JavaScript function.

    We’ve basically covered what not to do when it comes to linting. We can avoid these missteps with an npm package that specifically enforces these rules.

    npm install eslint-plugin-react-hooks --save-dev

    Here’s what we add to the package’s configuration file to make it do its thing:

    {
      "plugins": [
        // ...
        "react-hooks"
      ],
      "rules": {
        // ...
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn"
      }
    }

    If you are making use of Create React App, then you should know that the package supports the lint plugin out of the box as of v3.0.0.

    Go forth and write solid React code!

    React hooks are equally prone to error as anything else in your application and you’re gonna want to ensure that you use them well. As we just saw, there’s a couple of ways we can go about it. Whether you use Enzyme or You can either make use of enzyme or React Testing Library to write tests is totally up to you. Either way, try making use of linting as you go, and no doubt, you’ll be glad you did.

build-your-own-react

function createElement(type, props, ...children) {

children: children.map(child =>

typeof child === "object"

: createTextElement(child)

function createTextElement(text) {

function createDom(fiber) {

fiber.type == "TEXT_ELEMENT"

? document.createTextNode("")

: document.createElement(fiber.type)

updateDom(dom, {}, fiber.props)

const isEvent = key => key.startsWith("on")

const isProperty = key =>

key !== "children" && !isEvent(key)

const isNew = (prev, next) => key =>

const isGone = (prev, next) => key => !(key in next)

function updateDom(dom, prevProps, nextProps) {

isNew(prevProps, nextProps)(key)

.filter(isGone(prevProps, nextProps))

.filter(isNew(prevProps, nextProps))

dom[name] = nextProps[name]

.filter(isNew(prevProps, nextProps))

deletions.forEach(commitWork)

commitWork(wipRoot.child)

function commitWork(fiber) {

const domParent = fiber.parent.dom

fiber.effectTag === "PLACEMENT" &&

domParent.appendChild(fiber.dom)

fiber.effectTag === "UPDATE" &&

} else if (fiber.effectTag === "DELETION") {

domParent.removeChild(fiber.dom)

commitWork(fiber.sibling)

function render(element, container) {

let nextUnitOfWork = null

function workLoop(deadline) {

while (nextUnitOfWork && !shouldYield) {

nextUnitOfWork = performUnitOfWork(

shouldYield = deadline.timeRemaining() < 1

if (!nextUnitOfWork && wipRoot) {

requestIdleCallback(workLoop)

requestIdleCallback(workLoop)

function performUnitOfWork(fiber) {

fiber.dom = createDom(fiber)

const elements = fiber.props.children

reconcileChildren(fiber, elements)

nextFiber = nextFiber.parent

function reconcileChildren(wipFiber, elements) {

wipFiber.alternate && wipFiber.alternate.child

index < elements.length ||

const element = elements[index]

element.type == oldFiber.type

if (element && !sameType) {

if (oldFiber && !sameType) {

oldFiber.effectTag = "DELETION"

oldFiber = oldFiber.sibling

wipFiber.child = newFiber

prevSibling.sibling = newFiber

return <h1>Hi {props.name}h1>

const element = <App name="foo" />

const container = document.getElementById("root")

Didact.render(element, container)

how-to-build-a-todo-list-app-with-react-hooks-and-typescript

The best way to learn something is by doing. This tutorial will help you learn how to build your own todo list app with React hooks and TypeScript. Try this easy tutorial, build your own todo list app, and get better in JavaScript, React and TypeScript.

You can find the code on my GitHub.

Briefing

The goal for this tutorial is to build your own todo list app. About the app in general. This todo list app will have very simple interface and it will focus on the most important features, i.e. create, check off and delete todos. About code. You will use React and React hooks, mostly useState hook.

There will be one occasion where you will also use useRef hook. Since this todo list app will utilize React hooks for managing state there is no need to use class components. So, you will build this app only with functional components. When it comes to styling your todo list app, you will use external CSS stylesheets.

One last things. First every todo item will have a unique id. These ids will be generated when the todo item is created. You will use this id to mark the todo as complete or to remove it. To make this easier, while following good practices and avoiding using indexes, you will use shortid package.

Project setup

As the first thing let’s create the basic app for your todo list app. We can do this very fast with the help of create-react-app. You can use this package with npm init react-app react-hooks-todo-list-app-ts --typescript, npx create-react-app react-hooks-todo-list-app-ts --typescript or yarn create react-app react-hooks-todo-list-app-ts --typescript. If you don’t want to use TypeScript, omit the --typescript flag at the end of the command.

These commands will create starting template for your todo list app, with workflow setup and almost all necessary dependencies. There is one dependency you will need to install manually, the shortid and types for this package. So, use npm i shortid and npm i -D @types/shortid, yarn add shortid and yarn add -D @types/shortid or pnpm i shortid and pnpm i -D @types/shortid.

There are some assets, such as React logo, that came with the app template. You can remove it because you will not need it. A very simple version of your package.json should look similar to this:

{
  "name": "react-todo-list-hooks-ts",
  "version": "1.0.0",
  "description": "Simple Todo list app built with React hooks and TypeScript.",
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ],
  "main": "src/index.tsx",
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "dependencies": {
    "react": "16.11.0",
    "react-dom": "16.11.0",
    "shortid": "2.2.15"
  },
  "devDependencies": {
    "@types/react": "16.9.11",
    "@types/react-dom": "16.9.4",
    "@types/shortid": "^0.0.29",
    "react-scripts": "3.2.0",
    "typescript": "3.7.2"
  }
}

If you decide to use TypeScript, your tsconfig should look similar to this:

{
    "include": [
        "./src/*"
    ],
    "compilerOptions": {
        "lib": [
            "dom",
            "es2015"
        ],
        "jsx": "react",
        "target": "es5",
        "allowJs": true,
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true
    }
}

As the last thing, below is the final structure of this todo list app project. You can use this as you work on this tutorial to orient yourself. With that, you are ready to start working on your todo list app.

react-meme-generator-ts/
├─node_modules
├─public
│ ├─favicon.ico
│ ├─index.html
│ ├─manifest.json
│ └─robots.txt
├─src
│ ├─components
│ │ ├─todo-form.tsx
│ │ ├─todo-item.tsx
│ │ └─todo-list.tsx
│ ├─styles
│ │ └─styles.css
│ ├─index.tsx
│ ├─interfaces.ts
│ └─react-app-env.d.ts
├─ package.json
└─ tsconfig.json

Interfaces

The first thing to do is create interfaces for your todo list app. You will use them to define the shape of component props and the todo object, or to type them. If you decided to use pure JavaScript, instead of TypeScript, you can skip this step. You will need to create four interfaces.

One for todo (todo object), one for todo form one for todo list and one for todo item. The todo object will have three properties, id, text, isCompleted. The TodoForm props contain array of todo objects and handleTodoCreate method. The TodoList props will contain handleTodoUpdate, handleTodoRemove, handleTodoComplete and handleTodoBlur methods and array of todo objects.

The TodoItem props will contain handleTodoUpdate, handleTodoRemove, handleTodoComplete, handleTodoBlur and a single todo object.

// Todo interface
export interface TodoInterface {
  id: string;
  text: string;
  isCompleted: boolean;
}

// Todo form interface
export interface TodoFormInterface {
  todos: TodoInterface[];
  handleTodoCreate: (todo: TodoInterface) => void;
}

// Todo list interface
export interface TodoListInterface {
  handleTodoUpdate: (event: React.ChangeEvent, id: string) => void;
  handleTodoRemove: (id: string) => void;
  handleTodoComplete: (id: string) => void;
  handleTodoBlur: (event: React.ChangeEvent) => void;
  todos: TodoInterface[]
}

// Todo item interface
export interface TodoItemInterface {
  handleTodoUpdate: (event: React.ChangeEvent, id: string) => void;
  handleTodoRemove: (id: string) => void;
  handleTodoComplete: (id: string) => void;
  handleTodoBlur: (event: React.ChangeEvent) => void;
  todo: TodoInterface;
}

Todo item component

The first component you will build will be todo item. When you add new todo on your todo list, this item component will represent it. This component will be composed of a couple of elements. First, there will be a div with span elements for checking off the todo. Unchecked item will contain empty span, styled into a transparent circle with border.

Checked off todo item will contain span with check mark HTML entity, inside a green circle. The wrapper div will have onClick handler to check/uncheck the todo. Next will be another div with input. You will use this input element to render the title, or the text, of the todo. This is the simplest way to make every todo item editable, through input elements.

You will pass the title be done through value attribute, from todo object passed through props. Aside to this, this input will have two handler methods, one for onBlur and one for onChange. The last element will be also a div, now with “x” entity/icon. You will use this element to remove the todo item.

This div will have one onClick handler. As all the previous data, and handler methods, this too will be passed thorough props.

If you use TypeScript, import the TodoItemInterface interface from interfaces.ts and to use it to type props of this component. After this, type the onChange handler on input element with React.ChangeEvent because we are attaching onChange handler to input element.

// Import dependencies
import * as React from 'react'

// Import interfaces
import { TodoItemInterface } from './../interfaces'

// TodoItem component
const TodoItem = (props: TodoItemInterface) => {
  return (
    
props.handleTodoComplete(props.todo.id)}> {props.todo.isCompleted ? ( ) : ( )}
) => props.handleTodoUpdate(event, props.todo.id)} />
props.handleTodoRemove(props.todo.id)}> ⨯
) } export default TodoItem

Todo list component

The todo list will be the second component you will create. This component will be very simple. This component will accept handler methods for the TodoItem, you’ve just created, and array of todo objects through props. The component itself will contain one div as a wrapper element.

Inside this div will be a list, one ul element. Inside this element, you will use map() to iterate over the array of todo objects, and create one li element with one TodoItem component for every todo object. You will then pass the individually todo objects to the TodoItem component, along with handler methods.

For TypeScript, remember to import TodoListInterface interface and use it to type the props of the TodoList component.

// Import dependencies
import * as React from 'react'

// Import TodoItem
import TodoItem from './todo-item'

// Import interfaces
import { TodoListInterface } from './../interfaces'

// TodoList component
const TodoList = (props: TodoListInterface) => {
  return (
    
    {props.todos.map((todo) => (
  • ))}
) } export default TodoList

Todo form component

The todo “form” will is the first component where you will use useState React hook. It is also here where you will use the useRef React hook. You will use the useState hook to store the text passed to the input element, text for the todo title before you will create new todo item.

You will use the useRef hook to store reference to this input. The way you create new todo is by pressing “Enter” key, while you type some text inside that input. So, when you press “Enter” key you will use this reference to reset the input, by setting the value to empty string. This input will also have two handler methods for onChange and onKeyPress.

These two handler methods will be handleInputChange and handleInputEnter. The first, for onChange, will update the form state when you write something into the input, some todo title/text. The second, for onKeyPress, will create new todo object and reset the input field when it detect pressing “Enter” key.

Do you remember the shortid package? It is here where you are going to use this dependency. Inside the handleInputEnter function, inside the new todo object, you will use shortid to generate unique id for every new todo. Don’t worry. This will be simple. All you need is to call generate() on shortid and your new id is ready.

Lastly, few things for TypeScript. First, import TodoInterface and TodoFormInterface interfaces. Then, use the TodoInterface interface to type the new todo object inside handleInputEnter, and TodoFormInterface interface to type the props of TodoForm. Then, type the useRef hook, using and set it to null.

After that, there are also two events. For the first one, you can type it with React.ChangeEvent because we are attaching onChange handler to input element. For the second, you can type it with React.KeyboardEvent because we are “listening” for key press.

// Import dependencies
import * as React from 'react'
import shortid from 'shortid'

// Import interfaces
import {TodoInterface, TodoFormInterface} from './../interfaces'

// Todo form component
const TodoForm = (props: TodoFormInterface) => {
  // Create ref for form input
  const inputRef = React.useRef(null)

  // Create form state
  const [formState, setFormState] = React.useState('')

  // Handle todo input change
  function handleInputChange(event: React.ChangeEvent) {
    // Update form state with the text from input
    setFormState(event.target.value)
  }

  // Handle 'Enter' in todo input
  function handleInputEnter(event: React.KeyboardEvent) {
    // Check for 'Enter' key
    if (event.key === 'Enter') {
      // Prepare new todo object
      const newTodo: TodoInterface = {
        id: shortid.generate(),
        text: formState,
        isCompleted: false
      }

      // Create new todo item
      props.handleTodoCreate(newTodo)

      // Reset the input field
      if (inputRef && inputRef.current) {
        inputRef.current.value = ''
      }
    }
  }

  return (
    
handleInputChange(event)} onKeyPress={event => handleInputEnter(event)} />
) } export default TodoForm

Main (index) component

You are almost done. There is just one component you need to build. This is the main TodoListApp component. This component will implement methods for creating, updating, removing and completing your todos. This will be done via handleTodoCreate, handleTodoUpdate, handleTodoRemove and handleTodoComplete methods.

It is also this component where you will store all existing todos, using the useState React hook. So, let’s build this component, step by step.

Imports

First, as usually, you will need to import dependencies for react. Now, you will also need to import render method from react-dom. This is because you will render the TodoListApp component, your todo list app, in the DOM.

You will also import TodoForm and TodoList components so you can later return, and render, them. When you import these components you should also import the main external CSS stylesheet, so you can later style your todo list app.

For TypeScript, you will need to import the TodoInterface interface. You will use this interface a couple of times, to type todos state and some method parameters.

// Import dependencies
import * as React from 'react'
import { render } from 'react-dom'

// Import components
import TodoForm from './components/todo-form'
import TodoList from './components/todo-list'

// Import interfaces
import { TodoInterface } from './interfaces'

// Import styles
import './styles/styles.css'

Creating todo list app state

The state fo your todo list app will be simple. It will be an array of objects. One object will represent one existing todo. In the beginning, you will initialize the todos state as an empty array.

For TypeScript, make sure to use the TodoInterface interface along with []. This will tell TypeScript you are “talking” about an array of todos objects, not just one todo object.

// TodoListApp component
// ....
const TodoListApp = () => {
  const [todos, setTodos] = React.useState([])
  // ...
}

Creating new todos

The first method for your todo list app will be method to create new todos, handleTodoCreate method. This method will accept one parameter, a todo object. The way it will work is simple. First, it will create new todo list app state, the newTodosState, by copying the current todo list app state.

Next, it will take the todo object, you pass as parameter when you call this method, and add that todo to the new todo list app state, the newTodosState, using push() method. After that, it will update the todo list app state, using setTodos() method.

About TypeScript. You will use the TodoInterface interface to type the todo parameter. You will also use this interface to type the newTodosState variable. In this case, you will again have specify you want an array of todo objects, adding [] after the TodoInterface.

  // ....
  // Creating new todo item
  function handleTodoCreate(todo: TodoInterface) {
    // Prepare new todos state
    const newTodosState: TodoInterface[] = [...todos]

    // Update new todos state
    newTodosState.push(todo)

    // Update todos state
    setTodos(newTodosState)
  }
  // ....

Updating existing todos

Next, you will need method to update existing todos, handleTodoUpdate method. This method will accept two parameters, event and id. The id will be unique id generated for every todo item/object. Similarly to handleTodoCreate, this method will also start by creating new todo list app state, newTodosState, by copying the current todo list app state.

Next, it will use find() method to iterate over the newTodosState variable and find the correct todo item to update, using the id passed as argument. When it finds the correct todo item/object, it will change the value of its text key. New value will come from the value of the input inside specific todo item.

The last step is updating the todo list app state, using newTodosState and setTodos() method.

For TypeScript, use the TodoInterface interface to type the todo parameter passed to find() method. Use it also for the newTodosState variable, along with [] after the TodoInterface. Lastly, type the id parameter as a string.

  // ....
  // Update existing todo item
  function handleTodoUpdate(event: React.ChangeEvent, id: string) {
    // Prepare new todos state
    const newTodosState: TodoInterface[] = [...todos]

    // Find correct todo item to update
    newTodosState.find((todo: TodoInterface) => todo.id === id)!.text = event.target.value

    // Update todos state
    setTodos(newTodosState)
  }
  // ....

Removing existing todos

Removing todos will be done using filter() method. First, you will create new todo list app state, newTodosState, by copying the current todo list app state. During this, you will use the filter() method to remove the todo you want to remove. This will be done by comparing id of all todos with the id of todo you want to remove.

When this is done, you will use this new, filtered, state to update the todos state with the setTodos() method.

For TypeScript, use the TodoInterface interface to type the todo parameter passed to filter() method. Then, use it also for the newTodosState variable, along with [] after the TodoInterface. Finally, type the id parameter as a string.

  // ....
  // Remove existing todo item
  function handleTodoRemove(id: string) {
    // Prepare new todos state
    const newTodosState: TodoInterface[] = todos.filter((todo: TodoInterface) => todo.id !== id)

    // Update todos state
    setTodos(newTodosState)
  }
  // ....

Completing todos

The method for completing todos will look very similar to handleTodoUpdate method. First, it will copy the current todo list app state and store it in newTodosState variable. Then, it will use find() method to find specific todo item/object in todos state.

This time, it will negate the value of isCompleted key of the specific todo item/object. After this, it will use the setTodos method to update todos state.

Now, about TypeScript. First, use the TodoInterface interface to type the todo parameter passed to find() method. Next, use this interface also for the newTodosState variable, again with [] after the TodoInterface. The last type will be for the id. This will be a string.

  // ....
  // Check existing todo item as completed
  function handleTodoComplete(id: string) {
    // Copy current todos state
    const newTodosState: TodoInterface[] = [...todos]

    // Find the correct todo item and update its 'isCompleted' key
    newTodosState.find((todo: TodoInterface) => todo.id === id)!.isCompleted = !newTodosState.find((todo: TodoInterface) => todo.id === id)!.isCompleted

    // Update todos state
    setTodos(newTodosState)
  }
  // ....

Ensuring every todo has title

The last thing. When you edit existing todo there should be some warning if you leave the text/title empty. To get this done you can watch change on input element inside every todo. Then, you can check its value is not an empty string, the length of the value is bigger than “0”.

If there is an empty string, you will add specific CSS class. When you input some text, you will remove that CSS class. This CSS class will mark the input with red border. You will define this class in your CSS stylesheet later.

As usually, the TypeScript. This will be quick. All there is to type is the event passed as parameter. Since you are attaching a onChange event handler on input element, you can use React.ChangeEvent.

  // ....
  // Check if todo item has title
  function handleTodoBlur(event: React.ChangeEvent) {
    if (event.target.value.length === 0) {
      event.target.classList.add('todo-input-error')
    } else {
      event.target.classList.remove('todo-input-error')
    }
  }
  // ....

Returning all components

Your todo list app is almost finished. Now, you now need to take all the components you’ve built so far, and imported in component, and return them. Make sure to provide all components with necessary props. After that, you can use the render() method and render the TodoListApp in the DOM.

  // ...
  return (
    
{/* Todo form component */} {/* Todo list component */}
) } // Render the App in the DOM const rootElement = document.getElementById('root') render(, rootElement)

Putting it all together

You wrote a lot code in this main component. Let’s put it all together to make it more clear.

// Import dependencies
import * as React from 'react'
import { render } from 'react-dom'

// Import components
import TodoForm from './components/todo-form'
import TodoList from './components/todo-list'

// Import interfaces
import { TodoInterface } from './interfaces'

// Import styles
import './styles/styles.css'

// TodoListApp component
const TodoListApp = () => {
  const [todos, setTodos] = React.useState([])

  // Creating new todo item
  function handleTodoCreate(todo: TodoInterface) {
    // Prepare new todos state
    const newTodosState: TodoInterface[] = [...todos]

    // Update new todos state
    newTodosState.push(todo)

    // Update todos state
    setTodos(newTodosState)
  }

  // Update existing todo item
  function handleTodoUpdate(event: React.ChangeEvent, id: string) {
    // Prepare new todos state
    const newTodosState: TodoInterface[] = [...todos]

    // Find correct todo item to update
    newTodosState.find((todo: TodoInterface) => todo.id === id)!.text = event.target.value

    // Update todos state
    setTodos(newTodosState)
  }

  // Remove existing todo item
  function handleTodoRemove(id: string) {
    // Prepare new todos state
    const newTodosState: TodoInterface[] = todos.filter((todo: TodoInterface) => todo.id !== id)

    // Update todos state
    setTodos(newTodosState)
  }

  // Check existing todo item as completed
  function handleTodoComplete(id: string) {
    // Copy current todos state
    const newTodosState: TodoInterface[] = [...todos]

    // Find the correct todo item and update its 'isCompleted' key
    newTodosState.find((todo: TodoInterface) => todo.id === id)!.isCompleted = !newTodosState.find((todo: TodoInterface) => todo.id === id)!.isCompleted

    // Update todos state
    setTodos(newTodosState)
  }

  // Check if todo item has title
  function handleTodoBlur(event: React.ChangeEvent) {
    if (event.target.value.length === 0) {
      event.target.classList.add('todo-input-error')
    } else {
      event.target.classList.remove('todo-input-error')
    }
  }

  return (
    
) } const rootElement = document.getElementById('root') render(, rootElement)

Styles

Your todo list app is ready to go. Well, almost. There is a lot of space for some styling. Here are some styles you can use to make your todo list app look better.

/* Default styles*/
html {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

#root,
body {
  min-height: 100vh;
}

body {
  margin: 0;
}

#root,
.todo-list-app {
  display: flex;
  flex-flow: column nowrap;
}

#root {
  align-items: center;
  width: 100%;
}

/* Todo list app styles  */
.todo-list-app {
  padding-top: 32px;
  width: 100%;
  max-width: 480px;
}

/* Todo form styles */
.todo-form input,
.todo-item {
  border: 1px solid #ececec;
}

.todo-form input {
  padding: 0 14px;
  width: 100%;
  height: 48px;
  transition: .25s border ease-in-out;
}

.todo-form input:focus {
  outline: 0;
  border: 1px solid #3498db;
}

/* Todo list styles */
.todo-list ul {
  padding: 0;
  margin: 0;
}

.todo-list li {
  list-style-type: none;
}

/* Todo item styles */
.todo-item {
  display: flex;
  flex-flow: row nowrap;
  align-items: center;
  padding: 8px;
}

.todo-form   .todo-list ul .todo-item {
  border-top: 0;
}

.todo-item-input-wrapper {
  flex-grow: 1;
  padding: 0 16px;
}

.todo-item input {
  width: 100%;
  border: 0;
  border-bottom: 1px solid transparent;
  transition: .25s border-bottom ease-in-out;
}

.todo-item input:focus {
  outline: 0;
  border-bottom: 1px solid #3498db;
}

.todo-item .todo-input-error {
  border-bottom: 1px solid #e74c3c;
}

.todo-item span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid #ececec;
  transition: .25s all ease-in-out;
}

.todo-item-unchecked:hover {
  background: hsla(168, 76%, 42%, .25);
  border: 1px solid hsl(168, 76%, 42%, .25);
}

.todo-item-checked {
  color: #fff;
  background: #1abc9c;
  border: 1px solid #1abc9c;
}

.item-remove {
  display: flex;
  padding-left: 8px;
  padding-right: 8px;
  font-size: 28px;
  cursor: pointer;
  line-height: 1;
  color: #ececec;
  transition: .25s color ease-in-out;
}

.item-remove:hover {
  color: #111;
}

Conclusion: How to Build a Todo List App with React Hooks and TypeScript

Congratulations, you’ve just built your own todo list app using React hooks and TypeScript! However, you don’t have to stop here. So, go ahead. Take this todo list app and make it better. Think about what features you would like it to have. Then, don’t wait for anything. Try to implement them by yourself. Have fun!

If you liked this article, then please consider subscribing.

what’s-new-in-react-v16.9

I was starting to get a bit worried. The last few minor point releases of React had come out months apart and we were already into Q3 without a new release. My fears have been quelled though as React v16.9 is finally here with a few new features, bug fixes and deprecations!

The deprecations

I’m a “start with the bad news” type of guy, so let’s start by talking about the deprecations that were included in React v16.9 which will help us better prepare for v17 (and other future versions) down the road.

No more javascript: URLs

URLs that begin with javascript: have long been considered dangerous because of the potential of malicious data making it into your application:

const userProfile = {
  // Whoops, forgot to ensure this use input was /actually/ a URL
  websiteUrl: 'javascript: alert("Remember little bobby tables?")';
}

<a href={userProfile.websiteUrl}>My Website</a>

Obviously it wouldn’t be good if your users could inject JavaScript right into your running application.

As mentioned, this is simply a deprecation in v16.9 and will only log a warning. You could go out of your way to circumvent this entirely by using dangerouslySetInnerHTML but you’d still be asking for trouble.

In a future major version (not necessarily v17) this will be converted from a warning to an error. I’d say it’s best to just stop using it all together so you can save the trouble in the future.

No more “factory” components

A feature I can file in the “TIL” category, is that React had support for a “factory” component.

For those unfamiliar, the factory pattern is when you have a creator class handles creating new classes for you rather than instantiating them directly. This is great in scenarios where you are dynamically generating class names and such.

In React, this means you can have a component that returns an object with it’s own render method. It also has a pretty nasty syntax that looks an awful lot like a function component:

function GatorFactoryComponent() {
  return {
    render() {
      return <div />;
    }
  };
}

As mentioned, this was news to me, and evidently not that widely used of a feature as the React Team mentioned that this is rarely used and probably won’t affect the majority of modern code bases.

If you do attempt to use it, you will receive a warning. There’s also a work around, but you’re better off converting it to a function of class component instead of trying to stay set in your ways.

Unsafe lifecycle methods renamed

The React Team gave us a head’s up over a year ago that the lifecycle methods that were considered unsafe would be renamed in a future 16.x release and the old names removed completely from the 17.x series.

The methods in question are as follows:

  • componentWillMount renamed to UNSAFE_componentWillMount
  • componentWillReceiveProps renamed to UNSAFE_componentWillReceiveProps
  • componentWillUpdate renamed to UNSAFE_componentWillUpdate

Never fear though, the old names will still work in React v16.9 but will throw a helpful warning to urge you to refactor your code or move to the new UNSAFE_ naming.

The purpose of the UNSAFE_ naming convention is to make it easier for us developers to identify potentially problematic code when doing reviews and such.

To ease migration efforts, there is a “codemod” script that can update your entire project to the new UNSAFE_ naming convention:

$ npx react-codemod rename-unsafe-lifecycles

The features

While not necessarily a let down of a release, this particular React release is a bit light on new features. One man’s opinion though 🙂

Measuring performance with

You may remember back in React v16.5 when the React Profiler was added to the React DevTools. This is great for a lot of smaller applications, but for a larger application, you probably want a more programmatic way to gather metrics.

The solution to this comes in the way of the component which you can be used to wrap components to help gain insight to the rendering “cost” of your application.

The new component requires an id property as well as an onRender property which will fire every time a component within the tree updates and triggers a re-render:

import React, { Profiler } from 'react';

render(
  <Profiler id="alligator-app" onRender={onRender}>
    <AlligatorApp>
      <Switch>
        <Route {...props} />
        <Route {...props} />
        <Route {...props} />
      </Switch>
    </AlligatorApp>
  </Profiler>
);

The onRender callback will receive information about which component re-rendered, how long it took, and other valuable information. You can read more about the profiler here.

Also, the profiler is automatically disabled in production, so there’s no additional logic to implement to make sure you don’t accidentally deploy with the profiler turned on. (C’mon, we’ve all done it 😉

Asynchronous ReactTestUtils.act()

React v16.8 introduced the act() utility to help with testing your application as if it were being run in a web browser.

While extremely useful, the act() utility was synchronous with no easy way to get it work in a more asynchronous fashion. This may have resulted in some sporadic warnings in your logs which couldn’t be easily fixed. Quite the bummer.

Fortunately, the React Team has improved the act() utility in React v16.9 so it can function asynchronously!

The Rest

A release of React is never complete without some notable bug fixes:

  • Fix for a memory leak caused by deleted subtrees being retained.
  • Better handling of infinite loops caused by setState inside of useEffect.
  • Fix for a crash in when findDOMNode() is called.

Ready to start using React v16.9?

React v16.9 is ready for your hacking pleasure, all you need to do is install it via your favorite package manager:

# Via npm
$ npm install --save react@^16.9.0 react-dom@^16.9.0

# Via Yarn
$ yarn add react@^16.9.0 react-dom@^16.9.0
fetching-data-in-react-using-react-async

You’re probably used to fetching data in React using axios or fetch. The usual method of handling data fetching is to:

  • Make the API call.
  • Update state using the response if all goes as planned.
  • Or, in cases where errors are encountered, an error message is displayed to the user.

There will always be delays when handling requests over the network. That’s just part of the deal when it comes to making a request and waiting for a response. That’s why we often make use of a loading spinner to show the user that the expected response is loading.

See the Pen


ojRMaN
by Geoff Graham (@geoffgraham)


on CodePen.

All these can be done using a library called React Async.

React Async is a promised-based library that makes it possible for you to fetch data in your React application. Let’s look at various examples using components, hooks and helpers to see how we can implement loading states when making requests.

For this tutorial, we will be making use of Create React App. You can create a project by running:

npx create-react-app react-async-demo

When that is done, run the command to install React Async in your project, using yarn or npm:

## yarn
yarn add react-async

## npm
npm install react-async --save

Example 1: Loaders in components

The library allows us to make use of directly in our JSX. As such, the component example will look like this;

// Let's import React, our styles and React Async
import React from 'react';
import './App.css';
import Async from 'react-async';

// We'll request user data from this API
const loadUsers = () =>
  fetch("https://jsonplaceholder.typicode.com/users")
    .then(res => (res.ok ? res : Promise.reject(res)))
    .then(res => res.json())

// Our component
function App() {
  return (
    
{({ data, err, isLoading }) => { if (isLoading) return "Loading..." if (err) return `Something went wrong: ${err.message}` if (data) return (

React Async - Random Users

{data.map(user=> (

{user.name}

{user.email}

))}
) }}
); } export default App;

First, we created a function called loadUsers. This will make the API call using the fetch API. And, when it does, it returns a promise which gets resolved. After that, the needed props are made available to the component.

The props are:

  • isLoading: This handles cases where the response has not be received from the server yet.
  • err: For cases when an error is encountered. You can also rename this to error.
  • data: This is the expected data obtained from the server.

As you can see from the example, we return something to be displayed to the user dependent on the prop.

Example 2: Loaders in hooks

If you are a fan of hooks (as you should), there is a hook option available when working with React Async. Here’s how that looks:

// Let's import React, our styles and React Async
import React from 'react';
import './App.css';
import { useAsync } from 'react-async';

// Then we'll fetch user data from this API
const loadUsers = async () =>
  await fetch("https://jsonplaceholder.typicode.com/users")
    .then(res => (res.ok ? res : Promise.reject(res)))
    .then(res => res.json())

// Our component
function App() {
  const { data, error, isLoading } = useAsync({ promiseFn: loadUsers })
  if (isLoading) return "Loading..."
  if (error) return `Something went wrong: ${error.message}`
  if (data)
  
  // The rendered component
  return (
    

React Async - Random Users

{data.map(user=> (

{user.name}

{user.email}

))}
); } export default App;

This looks similar to the component example, but in this scenario, we’re making use of useAsync and not the Async component. The response returns a promise which gets resolved, and we also have access to similar props like we did in the last example, with which we can then return to the rendered UI.

Example 3: Loaders in helpers

Helper components come in handy in making our code clear and readable. These helpers can be used when working with an useAsync hook or with an Async component, both of which we just looked at. Here is an example of using the helpers with the Async component.

// Let's import React, our styles and React Async
import React from 'react';
import './App.css';
import Async from 'react-async';

// This is the API we'll use to request user data
const loadUsers = () =>
  fetch("https://jsonplaceholder.typicode.com/users")
    .then(res => (res.ok ? res : Promise.reject(res)))
    .then(res => res.json())

// Our App component
function App() {
  return (
    
Loading... {data => { return (

React Async - Random Users

{data.map(user=> (

{user.name}

{user.email}

))}
) }}
{error => `Something went wrong: ${error.message}`}
); } export default App;

This looks similar to when we were making use of props. With that done, you could break the different section of the app into tiny components.

Conclusion

If you have been growing weary of going the route I mentioned in the opening section of this tutorial, you can start making of React Async in that project you are working on. The source code used in this tutorial can be found in their different branches on GitHub.