Strapi.js – A new way to build Node.js APIs

Strapi.js is the most advanced Node.js Framework and Headless CMS out there. Even though it is not as popular as Express.js, it has the power to save weeks of development time for teams working on Node.js applications. With built-in admin panel and Koa.js under the hood, strapi can help you build your next awesome product.

Pre-requisites:

  • Basic knowledge of JavaScript
  • Node version 10.x+

Outline

  1. Intro
  2. What is Strapi.js?
  3. Features
  4. Get Started!

Intro

Since the launch of Node.js in 2011, we have seen several frameworks that were built on top of this amazing JavaScript runtime environment. Node.js allowed devs to write fast and robust APIs in minutes. Frameworks gave it super powers. The most popular framework for Node.js is and has been Express.js. Main reason for its popularity is its simplicity and minimalistic style. It accelerates Node.js project development even more. Besides Express.js, there are other frameworks such as Hapi, Koa, Meteor and Sails which have found their way into the hearts of developers. In this article, I would like to write about one Node.js framework that has been immensely underrated in the Node community, but has the power to save weeks of development – Strapi.js. Just stick with me here and we will look at the features of this powerful framework and explore what we can do with it.

What is Strapi?

Strapi is an open source Headless CMS based on Node.js. It comes with a flexible and customizable admin panel to build fast and secure content APIs. And it is used by thousands of companies around the world such as IBMWalmartDiscovery and others.

But what is a headless CMS in the first place?

Headless CMS is a type of content management system, such as WordPress, but without the view layer/front end (the head). It means we are not tied to one way of displaying data. We can just plug it into React, Angular or Vue alike view layers. It just provides the APIs and CRUD UI and it can be used with websites, mobile apps widgets, native apps etc. Strapi supports RESTful APIs and also GraphQL.

Features

There are a lot of reasons why you should give Strapi a try on your next project. Below we will go through the main ones.

Koa.js

Strapi is actually built on top of Koa.js, another Node.js framework made by the team who created the Express.js. Koa is a small and more expressive tool which is predicted to be the next generation Node.js framework. Having Koa.js under the hood makes Strapi future proof.

Plugin based

Almost everything in Strapi is plugin. Some of the plugins are installed by default upon creation of the project. Others can be created based on user needs. The real advantage of plugin based framework is that everything is customizable and extensible. Don’t like how the Admin Panel looks, go ahead, customize it. Can’t find the right plugin you are looking for, just create one!

Robust Content Management

Strapi comes with a built-in content management plugin that allows to easily CRUD content, including media files. Admin user interface is super user friendly and anyone can get started with it within minutes. The fact that the content management feature is actually a plugin means that it can be extended to fit any business needs.

React.js

Strapi Admin Panel is built using React.js and the source code is right there inside the project for you to modify if need be. Advanced functionalities of React allows Strapi to deliver amazing user experience.

Content Type Structure

Designing content type structures is made easy by available field types in the Content Type Builder plugin of the Admin Panel. The following types are accessible right out of the box:

  • Text Paragraphs
  • Boolean
  • Email
  • Date
  • Password
  • List of choices
  • Media files
  • JSON
  • String
  • Relation to other Content Types
  • Number

Similar to other plugins, content type builder plugin is also extensible. You can add your own field types to the list!

Secure and flexible APIs

APIs in Strapi can be requested using GraphQL or REST. Calls to all endpoints are secured by default, utilizing the Authentication and Permissions plugin. More often than not, we want our websites or apps to be used by people with different roles. And developing your own RBAC (Role Based Authentication System) can take weeks of development. Strapi provides that functionality out of the box!

Get Started!

Before we get started with Strapi, make sure you have the latest version of Node (10.x and above)

To install Strapi globally on your machine run the following

npm install strapi@alpha -g

Now we can start our new project.

Sample app – Strapi Cycle Bike Rental

We are going to build a backend for a simple bike rental shop using Strapi.js. We want to have APIs to manage clients, bike inventory and orders.

  • /users – to manage the client base
  • /bikes – to manage the bike inventory
  • /orders – to keep track of the orders

The beauty of the framework comes with the “Out of the box” configurations. Once we set up the server and add new models, necessary endpoints for CRUD operations get generated automatically. We can just start using them. Indeed, the APIs can be modified depending on the requirements.

Steps

Step 1

Run the following command to set up a new Strapi project

$ strapi new strapi_cycle

When prompted with the options in the terminal, we can decide which database we want to use and the kind of configurations we want to have. For simplicity, we will be using a local MongoDB database.

- Choose your main database: MongoDB
- Database name: strapi_cycle
- Host: 127.0.0.1
- +srv connection: false
- Port (It will be ignored if you enable +srv): 27017
- Username: <empty>
- Password: <empty>
- Authentication database (Maybe "admin" or blank): strapi_cycle
- Enable SSL connection: false

Note: If you have any issues connecting to your database, make sure you are using correct username/password. Also, leaving the Authentication databasequery blank can cause problems.

Step 2

Navigate to the project folder and run

$ strapi start

Command will start the server on port 1337. So if you navigate to localhost:1337 you should see a page similar to this:

Step 3

In order to create the endpoints for our bike rental shop, we navigate to the built-in admin panel. Click on the /admin link on the welcome page and you should be redirected to registration page.

Since there are no users in the database initially, the user you register first on the this page will have an admin role. Later on when we add more users without specifying the role, they will have a Public role by default. New roles can be added in the Roles and Permissions menu of the sidebar.

Step 4

Click on the Content Type Builder plugin and you will see that there are 3 content types already created by default. Those content types belong to the Roles & Permissions plugin. Since we do not need to use roles or permissions for our backend, we will just ignore the roles and permissions types for now. Lucky for us, the user content type is already there.

Click on the Add new content type button and enter the details for the model. First we create the bike model.

Step 5

Once we create the content type, next we have to add new fields. Click on Add new field button and add new fields for our bike model.

Name Type Value
model String
rate Number
status Enumeration Available,Taken,Broken

Step 6

Repeat steps 4 and 5 for Order model with the following fields

Name Type
rental_start Date
rental_end Number
cost Integer
user Relation with User
bikes Relation with Bike

An important thing to note here is that creating relations between models is ridiculously easy with Strapi.js. When selecting the type of the field for a model, just select the Relation type and you will be given options on how 2 entities should be related to each other.

In our case, an order can belong to a single user and a many bikes belong to many orders. So their relationship will be as follows:

Step 7

That’s it! We are pretty much done creating the APIs for users, bikes and orders. In order to see if they are working properly, let’s add some records and make calls to the newly created endpoints.

On the top of the sidebar, we have a list of our models. Click on each of them and add new records with dummy values.

Step 8

Now we can test our APIs using Postman. If you do not have this awesome tool installed on your machine, please do yourself a favor and download it from here.

Before we can make calls to the endpoints, we need to acquire a token from server. In order to do that, we will send a POST message to localhost:1337/auth/login with the following payload in the body

{
	"identifier":"sardor",
	"password":"<your_password>"
}

After that we can use the token in the header of each API call

Response body for /orders

[
    {
        "rental_time": "2020-04-13T13:00:00.000Z",
        "rental_due": "2020-04-12T16:00:00.000Z",
        "cost": 30,
        "bikes": [
            {
                "model": "M001",
                "rate": 10,
                "status": "Available",
                "_id": "5cbb1f03f3dc0d992df68176",
                "createdAt": "2019-04-20T13:30:43.846Z",
                "updatedAt": "2019-04-20T13:30:43.965Z",
                "__v": 0,
                "id": "5cbb1f03f3dc0d992df68176",
                "orders": null
            }
        ],
        "_id": "5cbb1f9cf3dc0d992df68177",
        "createdAt": "2019-04-20T13:33:16.558Z",
        "updatedAt": "2019-04-20T13:33:16.687Z",
        "__v": 0,
        "user": {
            "confirmed": true,
            "blocked": false,
            "name": "",
            "_id": "5cbb1eb7f3dc0d992df68173",
            "username": "jbourne",
            "email": "jbourne@jb.com",
            "provider": "local",
            "__v": 0,
            "role": "5cbb19ea6369dd710e43a720",
            "id": "5cbb1eb7f3dc0d992df68173"
        },
        "id": "5cbb1f9cf3dc0d992df68177"
    }
]

We are getting 200 status code, which means our APIs are working! If you pay attention to the body of the response, relation among the fields are implemented correctly as well.

Link to GitHub | Strapi

Cheers!

What are React Hooks and why you should care about them – Part 2

Outline

  1. Context Hook
  2. Custom Hooks
  3. Important Rules with Hooks
  4. Sample application

In the first part of the blog, we discussed the main concepts of React Hooks and the reasons for using them. We compared the traditional ways of creating components and the new ways with hooks. In the second part, we will continue exploring the the different types of hooks, learn about the important rules and create a sample application. We have a lot to cover. Let’s get started!

Context Hook

What is a context?

First of all, what is a context in React?

Context is a way to share global data across components without passing props. Usually, data in React application is passed from parent to child through props. Sometimes we have some data that should be delivered to a component deep inside the component tree. It is tedious to manually pass the same data over and over again through all components. Instead, we can create a central store that can be inserted into any component, just like in Redux.

How about we see an example code without Context API and identify the need for using it.

Let’s say there is a banana  plantation called “App” and it sets the prices for all bananas in the world. Before bananas reach the end customers, they need to go through wholesalers and supermarkets first. After that we can go to the stores and buy them. But since wholesalers and supermarkets need to add their profit margins in the process, the cost of the bananas go up.

Without Context API

// App.js
import React, { useState } from 'react';

const App = () => {
  const [bananaPrice, setBananaPrice] = useState(2); // Original price: $2
  return <Wholesaler price={bananaPrice}/>;
}

const Wholesaler = (props) => {
  const price = props.price + 2;
  return <Supermarket price={price}/>;
}

const Supermarket = (props) => {
  const price = props.price + 3;
  return <Customer price={price}/>;
}

const Customer = (props) => {
  return (
	<div>
		<p>Plantation price: $2</p>
		<p>Final price: ${props.price} :)</p>
	</div>
  );
}

What if we want to buy the bananas straight from the plantation and avoid the middlemen?

Now, the same code with Context API

With Context API

// App.js
import React, { useState } from 'react';

// First we need to create a Context
const BananaContext = React.createContext();

// Second we need to create a Provider Component
const BananaProvider = (props) => {
  const [bananaPrice, setBananaPrice] = useState(2); // Original price: $2
  return (
    <BananaContext.Provider value={bananaPrice}>
      {props.children}
    </BananaContext.Provider>
  );
}

const App = () => {
  return ( // Wrap the component in Provider, just like in Redux
    <BananaProvider>
      <Wholesaler/>
    </BananaProvider>
  );
}

const Wholesaler = () => {
  return <Supermarket/>;
}

const Supermarket = () => {
  return <Customer/>;
}

const Customer = () => {
  return (
    <BananaContext.Consumer>
      {(context) => (
        <div>
          <p>Plantation price: $2</p>
          <p>Final price: ${context} 😃</p>
        </div>
      )}
    </BananaContext.Consumer>
  );
}

useContext()

So how can we use Context API with hooks? With useContext!

import React, { useState, useContext } from 'react';

// First we need to create a Context
const BananaContext = React.createContext();

// Then we need to a Provider Component
const BananaProvider = (props) => {
  const [bananaPrice, setBananaPrice] = useState(2); // Original price: $2
  return (
    <BananaContext.Provider value={bananaPrice}>
      {props.children}
    </BananaContext.Provider>
  );
}

const App = () => {
  return ( // Wrap the component in Provider, just like in Redux
    <BananaProvider>
      <Wholesaler/>
    </BananaProvider>
  );
}

const Wholesaler = () => {
  return <Supermarket/>;
}

const Supermarket = () => {
  return <Customer/>;
}

const Customer = () => {
  const context = useContext(BananaContext);
  return (
    <div>
      <p>Plantation price: $2</p>
      <p>Final price: ${context} :)</p>
    </div>
  );
}

I know you could remove the line where you add the price to the cost of the banana and still get $2 at the end. But that is not the point. The point is that you have to do the props drilling when you don’t use Context. Incrementing the price while passing the the components is sort of the cost to do the props drilling.

Custom Hooks

Why?

Why would we want to create our own hooks? Because of 1 reason – it makes component logic more reusable.

  • Custom hook = Reusable logic

How?

How do we create a custom hook? Since hooks are just JavaScript functions, we can create a custom hook by just making a function.

  • The only difference is that the function name must start with the word – use. For example, useFunctionNameuseHungerStatus etc.
  • Custom hooks can call other hooks

Example without custom hook

Say we want to create an application with multiple Stopwatch timers on a single page. How would we do that?

This is what I mean by multiple timers:

Here is the code that implements hooks but does not reuse the logic for the timers

import React, { useEffect, useState } from 'react';

const App = () => {
  const [timerOneStatus, setTimerOneStatus] = useState(false);
  const [timerOneElapsed, setTimerOneElapsed] = useState(0);

  const [timerTwoStatus, setTimerTwoStatus] = useState(false);
  const [timerTwoElapsed, setTimerTwoElapsed] = useState(0);

  useEffect(() => {
    let intervalOne;
    if (timerOneStatus) {
      intervalOne = setInterval(
        () => setTimerOneElapsed(prevTimerOneElapsed => prevTimerOneElapsed + 0.1),
        100
      );
    }
    return () => clearInterval(intervalOne);
  }, [timerOneStatus]);

  useEffect(() => {
    let intervalTwo;
    if (timerTwoStatus) {
      intervalTwo = setInterval(
        () => setTimerTwoElapsed(prevtimerTwoElapsed => prevtimerTwoElapsed + 0.1),
        100
      );
    }
    return () => clearInterval(intervalTwo);
   }, [timerTwoStatus]);

  const handleReset1 = () => {
    setTimerOneStatus(false);
    setTimerOneElapsed(0);
  };

  const handleReset2 = () => {
    setTimerTwoStatus(false);
    setTimerTwoElapsed(0);
  };

  return (
    <div>
      <div>
        <h2>Stopwatch 1</h2>
        <h1>{timerOneElapsed.toFixed(1)} s</h1>
        <button onClick={() => setTimerOneStatus(!timerOneStatus)}>
          {timerOneStatus ? "Stop" : "Start"}</button>
        <button onClick={handleReset1}>Reset</button>
      </div>
      <div>
        <h2>Stopwatch 2</h2>
        <h1>{timerTwoElapsed.toFixed(1)} s</h1>
        <button onClick={() => setTimerTwoStatus(!timerTwoStatus)}>
          {timerTwoStatus ? "Stop" : "Start"}</button>
        <button onClick={handleReset2}>Reset</button>
      </div>
    </div>
  );
}

As we can see, we are not DRY coding here. Logic for the timers need to repeated every time we want to create a new timer. Imagine if we had 10 timers on one page.

Example with custom hook

Now what we could do is to separate the timer logic as a custom hook and use that one hook for creating any number of timers. Each timer would have its own state and action items. In the main component we use the custom hook just like useState or useEffect and destructure returned parameters from the hook.

import React, { useEffect, useState } from 'react';

const App = () => {
  const [timerOneStatus, setTimerOneStatus, timerOneElapsed, resetTimerOne] = useTimer();
  const [timerTwoStatus, setTimerTwoStatus, timerTwoElapsed, resetTimerTwo] = useTimer();

  return (
    <div>
      <div>
        <h2>Stopwatch 1</h2>
        <h1>{timerOneElapsed.toFixed(1)} s</h1>
        <button onClick={() => setTimerOneStatus(!timerOneStatus)}>
          {timerOneStatus ? "Stop" : "Start"}</button>
        <button onClick={() => resetTimerOne()}>Reset</button>
      </div>
      <div>
        <h2>Stopwatch 2</h2>
        <h1>{timerTwoElapsed.toFixed(1)} s</h1>
        <button onClick={() => setTimerTwoStatus(!timerTwoStatus)}>
          {timerTwoStatus ? "Stop" : "Start"}</button>
        <button onClick={() => resetTimerTwo()}>Reset</button>
      </div>
    </div>
  );
}

const useTimer = () => {
  const [status, setStatus] = useState(false);
  const [elapsedTime, setElapsedTime] = useState(0);

  useEffect(() => {
      let interval;
      if (status) {
        interval = setInterval(() =>
          setElapsedTime(prevElapsedTime => prevElapsedTime + 0.1),
          100
        );
      }
      return () => clearInterval(interval);
    },[status]);

  const handleReset = () => {
    setStatus(false);
    setElapsedTime(0);
  };

  return [status, setStatus, elapsedTime, handleReset];
}

In this case, we can place the hook in another file with other custom hooks and call it from anywhere in our project. Much cleaner and more reusable!

Rules with Hooks

Even though hooks are just functions, react team recommends to follow certain rules when using them. If you are lazy or just want to make sure you are following the rules automagically, you can install this linter. However, it is important that we have some common knowledge about the rules.

Rule 1 – Call hooks only at the top level

What does that mean?

It means we should not call hooks inside conditions, loops or nested functions. Rather use them at the top level of React functions.

Rule 2 – Hooks cannot be called from regular JavaScript functions. Call them from React functions

You can call hooks from the following functions:

  • Custom hooks
  • React function components

Rule 3 – Always start your custom hooks’ name with the word ‘use’

Sample application

Now let us build an application that takes advantage of the most hooks we have covered so far.

We will be building an application called Caturday and it will fetch pictures of random cats on the internet and allow us to vote on the pictures. It will keep count of our likes and dislikes. We will also add a button that can turn our page into dark mode (just change the background color of the page for simplicity).

Here is what the final result will look like: Link to Demo | Github

Step 1

We start building our app by running

$ create-react-app caturday

(If you don’t have create-react-app module installed, please run npx create-react-app caturday)

After navigating into the project folder, run

$ npm install semantic-ui-react --save

to install the Semantic UI design tool that will make dealing with CSS much easier.

Step 2

Create 3 files in the /src folder:

  • Caturday.js,
  • Caturday.css
  • customHooks.js

Step 3

Open the Caturday.css file and copy paste the following:

@media only screen and (max-width: 600px) {
  #image-container {
    width: 100%;
    padding-top: 50px;
  }
}
@media only screen and (min-width: 600px) {
  #image-container {
    width: 50%;
    margin: auto;
    padding-top: 50px;
  }
}
.dark-mode {
  background-color: #3b3939;
}
.main-img {
  margin: auto;
  height: 30em !important;
}
.main-placeholder {
  height: 30em !important;
}

Step 5

We create 2 custom hooks to use in our application. Open customHooks.js file and add these hooks

import { useState, useEffect } from "react";

export const useFetchImage = (url, likeCount, mehCount) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(j => j.json())
      .then(data => {
        console.log(JSON.stringify(data))
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        console.log(err);
      });
  }, [likeCount, mehCount]);
  return { data, loading };
};

export const useDarkMode = () => {
  const [enabled, setEnabled] = useState(false);
  useEffect(
    () => {
      enabled
        ? document.body.classList.add("dark-mode")
        : document.body.classList.remove("dark-mode");
    },
    [enabled] // Only re-runs when 'enabled' changes
  );
  return [enabled, setEnabled]; // Return state and setter
};

Step 5

We start constructing the primary file Caturday.js by importing the css file and creating a container for the app. We will also define the state of component and a customs hooks that update the image url when Like/Dislike buttons are clicked.

import React, { useState, useEffect } from "react";
import { Header, Segment, Image, Placeholder, Button, Container, Label, Icon, Checkbox } from "semantic-ui-react";
import './Caturday.css';

const Caturday = () => {
  const [likeCount, setLikeCount] = useState(0);
  const [mehCount, setMehCount] = useState(0);
  const [darkMode, setDarkMode] = useDarkMode();
  const {data, loading} = useFetchImage(
    "https://api.thecatapi.com/v1/images/search",
    likeCount,
    mehCount
  );
  
  return (
	<Container id="main-container">
      <Segment raised>
        // Header
        // Dark Mode toggle
        // Image container
        // Like and Dislike buttons
      </Segment>
    </Container>
  );
};

export default Caturday;

Step 6

Now when we define each element of our Caturday component and put all pieces together, our Caturday.js file should look like this.

import React, { useState, useEffect } from "react";
import { Header, Segment, Image, Placeholder, Button, Container, Label, Icon, Checkbox } from "semantic-ui-react";
import './Caturday.css';
import {useDarkMode, useFetchImage} from './customHooks';

const Caturday = () => {
  const [likeCount, setLikeCount] = useState(0);
  const [mehCount, setMehCount] = useState(0);

  const [darkMode, setDarkMode] = useDarkMode();
  const { data, loading } = useFetchImage(
    "https://api.thecatapi.com/v1/images/search",
    likeCount,
    mehCount
  );

  return (
    <Container id="image-container">
      <Segment raised>
      <Header className="ui basic segment centered">Caturday</Header>
        <Segment>
          <Checkbox onChange={() => setDarkMode(!darkMode)} toggle floated='right' label="Dark mode"/>
        </Segment>
        {
          loading ?
          <Placeholder fluid><Placeholder.Image className="main-placeholder"/></Placeholder> :
          <Image src={data[0] ? data[0].url : ''} className="main-img"/>
        }
        <Segment textAlign="center">
          <Button as="div" labelPosition="right">
            <Button onClick={() => setLikeCount(likeCount+1)} icon color="green">
              <Icon name="heart" />
              Like
            </Button>
            <Label as="a" basic pointing="left">
              {likeCount}
            </Label>
          </Button>
          <Button as="div" labelPosition="left">
            <Label as="a" basic pointing="right">
              {mehCount}
            </Label>
            <Button onClick={() => setMehCount(mehCount+1)} color="red" icon>
              <Icon name="thumbs down" />
              Meh
            </Button>
          </Button>
        </Segment>
      </Segment>
    </Container>
  );
};

export default Caturday;

Step 7

Open the App.js file and replace the return content with Caturday component

import React from 'react';
import Caturday from './Caturday';

const App = () => {
	return (
      <Caturday/>
    );
}

export default App;

Conclusion

We have covered most of the concepts about hooks and that should be enough for you to get started. If you have a project that you are working on right now that implements states and components in a traditional way, it is absolutely fine. No need to convert everything into hooks. However, when you are about to create new components, just give the hooks a try. They are 100% compatible with regular components with classes. You will be surprised how many lines of code you will be avoiding to accomplish the same functionalities. If you need more information about hooks, please checkout the official documentation by Facebook.

Thanks for spending your time to read the article!

What are React Hooks and why you should care about them – Part 1

Outline

  1. Intro
  2. What is wrong with React Components now?
  3. Hooks overview
  4. useState
  5. useEffect
  6. TLDR

Intro

There is a new kid on the block . React introduced a new feature called Hooks which will improve the code quality even more and make creating user interfaces easier. From now on, if you are going to create a new project, you should definitely take advantage of the new addition and keep your projects lean and clean. It was actually released a while ago, but production ready stable version has come out recently. So now is the time to really start using them. In this article, we will cover the main concepts and look at some examples. At the end of the article, you will have a fair amount of idea about React Hooks and you can start implementing them in your applications.

Before we dive in to the details of hooks, let us take a step back to understand the motivation behind it.


What is wrong with React components now?

3 things:

  1. Classes
  2. Hierarchical abyss (Not reusing stateful logic)
  3. Big components

1. Classes

Currently, there are mainly two ways to create components in React. First way is by using stateless functions:

function Greet(props){
	return <h1>Hello there, {props.name}!</h1>;
}

Second, using ES6 Classes:

class Greet extends React.Component {
	render(){
		return <h1>Hello there, {props.name}!</h1>;
	}
}

Right, so why are you saying there is something wrong with those two methods, you ask?

Well, first of all, there are no classes in JavaScript. A class is a syntactical sugar over JavaScript’s prototype-based inheritance. In other words, it is just a function with special features, which creates extra work for browser to process. But that is not the problem here. The problem is, classes are harder to understand and do not play well with minifying. They cause issues with hot reloading. Also, people often struggle when deciding to use classes or functions to make the components. Which results in inconsistency.

So, why not to use just functions then? Functions are stateless. Meaning, we cannot manage state in them. We can pass props back and forth, but that makes it hard to keep track of the changes.

2. Hierarchical Abyss

Just take a look at the picture below.

Extreme level of nested component tree makes it difficult to follow the data flow through the app.

3. Big components

Whether we like it or not, at some point of the development, our application gets large and requires more complex components. When that happens, our components end up implementing multiple React lifecycles that might contain unrelated data. A single task can be split across different lifecycles. That creates an opportunity for bugs in the application.


Hooks to the rescue!

Hooks solve all of the issues mentioned above. It does that by allowing us to add state management to functional components and use other React features.

Say what?

See, it is usually preferred to use just functions to create components. But as we mentioned above, we cannot manage the state or use lifecycles in our functions. But with the hooks we can!

(If you are thinking, why not use Redux for state management, hold on to that thought. That’s a discussion for another time.)

State Hook

Let’s look at an example code that changes a text when we click a button.

import React, { useState } from 'react';

function FruitMaster(){
	const [fruit, setFruit] = useState('Banana');
	return (
		<div>
			<p>Selected:{fruit}</p>
			<button onClick={
				() => setFruit(fruit=='Banana'?'Apple':'Banana')
			}>Change</button>
		</div>
	);
}

This is what supposed to happen:

We have a selected text variable, which is set to Banana by default. When we click on the Change button it changes the text from Banana to Apple or vice versa.

Now, let us break down the new elements in the component.

What are those things in the state variable?

In this case, setFruit() is equivalent of this.setState(). There is one important difference though. When we use this.setState(), it merges the changes to the state object. State hook on the other hand, will completely replace the state with the given value.

We used a state hook called useState in this example. There are other hooks too. We will see them soon.

So a hook, is actually a function that uses React features and returns a pair of values: one to hold the state value and one function to manage the value. We can name those values whatever we want. We can set a default value by passing it to the useState function.

Note that we are using a destructuring assignment to retrieve the pair of values. If you are not familiar with this method, take a look at here. Having said that, we could actually get the two values this way too:

const stateVariable = useState('Cherry'); //Returns an array with 2 values
const fruit = stateVariable[0];
const setFruit = stateVariable[1]; //function

Converting

Now, let’s convert our functional component to a class based component:

import React from 'react';

export default class FruitMaster extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      fruit: 'Banana'
    };
    this.setFruit = this.setFruit.bind(this);
  }

  setFruit(value){
    this.setState({fruit: value});
  }

  render(){
    return (
      <div>
        <p>Selected: {this.state.fruit}</p>
        <button onClick={
          () => this.setFruit(this.state.fruit == 'Banana' ? 'Apple' : 'Banana')
          }>Change</button>
      </div>
    );
  }
}

Comparison

We can see from the picture that using hooks reduces code volume almost by half!

Now, let us address the elephant in the room. What do we do when we have more than one variable in state?*

Simple! Create more state variables.

const [fruit, setFruit] = useState('Banana');
const [food, setFood] = useState('Taco');

Multiple state variables

import React, { useState } from 'react';

function FruitMaster(){
	const [fruit, setFruit] = useState('Banana');
	const [food, setFood] = useState('Taco');
	return (
		<div>
			<p>Fruit: {fruit}</p>
			<p>Lunch: {food}</p>
			<button onClick={
				() => setFruit(fruit=='Banana'?'Apple':'Banana')
			}>Change Fruit</button>
			<button onClick={
				() => setFood(food=='Taco'?'Burger':'Taco')
			}>Change Lunch</button>
		</div>
	);
}

What if I want to store all variables in one object? you might ask. Well, go ahead. But one thing to remember is that, state hook function replaces the stateand not merges to it. this.setState() merges the given values to the state object, hook function does not. But there is a way to fix it. Let us see how:

import React, { useState } from 'react';

function MealMaster(){
	  const [myState, replaceState] = useState({
	    fruit: 'Apple',
	    food: 'Taco'
	  });
	  return (
	    <div>
	      <p>Fruit: {myState.fruit}</p>
	      <p>Lunch: {myState.food}</p>
	      <button onClick={
	        () => replaceState(myState => ({
	          ...myState,
	          fruit: myState.fruit=='Banana'?'Apple':'Banana'
	        }))
	      }>Change Fruit</button>
	      <button onClick={
	        () => replaceState(myState => ({
	          ...myState,
	          food: myState.food=='Taco'?'Burger':'Taco'
	        }))
	      }>Change Lunch</button>
	    </div>
	 );
}

We have to use spread operator to change the only part of the state we need and keep the rest of it as it is.

Effect Hook

What about the lifeCycle methods of React? We could use those with classes. But now they are gone…

Not really.

There is another hook called useEffect and we can use it instead of the lifecycles. In other words, we can handle side effects in our applications with hooks. (What is a side effect?)

Here is an example of a component that uses familiar lifecycles:

Old method

import React from 'react';

class TitleMaster extends React.Component {
	constructor(props){
		super(props);
		this.state = {
			title: 'Tuna'
		};
	}
	componentDidMount(){
		document.title = this.state.title; // Changes tab title
	}
	componentDidUpdate(){
		document.title = this.state.title;
	}
	updateTitle(value){
		this.setState({title: value});
	}
	
	render(){
		return (
			<div>
		        <button onClick={
		          () => this.updateTitle(this.state.title =='Tuna'?'Donut':'Tuna')
		        }>Update Title</button>
		    </div>
		);
	}
}

Our component in action.

The component is bulky even for a small functionality. Code in componentDidMount and componentDidUpdate is repeated.

With hooks

Now let’s create the same component with hooks!

import React, { useState, useEffect } from 'react';

function TitleMaster(){
  const [title, updateTitle] = useState('Tuna');
  useEffect(() => {
    document.title = title;
  });
  return (
      <div>
          <button onClick={
            () => updateTitle(title =='Tuna'?'Donut':'Tuna')
          }>Update Title</button>
      </div>
    );
}

export default TitleMaster;

Much cleaner. Less code. Easier to understand.

As we mentioned before, a hook is a function. useEffect is also a function that accepts another function and an array. Don’t worry about the array part for now.

So inside the function we pass to useEffect, we can perform our side effect logic. In the example above, we are updating the tab title in the browser. Another common practice is to use data fetching in the useEffect hooks.

We can also use multiple useEffect hooks in one component.

Note that we are placing the hooks inside of our component functions. That way they will have access to the state variables.

Infinite loop

By default useEffect re-renders every time the component changes. Sometime incorrectly implementing the hook might cause infinite loop issue. Remember, we said that the useEffect takes 2 arguments? So the second argument is an object or array of values. Which tells React, “Hey React, re-run the useEffect only when these state values change

const [user, setUser] = userState();
useEffect(() => {
	document.title = user.id;
}, [user.id]); // Re-render the hook Only when user.id changes

Cleanup logic

useEffect hook can also handle cleanup logic. What does that mean? Well, sometimes we subscribe to some APIs and once we are done, we need to unsubscribe from it to prevent any leaks. Or when we create an eventListener, we need a way to remove it.

useEffect can do it by returning a function.

import React, { useState, useEffect } from 'react';

function TitleMaster(){
  const [title, updateTitle] = useState('Tuna');
  useEffect(() => {
    document.title = title;
    return () => {
		// Perform clean up logic here
	}
  });
  return (
      <div>
          <button onClick={
            () => updateTitle(title =='Tuna'?'Donut':'Tuna')
          }>Update Title</button>
      </div>
    );
}

TLDR:

React hooks are special functions that can be used in stateless components. They allow us to hook into react features and add state management to the components. There are 3 main hooks: useState, useEffect and useContextuseState can replaces the current way of declaring state object in the constructor and manipulating values in it by using this.setState()useEffect can be used instead of react lifecycles. These hooks are not meant to replace the current way of creating components. They are backwards compatible. No need to rewrite your existing components using hooks. They can make your projects much cleaner by letting you write less code though.

Part 2: What are React Hooks and why you should care about them

In Part 2 we will cover the following:

  • Context Hook
  • How to make custom hooks!
  • Important Rules with Hooks
  • Real life application example