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