Create Custom React Hooks

Creating Custom Hooks

General convention


Custom hook: useLocalStorage

Imagine useState hook, but with ability to persist value between page refresh. This will involve data storage and there's no react built in hook for it. Coding in the app can be done, however since this is a common functionality, a more effective way is to create a custom hook, that in the main app can written as simple as using normal format for useState.


Set MainApp

Set MainApp similar to using useState, but use the custom hook useLocalStorage we are going to create instead.

Details

  • Hook name: useLocalStorage
  • Additional parameter key, set value to "name.

Comparison with useState

  • const [name, setName] = useState("")
  • const [name, setName] = useLocalStorage("name", "")
MainApp.js
function MainApp() {
    const [name, setName] = useLocalStorage("name", "")
    return (
        <input
            type="text"
            value={name}
            onChange={e => setName(e.target.value) 
        />
    )
}

Create useState mimic: useLocalStorage

First, create custom hook in its own js file as a function with regular useState functionality. This hook is pretty much a wrapped useState function, and can be used in the place of useState in the MainApp.

  • File: useLocalStorage.js file
  • Function: useLocalStorage
  • Parameter: initialValue
  • Code within: the typical useState declaration, but taking in initialValue
  • Return mimicking useState's: [value, setValue]
useLocalStorage.js
function useLocalStorage(initialValue) {
    const [value, setValue] = useState(initialValue);
    return [value, setValue];
}

At this point, the custom hook only works like useState, doesn't have data persisting and taking data back from storage ability yet.


Create getSavedValue function

Create the function and set the parameter

Create function getSavedValue for retrieving our saved value. This function uses parameter key to locate the stored data wanted.

  • Location: useLocalStorage.jss
  • Parameters: key and initialValue.
useLocalStorage.js
function getSavedValue(key, initialValue) {
    ...
}

This key needs to be passed all the way from the MainApp

  • Add parameter to useLocalStorage function: key
  • Add key value to the useLocalStorage declaration in the main app: "name" string.
useLocalStorage.js
function useLocalStorage(key, initialValue) {
    ...
}
MainApp.js
function MainApp() {
    const [name, setName] = useLocalStorage("name", "")
}

Code getting stored value functionality

  • Get what was stored at key location: localStorage.getItem(key)
  • Convert above to JSON: JSON.parse(...)
  • Keep as a constant: const savedValue = ...
  • If there's a previously saved value, return that saved value: if (savedValue) return savedValue
  • Otherwise: return initialValue

Make it able to take function as initial state

To mimic useState functionality fully, remember that useState can take not only value, but also function:

  • const [value, setValue] = useState(() => {...}))

So we need to check whether the initialValue is a function. If so, we want to call it as return:

  • if (initialValue instanceof Function) return initialValue()
function getSavedValue(key, initialValue) {
    const savedValue = JSON.parse(localStorage.getItem(key))
    if (savedValue) return savedValue;
    if (initialValue instanceof Function) return initialValue();
    return initialValue;
}

Set it renders only at the beginning

JSON parse of local storage is quite a slow action. And we don't need this to run in every single rerendering. We only need to check for persistence value upon page refresh. For that, useState is modified as follow.

  
function useLocalStorage(key, initialValue) {
    const [value, setValue] = useState(() => {
        return getSavedValue(key, initialValue)
    })
  return [value, setValue]
}

Update and save value with useEffect

Easiest way is to use useEffect

useEffect(() => {code}, [dependencies])

Coding

  • We want useEffect to save the value to local storage: localStorage.setItem()
  • Pass the key and value as parameters: ...setItem(key, value)
  • We want to do this only when value changes. Set value as dependency: useEffect(() => {...}, [value])
  • We need to stringify the value because we can only keep string value in local storage: ...setItem(key, JSON.stringify(value))
useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
}, [value])

Final codes

Regular useState

MainApp.js
import React, { useState } from "react";

export default function InputRegular() {
  const [name, setName] = useState("");
  const [address, setAddress] = useState("");
  return (
    <div>
      <input
        type="text"
        value={name}
        placeholder="Type name"
        onChange={(e) => setName(e.target.value)}
      />
      <p>Name: {name}</p>
      <input
        type="text"
        value={address}
        placeholder="Type name"
        onChange={(e) => setAddress(e.target.value)}
      />
      <p>Address: {address}</p>
    </div>
  );
}

Custom useLocalStorage

MainApp.js
import React from "react";
import useLocalStorage from "./useLocalStorage";

export default function InputPersistance() {
  const [name, setName] = useLocalStorage("name", () => "");
  const [address, setAddress] = useLocalStorage("address", () => "");
  return (
    <div>
      <input
        type="text"
        value={name}
        placeholder="Type name"
        onChange={(e) => setName(e.target.value)}
      />
      <p>Name: {name}</p>
      <input
        type="text"
        value={address}
        placeholder="Type address"
        onChange={(e) => setAddress(e.target.value)}
      />
      <p>Address: {address}</p>
    </div>
  );
}
useLocalStorage.js
import React, { useEffect, useState } from 'react'

function getSavedValue(key, initialValue) {
    const savedValue = JSON.parse(localStorage.getItem(key))
    if (savedValue) return savedValue;
    if (initialValue instanceof Function) return initialValue();
    return initialValue; 
}

export default function useLocalStorage(key, initialValue) {
    const [value, setValue] = useState(() => {
        return getSavedValue(key,initialValue)
    })
    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value))
    }, [value])
  return [value, setValue]
}

Usage

Open local storage in Application tab

  • Application > Storage > Local Storage > http://localhost:3000
  • Key: name, Value: ""

Upon changing value in input box, the value is local storage is updated (by that, kept). When refreshing the page, this value is retained and therefore value is applied when the page load again.

  • Key: name, Value: "Dina"
  • Key: address, Value: "Picton St."

The hook code might be a bit complicated, but it's all wrapped inside the custom hook that can be used in a very simple manner just like the regular useState hook. The only thing different is the additional key parameter, and this is a very minor addition. Just like the hook useState, this hook can be used multiple times in the MainApp.


Resources