ReactJS: A Simple Custom Hook

React Hooks have changed the way we write components & has mentally pushed to write more Functional Components. Lets see how to make a Custom Hook in React.

ReactJS: A Simple Custom Hook

React Hooks have changed the way we write components. Hooks have mentally pushed us to write more Functional than Classical Components.

Once you start making your app with Hooks, you suddenly have 10s of different hooks, and even though they are managing a related state, it becomes hard to manage them.

They start feeling like clutter in the good-old Functional Components.

Looks unrelated? Have a look at this Component:

import React from 'react';

const DataTable = ({ movies = []}) => (
  <div>
    {movies.map(({Poster, Title, imdbID, Year}) => (
      <div key={imdbID}>
        <img src={Poster} height="100" alt={Title} />
        <p>
          <a href={`/?t=${imdbID}`}>{Title}</a>
          <br />{Year}
        </p>
      </div>
    ))}
  </div>
)

export default DataTable

Now if we were to add the Data Loading requests and build the profile links, there are two ways to do it:

  1. Add all the requests and functions in the same component
  2. Create a wrapper component to
  • Make a request and build links
  • Pass all the required Data and Functions as Props

Let’s see both ways and how our computer develops in size and functionalities.

Loading Data, building event handlers, and Markup in the same component:

import React, { useEffect, useState, useContext } from 'react';
import KEY from './KeyContext';

const url = 'http://www.omdbapi.com/?s='

const DataTable = ({ query = 'Harry Potter' }) => {
  const key = useContext(KEY);
  const [movies, setMovies] = useState([])
  useEffect(() => {
    fetch(`${url}${query}&apikey=${key}`)
      .then(r => r.json()).then(res => setMovies(res.Search.sort((a,b) => (a.Year-b.Year))))
  }, [key, query])

  return (
    <div>
      {movies.map(({Poster, Title, imdbID, Year}) => (
        <div key={imdbID}>
          <img src={Poster} height="100" alt={Title} />
          <p>
            <a href={`/?t=${imdbID}`}>{Title}</a>
            <br />{Year}
          </p>
        </div>
      ))}
    </div>
  )
}

export default DataTable

And if we make a Wrapper Component to wrap the data table and pass the data as props, it will look like the following:

import React, { useEffect, useState, useContext } from 'react';
import KEY from './KeyContext';

const url = 'http://www.omdbapi.com/?s='

const DataTable = ({ movies = []}) => (
  <div>
    {movies.map(({Poster, Title, imdbID, Year}) => (
      <div key={imdbID}>
        <img src={Poster} height="100" alt={Title} />
        <p>
          <a href={`/?t=${imdbID}`}>{Title}</a>
          <br />{Year}
        </p>
      </div>
    ))}
  </div>
)

const DataContainer = ({ query = 'Harry Potter' }) => {
  const key = useContext(KEY);
  const [movies, setMovies] = useState([])
  useEffect(() => {
    fetch(`${url}${query}&apikey=${key}`)
      .then(r => r.json()).then(res => setMovies(res.Search.sort((a,b) => (a.Year-b.Year))))
  }, [key, query])

  return <DataTable movies={movies} />
}

export default DataContainer

Now here comes the custom hooks.

First, we can take the loading of data and related functions in separate functions, triggering the same thing through that function.

Additionally, we can have a context to initialize the defaults and some common data to share among apps.

First of all, we want to separate the data loading. Let's make out a new hook called useMovies

const useMovies = (query = null) => {
  return fetch(`${url}${query}&apikey=${key}`)
    .then(r => r.json())
    .then(r => r.Search.sort((a,b) => (a.Year-b.Year)))
}

Now that our function loads data; let's add some persistence with state hooks.

import {useState} from 'react';

const useMovies = (query = null) => {
  const [movies, setMovies] = useState([])
  fetch(`${url}${query}&apikey=${key}`)
    .then(r => r.json())
    .then(r => r.Search.sort((a,b) => (a.Year-b.Year)))
    .then(setMovies)
  return movies;
}

But we want to load the movies on the first call, not every call, and then get the new data when there is a change in the query.

Along with that, let’s separate the fetching/AJAX code in a separate file.

With the above-mentioned separation of concerns in the code, we have the following useMovies hook and request module, respectively:

// useMovies.js
import { useState, useEffect, useContext } from 'react';
import KeyContext from './KeyContext';
import request from './request';
import queryString from 'query-string';

const url = 'http://www.omdbapi.com/'

const sortMovies = (movies = []) => movies.sort((a, b) => (a.Year - b.Year))

const getUrl = (params) => [url, queryString.stringify(params)].join('?')

const useMovies = (query = null) => {
  const [q, setQuery] = useState(query)
  const [movies, setMovies] = useState([]);
  const apikey = useContext(KeyContext);

  useEffect(() => {
    q && request(getUrl({ apikey, s: q }))
    .then(r => r.Search)
    .then(sortMovies)
    .then(setMovies)
  }, [q, apikey])

  return [movies, setQuery];
}

export default useMovies;
// request.js
export default (url, params) => fetch(url, params)
  .then(response => {
    if (response.status === 200) {
      try {
        return response.json()
      } catch (e) {
        return response.text()
      }
    }
    return response
  })

In the above function of our custom hook, we did the following:

  • Receive the First Query & initialize a state to receive changes in the Query
  • Movies data with useState Hook
  • API key from the Context and useContext Hook
  • Use useEffect to
  1. Trigger the first request for First Query
  2. Request changes to API on change of Query
  3. As API Key is coming from Context, it is prone to change and hence keeps it in the Dependency of useEffect hook
  4. Return the Data (i.e. movies) and Function to change Query (i.e. setQuery)

Though while creating or using hooks, there are two rules that you need to keep in mind

  1. Only Call Hooks at the Top Level
  2. Only Call Hooks from React Functions

The name of the rules states enough, though you can read more about them here: Rules of Hooks – React.


Moreover, if you want to use Hooks in most cases, you can check out the following repository; it is a collection of Custom Hooks for almost everything:

streamich/react-use
React Hooks — ?. Contribute to streamich/react-use development by creating an account on GitHub.

Check out the above code here:

time2hack/custom-hook-example
Example code for https://time2hack.com/reactjs-simple-custom-hook/ - time2hack/custom-hook-example

And the demo of the above code with a custom hook is here:

Custom Hooks Example - Time to Hack
Use Custom Hooks to separate critical logic and UI code

Conclusion

Hooks have simplified the code a lot in terms of writing and reading.

I am personally trying to use Hooks as much as possible.

I would like to know, Did you make your custom hook? And How?

Let me know through comments. or on Twitter at  @heypankaj_  and/or  @time2hack

If you find this article helpful, please share it with others.

Subscribe to the blog to receive new posts right to your inbox.


Credits

Icon from IconFinder
Photo by  Ferenc Almasi  on  Unsplash