2 min read

Tutorial: Create a custom hook that allows saving items to the local storage

šŸ’”
This is a detailed tutorial that solves the Create a local storage hook exercise.
If you haven't already, try to solve it yourself first before reading the solution!

To build this custom hook, weā€™ll first look at the basics of working with the local storage. Then, weā€™ll see how to hook that into React using the useState hook (and no useEffect!).

Fundamentals of local storage

Local storage is a way to persist information on the userā€™s computer. The data you save in local storage persists across multiple sessions, as opposed to session storage, for example, that gets cleared when the user closes the browser tab.

Local storage is a key-value storage. Values are stored as strings.

To get/set an item from storage you would use the following methods:

localStorage.setItem(key, value);
localStorage.getItem(key);
localStorage.removeItem(key);

Storing the todo list

With the above points settled, letā€™s decide how to store the todo list.

Since the values need to be stored as strings, we will need to convert our Javascript array to a JSON string (this process is also known as serialisation - converting an object to a string, sort of ā€œflatteningā€ it). We can do that using JSON.stringify. Then, before reading the value from local storage, we can deserialise (convert it back from a string to an object) it with JSON.parse.

const todoList = [
    { id: 1, text: ā€˜Wash dishesā€™ }, 
    { id: 2, text: ā€˜Buy milk }
];
localStorage.setItem(ā€˜todoListā€™, JSON.stringify(todoList));
const saved = JSON.parse(localStorage.getItm(ā€˜todoListā€™));

Hooking into React

To figure out what hooks we need to for this, it helps to recap everything that needs to happen:

  1. When rendering the todo list, if there is already a value saved in the local storage, we should use that; otherwise it should be initialised with an empty array

  2. Whenever a new todo is added/edited/deleted, we should update the local storage value

Initialisation

The todo list is saved in the component state using the useState hook, which supports an initial value as its parameter. Thus, to initialise the todo list we can use the following expression:

  const [todoList, setTodoList] = useState(
    JSON.parse(localStorage.getItem(ā€˜todoListā€™)) || []
  );

Handling updates

Whenever the todo list changes, we need to update both the state and the local storage.

There are two ways to go about it - with useEffect, to synchronise the changes, or using a custom event handler - i.e. a function we can call on a specific event.

The React docs advise against using useEffect if an update needs to be performed after a user action (i.e. an event), so letā€™s just use a simple onChange handler:

const handleTodoListChange = (updatedTodoList) => {
  localStorage.setItem(ā€˜todoListā€™, JSON.stringify(updatedTodoList));
  setTodoList(updatedTodoList);
}

Extracting everything into a custom hook

With these building blocks in place, we can put together the final custom hook. To make it generic, letā€™s rename todoList to simply value:

import { useState, useEffect } from "react";

export default function useLocalStorage(keyName, initialValue) {
  const [value, setValue] = useState(
    JSON.parse(localStorage.getItem(keyName)) || initialValue
  );

  const handleValueChange = (updatedValue) => {
    localStorage.setItem(keyName, JSON.stringify(updatedValue));  
    setValue(updatedValue);  
  }


  return [value, handleValueChange];
}

Wrapping up

This is a very basic hook, that still has some room for improvement.
What would you change about it? Share your thoughts in the comments below!