La mejor forma de guardar datos en el navegador con React

enero 19, 2023

En este pequeño tutorial hablaremos sobre cómo hacer que los datos de la API de Context en React persistan entre visitas utilizando las herramientas que trae el navegador.

Haremos uno de estos

Context meme

La API de Context en React te permite crear variables “globales” a las que cualquier componente dentro de tu aplicación tiene acceso, pero no almacena nada en la memoria del navegador, recargas y pum!, todas las variables vuelven a su estado inicial.

Empecemos con un Hook personalizado

Almacenaremos nuestros datos en la memoria del navegador con la ayuda de la API de LocalStorage, que está disponible en todos los navegadores.

localStorage por sí solo es bastante básico, sólo recibe textos, así que cuando estás leyendo o escribiendo información de ahí que ir convirtiendo los datos.

Este hook personalizado de sanderdebr que conseguí nos ayudará con eso, utiliza JSON.stringify y JSON.parse para leer y escribir nuestros datos y puede ser utilizado como cualquier otro hook dentro de nuestra aplicación:

//https://dev.to/sanderdebr/building-a-custom-react-localstorage-hook-2bja
const useLocalStorage = (key: string, initialValue: any) => {
    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: any) => {
        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];
};
//Using the custom hook
const [niceValue, setNiceValue] = useLocalStorage("keyGoesHere", 69)

Combinando useLocalStorage con la API Context

Un proveedor típico de la API de Context se ve así. Define valores utilizando useState y luego los pone a disposición de cada componente hijo.

export const DataProvider = ({ children }) => {
    const [counter, setCounter] = useState(0) //Estos valores no persisten entre recargas
    return <DataContext.Provider value={{ counter, setCounter }}>
        {children}
    </DataContext.Provider>
}

Pero ese es aburrido. Si lo combinamos con nuestro useLocalStorage queda así:

export const DataProvider = ({ children }) => {
    //Nuestro hook personalizado para guardar datos en la memoria del navegador
    const [counterStored, setCounterStored] = useLocalStorage("counter", defaultCounterValue)
    const [counter, _setCounter] = useState(defaultCounterValue)

    //La nueva y mejorada función de SetCounter
    const setCounter = (value) => {
        //Cambia tanto el valor en useState y el de 
        //useLocalStorage para mantenerlos sincronizados
        _setCounter(value)
        setCounterStored(value)
    }

    //Inicializa todas las variables de useState con sus contrapartes en localStorage
    useEffect(() => {
        _setCounter(counterStored)
    }, [])

    return <DataContext.Provider value={{ counter, setCounter }}>
        {children}
    </DataContext.Provider>
}

Mucho mejor! podemos seguir utilizándolo como lo haríamos normalmente, pero ahora su estado se mantendrá así cierres la página :O

const Counter = () => {
 const { counter, setCounter } = useDataState() 
 return <div className='counter'>
   <p>{counter}</p>
   <button onClick={() => { setCounter(counter - 1) }}>-1</button>
   <button onClick={() => { setCounter(counter + 1) }}>+1</button>
 </div>
}

¡Ya ta listo! Ahora tienes un super Context API en esteroides.

¿Quieres intentarlo? Aquí está el link con el proyecto completo funcionando.