Custom Hooks: In react with examples
Custom Hooks in react

Custom Hooks: In react with examples

Dead Simple Chat Team

Table of Contents

in this article we are going to learn about custom hooks in react.

Custom hooks are basically functions, that allow users to define a hook for a specific purpose. Like a hook for fetching data, or a hook for checking whether a user has joined a chat room or not etc

Basically, you can extract and re use stateful logic from functional components with react custom hooks.

What are custom hooks: sharing logic between components

let us consider an application which relies heavily on the internet to function. In such an application it is important to notify the user if they are disconnected from the internet

We can define a component that tracks if the user is connected or not. For this we need

  1. A state variable that tracks if the user is online or not
  2. An effect that subscribes to the global online and offline events, and updates the state
import { useState, useEffect } from "react";

export default function StatusBar() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);
    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);

  return <h1>{isOnline ? "✅ Connected to the internet" : "❌ Offline"}</h1>;
}
checking whether connected to the internet
working app

You can turn on and off the network to see the component working.

Now, lets consider that we need to use this functionality in a different component.

Let's say we have a button that saves that app. We want this button to be disabled when the app is not connected to the internet.

Let us try to build this application

to start we can copy and paste the isOnline state and the Effect into the SaveButton

If you are looking for a React Native Chat SDK to build in app chat messaging application, you can consider DeadSimpleChat React Native SDK

import { useState, useEffect } from 'react';

export default function SaveButton() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  function handleSaveClick() {
    console.log('✅ App saved');
  }

  return (
    <button disabled={!isOnline} onClick={handleSaveClick}>
      {isOnline ? 'Save progress' : 'You are Offline'}
    </button>
  );
}

These two components work fine but there is duplication of code. Even though both these components look different they have the same logic working behind the scenes

Extracting code to make a custom hook from a component

Imagine that there was a useOnlineStatus hook along with the useState and useEffect

Then we could easily remove duplication betweeen the components like so

function StatusBar() {
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? '✅ Online' : '❌ Offiline'}</h1>;
}

function SaveButton() {
  const isOnline = useOnlineStatus();

  function handleSaveClick() {
    console.log('✅ App saved');
  }

  return (
    <button disabled={!isOnline} onClick={handleSaveClick}>
      {isOnline ? 'Save progress' : 'You are offline'}
    </button>
  );
}

There is no such built in hook but we can write the hook ourselves. Lets do it

  1. Declare a function and name is useOnlineStatus and copy all the duplicated code inside of it
  2. At the end of the function return isOnline. This lets your component read the values
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  return isOnline;
}

So, finally we have created the useOnlineStatus custom hook. Lets see how it works.

Here is the complete code:

useOnlineStatus:

import { useState, useEffect } from 'react';

export function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  return isOnline;
}
useOnlineStatus.js

App.js

import { useOnlineStatus } from './useOnlineStatus.js';

function StatusBar() {
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? '✅ Online' : '❌ You are disconnected from the internet'}</h1>;
}

function SaveButton() {
  const isOnline = useOnlineStatus();

  function handleSaveClick() {
    console.log('✅ App saved');
  }

  return (
    <button disabled={!isOnline} onClick={handleSaveClick}>
      {isOnline ? 'Save progress' : 'Offline'}
    </button>
  );
}

export default function App() {
  return (
    <>
      <SaveButton />
      <StatusBar />
    </>
  );
}

App.js

We can verify the app is working by switching the network on and off

The components what they want to do and not how they want to do

when you extrac logic from the components you can hide the details on how you are access the external system like fetching the data from the internet using api or connecting to a database etc.

Example 2: useCounter

Let us consider a useCounter that implements a counter functionality.

Benefits of Custom Hooks

  1. Ability to re use code: With custom hooks you can easily re use code across multiple components. That reduces code duplication and improves maintainability
  2. Keeping the concerns seprated: Usually you need hooks to communicate with an external system. When using a hook inside a component the component does not need to know the details of how the custom hook is functioning and thus the concerns of the custom hook and the component remains seprated
  3. Improved Readibility: When the code for the hook is seprated from the component there is imporoved readability because there is less code overall ( As the code related to the custom hook has been already written in a separate file). This improve code readibility
  4. Easier to test: As the code for the custom hook can be tested seprately from the code of the component there is imporved readability

Why Hook names start with use

React components are build form hooks either custom hooks or built in hooks. You will often have to use hooks that are created by others or you might write one yourself

Here are the naming conventions that you must follow

  1. Component names must start with a capital letter: like so SaveButton or SaveStatus. React components must also return JSX that react and use to display on the screen
  2. Hook names must start with use and followed by a capital letter: like useEffect or useApiData etc.  Hooks can return any value.

Following this convention you know where the components state, hook or other react features might hide

for examples: if you see a function called someColor inside your component you can be sure that it can't contain react state because its name doesn't start with use . However a hook like useEffects might contain other hooks inside it

With custom hooks you can share stateful logic but not the state itself

In the top level component when you turned the network on and off the components are added together

however it is wrong to think that the single isOnline state variable is shared between them

let us look at the code

function StatusBar() {
  const isOnline = useOnlineStatus();
  // ...
}

function SaveButton() {
  const isOnline = useOnlineStatus();
  // ...
}

It works the same way as if you never extracted the code.

Now let us consider another hook:

function StatusBar() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    // ...
  }, []);
  // ...
}

function SaveButton() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    // ...
  }, []);
  // ...
}

These are teo different state variables and Effects. They have the same value and the same time because they are synchronized with the same external resource

To explain this better let us consider a completely different examples

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('Mary');
  const [lastName, setLastName] = useState('Poppins');

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
  }

  return (
    <>
      <label>
        Your First name:
        <input value={firstName} onChange={handleFirstNameChange} />
      </label>
      <label>
        Your Last name:
        <input value={lastName} onChange={handleLastNameChange} />
      </label>
      <p><b>Hello, {firstName} {lastName}.</b></p>
    </>
  );
}

Here is some of the repetative logic from each form field

  1. the state variables firstName and lastName
  2. change handlers handleFirstNameChange and lastNameChange
  3. JSX specifies the value and onChange attribute for the input

We can extract the repitative logic into a useFormInput custom hook

App.js

import { useFormInput } from "./useFormInput.js";

export default function Form() {
  const firstNameProps = useFormInput("Mary");
  const lastNameProps = useFormInput("lamikasde");

  return (
    <>
      <label>
       Your First name:
        <input {...firstNameProps} />
      </label>
      <label>
       Your Last name:
        <input {...lastNameProps} />
      </label>
      <p>
        <b>
          Hello, {firstNameProps.value} {lastNameProps.value}.
        </b>
      </p>
    </>
  );
}
App.js

useFormInput.js

import { useState } from "react";

export function useFormInput(initialValue) {
  const [value, setValue] = useState(initialValue);

  function handleChange(e) {
    setValue(e.target.value);
  }

  const inputProps = {
    value: value,
    onChange: handleChange
  };

  return inputProps;
}

notice that we declare one state variable called as value

However, the Form component calls the useFormInput two times

function Form() {
  const firstNameProps = useFormInput('Mary');
  const lastNameProps = useFormInput('Poppins');
  // ...

This is why it works like declaring two state variables.

Custom Hooks do not share the state itself but do share the stateful logic.

Each call to the hook is independent of all the other calls to the hook. If you need to share the state between two hooks then lift the state up and pass it down

Passing Reactive Values between hooks

Like components the custom hooks also need to be pure because the code inside of your custom hooks fire at every re render of the component

Pure meaning the out put of the hook should be the same for the same input values

Always think of the custom hooks code to be a part of the components body

Custom hooks recieve latest props and state because they re render with your component.

Chat room example

Lets re visit the chat room example to explore more in detail

chatroom.js


import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
import { showNotification } from './notifications.js';

export default function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.on('message', (msg) => {
      showNotification('New message: ' + msg);
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        Chat Server URL:
        <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} />
      </label>
      <h1>This is the {roomId} chat room!</h1>
    </>
  );
}
Chatroom.js

when the server url or the chat url changes the Effect reacts to the changes and re renders.  You can tell by the console messages that the chat re connects every time the chat or the server url is different

Conclusion

In this article we learnt about the react custom Hooks. What are custom hooks and how you can make a custom hook of your own and how does the custom hooks works