jdPerez Logo Header
7 minutos de lectura | 24/07/2024

Crear un estado persistente con DarkMode en ReactJS

En este post vamos a ver cómo poder crear un estado persistente trabajando con el local storage del navegador, así además vamos a ver como lo podríamos utilizar para hacer un darkMode en nuestro proyecto.

Primero lo haremos con el useState de ReactJS para ver como este trabaja con el estado de una variable y ya a continuación lo haremos con el local storage.

Vamos a crear un fichero que llamaremos, por ejemplo, themeMode.js. Este fichero tendrá de forma inicial lo siguiente:

/**
 * @author jdperez128
 */
 
import React, {useState} from "react"
 
const ThemeMode = () => {
    const [isDark, setIsDark] = useState('default');
 
    return (
        <>
            <button onClick={() => setIsDark((isDark === 'default') ? 'dark' : 'default')}>
                {'Change mode'}
            </button>
            <span>{'Current mode: '+ isDark}</span>
        </>
    )
}
 
export default ThemeMode
 

Bien, aquí estamos creando nuestro componente ThemeMode, el cual tendrá nuestro estado isDark, de manera inicial. Con el useState le vamos a dar el valor default. Después vemos que he implementado un botón, el cual al hacer click tendrá una función que hará un setState que se encargará de cambiar el valor del estado isDark a dark para avisar de que hay que cambiar el tema del proyecto.

Si probamos esto que tenemos ahora podemos comprobar que el contenido de nuestro <span>{'Current mode: '+ isDark}</span> nos pintará el estado de isDark.

Pero, de momento, esto no nos ayuda en sí a utilizar el darkMode en nuestro, ya que cambiamos el estado pero no provocamos un cambio en nuestra web para cambiar el tema del proyecto.

Ahora bien, vamos a implementar el useEffect de ReactJS, el cual la doc nos dice lo siguiente:

"Al usar este Hook, le estamos indicando a React que el componente tiene que hacer algo después de renderizarse. React recordará la función que le hemos pasado (nos referiremos a ella como nuestro “efecto”), y la llamará más tarde después de actualizar el DOM. En este efecto, actualizamos el título del documento, pero también podríamos hacer peticiones de datos o invocar alguna API imperativa."

Con el useEffect provocaremos un cambio en el dom para ayudarnos a detectar el darkMode y así hacer los cambios necesarios en el proyecto, el código ahora quedaría tal que así:

/**
 * @author jdperez128
 */
 
import React, {useEffect, useState} from "react"
 
const ThemeMode = () => {
    const [isDark, setIsDark] = useState('default');
 
    useEffect(() => {
        if (isDark === 'dark') {
            document.documentElement.classList.add('dark')
        } else {
            document.documentElement.classList.remove('dark')
        }
    }, [isDark]);
 
    return (
        <>
            <button onClick={() => setIsDark((isDark === 'default') ? 'dark' : 'default')}>
                {'Change mode'}
            </button>
            <span>{'Current mode: '+ isDark}</span>
        </>
    )
}
 
export default ThemeMode

De este modo cambiaremos en el dom nuestro elemento html añadiendo la clase dark, yo en mi caso tengo unas variables definidas con SASS en las cuales tengo definidas las variables para el tema por defecto o para el tema de dark, ya con esto podríamos a través de CSS trabajar y cambiar el tema para hacer el darkMode a nuestro gusto.

Ahora bien, esto si recargamos o no movemos de página, el estado volverá siempre a su estado inicial, ¿cómo podemos hacer para tener siempre el estado anterior?, tendremos que trabajar con el localStorage de nuestro navegador almacenando el estado de la variable isDark.

Para ello podemos hacer lo siguiente:

const [isDark, setIsDark] = useState(() => {
        const storedState = localStorage.getItem('themeMode');
 
        return storedState ?? 'default';
    });
 
    useEffect(() => {
        localStorage.setItem('themeMode', isDark);
        if (isDark === 'dark') {
            document.documentElement.classList.add('dark')
        } else {
            document.documentElement.classList.remove('dark')
        }
    }, [isDark]);

Cambiando el useState y el useEffect por lo anterior debería de ser suficiente, ahora el useState buscará un item dentro del local storage y asignará al estado el valor que recoja del local, siempre y cuando la key del item exista.

Después en el useEffect este mismo se encargará de detectar el nuevo valor que le llega y crear/actualizar la key del item en el local storage.

Esto sería la forma de crear nuestro estado persistente y además hacer un darkMode.