HowTo: Custom React localStorage Hook ⚓
Tags: react, javascript
Published: 2020-06-22 15:34:33

Traditionally React had two popular ways to share stateful logic between components: **render props** and **higher-order components**. Hooks solve many of the problems these two techniques have.

In this tutorial you will learn how to create your own Hooks in React by building a custom Hook for storing and reading the Local Storage web API! 😃

[➡️ Checkout what we're building.](https://codesandbox.io/s/elastic-surf-ss0qs?file=/src/App.js)

<hr />

### What is a custom hook?
First, let's check what a custom hook actually is. According to the official React docs, a custom Hook is a JavaScript function whose name starts with `use` and that may call other Hooks. Hooks are functions that let you hook into React state and lifecycle from function components). Sounds a bit vague still right. Next up, let's see what localStorage is.

<hr />

### Local Storage
The read-only localStorage property of the global window object gives us the possibility to store data at the client side without an expiration time (sessionStorage contrarily gets lost after closing the browser).

So.. what can you actually do with localStorage? Many things! Like:
- Remembering last search term
- Save your comment
- Save the username
- Toggle theme
- Many more..


**Keep in mind though:**
- Do not store sensitive user information in localStorage
- Limited to 5MB across all major browsers
- No form of data protection (do not store e.g. JWT tokens here)

<hr/>

Let's start with building our Hook!

Setting an item with localStorage is very easy. Let's set our theme to dark:

```javascript
localStorage.setItem('theme', 'dark');
```

And reading is easy as well:

```javascript
localStorage.getItem('theme');
```

Awesome right!

Now, what we actually want to achieve here is to use the localStorage methods from everywhere in our React application.

Let's create a new folder called `/hooks` and create a function called `useLocalStorage` that acceps two arguments: a key and an initialValue.

```javascript
export const useLocalStorage = (key, initialValue) => {
  ...
}

export default useLocalStorage;
```

When we are finished with our Hook, we want to use it as follows:
```javascript
// Destructuring off an array
const [storedTheme, setTheme] = useLocalStorage("theme");
// Get the current theme
const theme = storedTheme;
// Setting a theme
setTheme("dark");
```

This means our Hook has to return an array containing: 
- a value with the requested stored item in localStorage and
- a function that can set an item in localStorage.

We'll use the `useState` Hook to let React keep track of the stored value.


```javascript
import { useState } from "react";

export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(initialValue);

  const setValue = (value) => {
    window.localStorage.setItem(key, value);
    setStoredValue(value);
  }

  return [storedValue, setValue];
}
```

We only have set up the basis but let's test it already! 🤖

Inside another component, import our Hook and add:

```javascript
import React, { useEffect } from "react";
import { useLocalStorage } from "./hooks/useLocalStorage";

function App() {
  const [storedTheme, setTheme] = useLocalStorage("theme");

  useEffect(() => {
    setTheme("dark");
  }, [setTheme]);

  console.log(storedTheme);

  return <h1>Hi Dev</h1>
}
```

You should see 'dark' in your console and also you can see the localStorage items with Chrome Dev Tools:

![localStorage](https://i.ibb.co/vYQLvgj/our-theme.png)

We're not ready yet, let's improve our Hook!

<hr />

### Improving our Hook

We want to improve our hook so we can do the following:
- Setting a default value
- Store objects and functions
- Add error handling if our function failes

To get our stored value, we will add a function to our `useState` Hook that checks if the item exists inside our localStorage. If not, we'll throw an exception with a try-catch block and return the initialValue so the state always stores our theme if localStorage failes.

We'll use JSON.parse() to transform an object to a string so we can also store objects.

```javascript
...
export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (err) {
      console.warn("Setting localStorage went wrong: ", err);
      return initialValue;
    }
  });
...
};
```

Awesome! 👍

To test this we can create use our hook to create a new storage item to store a name. Without setting it in localStorage yet, we can let `useState` store our name:

```javascript
const [storedName, setUsername] = useLocalStorage("name", "Tim");
console.log(storedName); // Gives Tim without using localStorage
```

Finally, we'll add a try-catch block to our `setValue` function, add JSON.stringify() to transform our string back to an object and check if the stored value is a function:

```javascript
...
const setValue = (value) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };
...
```

Our complete Hook:

```javascript
import { useState } from "react";

export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (err) {
      console.error(err);
      return initialValue;
    }
  });

  const setValue = value => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (err) {
      console.error(err);
    }
  };

  return [storedValue, setValue];
};

export default useLocalStorage;
```

That's it! We now have a custom hook that we can use anywhere in our application and store any variable we'll like.

Thanks for following this tutorial.

Make sure to follow me for more tips and tricks. 🤓