# Custom Hook: useLocalStorage

En este artículo vamos a centrarnos en la construcción de un custom hook que utilizaremos para poder trabajar con el localStorage en nuestras aplicaciones de React, hook al que vamos a llamar \`useLocalStorage' y para ello vamos a partir de una aplicación muy simple en la que se nos estará mostrando la siguiente interfaz de usuario:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714043949593/905a55b4-5218-4065-afeb-f72cdf05f0af.png align="center")

La interfaz que nos presenta la aplicación es realmente simple puesto que únicamente tenemos a nuestra disposición un campo de texto en el que podremos escribir lo que queramos y tres botones *Set*, *Get* y *Remove* aunque en este punto cada uno de estos tres botones no va a hacer nada puesto que no tienen ninguna función asociado que esté presente en el código. Nuestra misión, por lo tanto va a ser crear el hook `useLocalStorage` el cual va a proporcionarnos tres funciones: una para poder establecer un valor dentro del localStorage, otra para poder obtener un valor del localStorage y una última opción que nos permitirá borrar un elemento del localStorage.

Así pues el código de partido del componente que se estará encargando de mostrar el contenido que se renderiza en la imagen anterior será algo parecido a lo que se puede ver a continuación:

```typescript
import { useState } from 'react'

export const Page = () => {
  const [value, setValue] = useState('')

  return (
    <div>
      <h1 className='mb-2 text-3xl font-bold'>useLocalStorage</h1>
      <input
        className='mb-4'
        onChange={e => setValue(e.target.value)}
        type='text'
        value={value}
      >
      <div className='flex flex-row gap-4'>
        <button>Set</button>
        <button>Get</button>
        <button>Remove</button>
      </div>
    </div>
  )
}
```

Como podemos observar el código de nuestro componente es sencillo puesto que dentro del mismo estamos definiendo un valor de su estado al que llamamos `value` y este será el que asignaremos como valor que tendrá el campo `<input>` que se renderiza para poder interactuar con el usuario.

Por lo tanto lo que ahora vamos a tener que hacer es crear nuestro custom hook junto con cada una de las tres funciones que vamos a poder asociar a cada uno de los botones que se muestran en la interfaz y que servirán para interactuar con los elementos que tenemos dentro del localStorage.

## Creación de `useLocalStorate`

Lo primero que vamos a hacer es crear el fichero `useLocalStorage.ts` para guardar dentor del mismo el código de nuestro custom hook y lo que haremos dentro del mismo será definir la función que representará a nuestro custom hook como sigue:

```typescript
export const useLocalStorate = () => {}
```

Una vez que hemos construido nuestro hook lo que vamos a hacer es pasar a utilizarlo dentro de nuestro componente `Page` y para ello, como sucede con cualquier otro hook, lo primero que haremos será importarlo:

```typescript
import { useLocalStorage } from './useLocalStorate'
```

y ahora lo que pasaremos a hacer es invocarlo en nuestro componente:

```typescript
export const Page = () => {
  const [value, setValue] = useState('')
  useLocalStorage()
  // ... resto del código
```

> **Nota:** el hecho de crear un custom hook en un fichero independiente y posteriormente invocarlo en el componente donde se utiliza es la forma que se considera correcta de proceder cuando estamos trabajando en React.

### Establecer un valor en el localStorage

Vamos a crear la primera de las funciones que nos tiene que proporcionar `useLocalStorage` que no es otra que aquella que nos permita guardar un valor dentro del localStorage del navegador donde se está ejecutando nustra aplicación. Ahora bien, en este punto es necesario saber que en el localStorage la información que se guardará siempre estará basada en una *key* y esto es así puesto que el navegador nos permitirá trabajar con el localStorage a traves de una llamada del tipo:

```typescript
window.localStorage.setItem(key, value)
```

> **Nota:** podemos pensar en el localStorage del navegador como en una espacie de `Map`.

¿Cuál es el problema que nos encontramos ahora? Pues que en nuestra definición de `useLocalStorage` no hemos pensado en ningún momento en el uso de esta *key* por lo que nos vamos a ir a la definición de nuestro hook de tal manera que cuando este sea invocado va a esperar recibir esta *key* como un parámetro, lo que nos deja algo como lo siguiente:

```typescript
export const useLocalStorage = (key: string) => {}
```

Esto automáticamente lo que provoca es que tengamos que especificar esta *key* dentro de la llamada que se hace del hook en el componente `Page` por lo que escribiremos algo como lo siguiente donde optamos por llamar a la *key* `value` pero podría ser lo que nosotros quisiésemos:

```typescript
export const Page = () => {
  const [value, setValue] = useState('')
  useLocalStorage('value')
  // ... resto del código
```

Como ahora mismo ya tenemos la *key* con la que trabajaremos dentro de nuestro custom hook vamos a centrarnos en al construcción de la función que nos permita establecer el valor en el localStorage. La definición de esta función nos deja algo como lo siguiente:

```typescript
export const useLocalStorage = (key: string) => {
  const setItem = (value: unknown) => {
    window.localStorage.setItem(key, JSON.stringify(value))
  }
}
```

Lo primero que que tenemos que entender de esta definición es que el parámetro `value` que recibe `setItem` es de tipo `unknown` puesto que desconocemos cuál es el tipo que se va a guardar en el localStorage y además no vamos a tener que preocuparnos por ello. Además estamos haciendo uso del método `setItem` que nos proporciona el objeto `localStorage` de `window` (objeto que se encarga de todas las interacción con el localStorage del navegador) y utilizamos en el método `stringify` del objeto `JSON` para que en el caso de que el valor a guardar que sea un objeto de JavaScript o un array este método lo convierta previamente a string antes de que los guardemaos en el localstorage.

Nos queda un detalle más en el código que estamos desarrollando y es que `useLocalStorage` tienen que devolver esta función para que pueda ser utilizada en todos los componentes que vayan a usar nuestro custom hook por lo que escribiremos:

```typescript
export const useLocalStorage = (key: string) => {
  const setItem = (value: unknown) => {
    window.localStorage.setItem(key, JSON.stringify(value))
  }

  return {
    setItem
  }
}
```

La pregunta que puede surgir en este punto es ¿por qué estamos retonando un objeto y no la función `setItem` directamente? La respuesta es sencilla puesto que nuestra intención es que `useLocalStorage` retorne más de una función y la forma correcta de hacerlo es como métodos de un objeto.

Si nos vamos a ahora al componente `Page` vamos a poder capturar esta función:

```typescript
export const Page = () => {
  const [value, setValue] = useState('')
  const { setItem } = useLocalStorage('value')
  // ... resto del código
```

Y ahora tan soló nos quedará vincular la llamada a esta función en el momento en el que pulsa sobre el botón que está etiquetado como *Set* en el código JSX del componente:

```typescript
<button onClick={() => setItem(value)}>Set</button>
```

donde lo que estamos haciendo es que cada vez que se pulse sobre el botón *Set* llamaremos a la función `setItem` que nos proporciona `useLocalStorage` pasándole como valor que queremos establecer en el localStorage lo que tenga el atributo del estado `value` que recordemos que es lo que esté escrito en el `<input>` que se está renderizando en la interfaz.

Si ahora volvemos a nuestra aplicación en el navegador, tecleamos *something* en el cuadro de texto, pulsamos sobre *Set* y nos vamos a pestaña **Aplication** dentro de la herramientas para desarrolladores, desplegando la opción **Local Storage** y seleccionando nuestra aplicación podremos ver que se ha creado la clave `value` y que el valor que tiene asociado es `something` tal y como esperábamos:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714044180885/61e48a80-ea54-45c4-8d77-151aeb2a4d57.png align="center")

Con esto estaremos validando que la función `setItem` que hemos construido en nuestro custom hook está funcionando y que esta nos permite guardar un elemento dentro del localStorage de nuestro navegador.

Pero antes de dar por finaliza esta implementación tenemos que tener cuidado puesto que no todos los navegadores con los que los usuarios acceden a nuestra aplicación van a tener implementado un localStorage y, en esos casos, nuestro código acabaría dando un error puesto que se trataría de una situación que el método `setItem` del objeto `localStorage` de `window` interpreta como que el navegador no tiene más espacio (en realidad no tiene ningún espacio) para guardar un elemento y por lo tanto lanzará un error.

Así pues podemos encerrar el código que está dentro del método `setItem` dentro de un bloque `try-catch` de tal manera que si se produce un error seamos capaces de mostrarle un mensaje al usuario de que ha ocurrido un problema. Con el objetivo de hacer las cosas lo más sencillas posible en nuestra implementación lo que vamos a hacer es que en el caso de que se produzca un error simplemente lo escribiremos por la consola:

```typescript
export const useLocalStorage = (key: string) => {
  const setItem = (value: unknown) => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value))
    } catch (error) {
      console.log(error)
    }
  }

  return {
    setItem
  }
}
```

### Obtener un valor del localStorage

Vamos con la segunda de las funcionalidades que nos tiene que proporcionar `useLocalStorage` que no es otra que el ofrecernos un método que nos permita obtener un valor que haya sido almacenado en el localStorage. Así pues definimos la siguiente función dentro de `useLocalStorage`:

```typescript
export const useLocalStorage = (key: string) => {
  const getItem = () => {
    try {
      const item = windown.localStorage.getItem(key)
      return item ? JSON.parse(item) : undefined
    } catch (error) {
      console.log(error)
    }
  }
  // Resto del código

  return {
    getItem,
    setItem
  }
}
```

Como podemos ver el código de nuestra función `getItem` queda envuelto en una instrucción `try-catch` por los mismos motivos que hemos explicado para el caso de la funicón `setItem` y dentro de ella lo que hacemos es intentar recuperar el *item* que se ha guardado bajo la clave que está asociada nuestro hook dentro del localStorage de tal manera que si existe lo que hacemos es retorna ese valor tras parsearlo como un JSON gracias al método `parse` del objeto `JSON` de JavaScript.

> **Nota:** el método `parse` del objeto `JSON` lo que va a hacer es convertir los posibles arrays que hayamos guardado con el método `stringify` en el método `setItem` a un array de JavaScript o en el caso de haber guardado un objeto a un objeto de JavaScript. Si se hubiese guardado un tipo primitivo este método lo que hace es devolver el tipo primitivo.

Además vemos como en el objeto que retornará `useLocalStorage` además se estará devolviendo el método `getItem` de tal manera que pueda ser utilizado en cualquier sitio donde se esté haciendo uso del hook.

Sabiendo esto podemos volver al código de nuestro componente `Page` y obtener esta función:

```typescript
export const Page = () => {
  const [value, setValue] = useState('')
  const { getItem, setItem } = useLocalStorage('value')
  // ... resto del código
```

y vincularla con el botón *Get* dentro del código JSX de tal manera que cada vez que este sea pulsado se obtenga ese valor y, nuevamente por mantener las cosas sencillas, lo que haremos será mostrar ese valor en la consola del navegador por lo que escribiremos:

```typescript
<button
  onClick={() => {
    console.log(getItem())
  }}
>
  Get
</button>
```

Si ahora volvemos a nuestra aplicación en el navegador y escribimos *something else* en el cuadro de texto, pulsamos sobre *Set* y posteriormente pulsamos sobre *Get* podemos observar como en la consola nos aparecerá el mensaje `something else` tal y como esperamos:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714044278215/15e5e7bb-110e-4aab-bcfe-c60ed9ea4b24.png align="center")

### Eliminar un valor del localStorage

Ya solamente nos queda una función más por implementar dentro de nuestro custom hook y no es otra que aquella que nos permita eliminar un elemento de dentro del localStorage. Así pues nos dirigimos una vez más al código de `useLocalStorage` y definimos lo siguiente:

```typescript
export const useLocalStorage = (key: string) => {
  const removeItem = () => {
    try {
      window.localStorage.removeItem(key)
    } catch (error) {
      console.log(error)
    }
  }

  // Resto del código.

  return {
    getItem,
    removeItem,
    setItem
  }
}
```

Con lo que hemos estado explicando hasta ahora el código anterior parece bastante claro pero por si acaso decir que estamos envolviendo todo el contenido de la función en un bloque `try-catch` para evitar que la aplicación falle y dentro del mismo lo que hacemos es llamar al método `removeItem` que nos proporciona el objeto `localStorage` de `window` que es el encargado de eliminar del localStorage del navegador el valor que esté asociado a la clave que se recibe como parámetro que es precisamente lo que nosotros queremos.

Además estamos añadiendo la función `removeItem` como un método más del objeto que retorna `useLocalStorage` de tal manera que pueda ser usado en el lugar en el que se invoque a nuestro hook cosa que vamos a hacer en componente `Page`:

```typescript
export const Page = () => {
  const [value, setValue] = useState('')
  const { getItem, removeItem, setItem } = useLocalStorage('value')
  // ... resto del código
```

Y ahora lo que hacemos es vincularlo como al evento `onClick` dentro del botón etiquetado como *Remove* dentro del código JSX del componente:

```typescript
<button onClick={() => removeItem()}>Remove</button>
```

Si ahora volvemos a nuestra aplicación si pulsamos sobre el botón `Remove` y posteriormente sobre el botón `Get` veremos que en la consola se nos muestra el valor `undefined` tal y como esperaríamos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714044333237/ddde2e41-3fcd-4c6a-b9fa-ead96099f754.png align="center")

## Resumen

Al final lo que hemos conseguido es tener la funcionalidad que nos permitirá interactuar con el localStorage en un único punto dentro de nuestra aplicación (en el custom hook) `useLocalStorage` de tal manera que dicha funcionalidad va a ser fácilmente reutilizable y mantenible lo cual siempre es una buena práctica de programación.

## Código completo

El código completo del custom hook `useLocalStorage` es el que se recoge a continuación:

```typescript
export const useLocalStorage = (key: string) => {
  const getItem = () => {
    try {
      const item = windown.localStorage.getItem(key)
      return item ? JSON.parse(item) : undefined
    } catch (error) {
      console.log(error)
    }
  }

  const setItem = (value: unknown) => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value))
    } catch (error) {
      console.log(error)
    }
  }

  const removeItem = () => {
    try {
      window.localStorage.removeItem(key)
    } catch (error) {
      console.log(error)
    }
  }

  return {
    getItem,
    removeItem,
    setItem
  }
}
```

Y el código completo del componente que nos ha servido de ejemplo durante la explicación que se ha seguido en el artículo es el siguiente:

```typescript
import { useState } from 'react'
import { useLocalStorage } from './useLocalStorate'

export const Page = () => {
  const [value, setValue] = useState('')
  const { getItem, removeItem, setItem } = useLocalStorage('value')

  return (
    <div>
      <h1 className='mb-2 text-3xl font-bold'>useLocalStorage</h1>
      <input
        className='mb-4'
        onChange={e => setValue(e.target.value)}
        type='text'
        value={value}
      >
      <div className='flex flex-row gap-4'>
        <button onClick={() => setItem(value)}>Set</button>
        <button
          onClick={() => {
            console.log(getItem())
          }}
        >
          Get
        </button>
        <button onClick={() => removeItem()}>Remove</button>
      </div>
    </div>
  )
}
```
