Implementando dark mode no React

15 de janeiro de 20214 minutos de leitura

Em diversas aplicações, um dos recursos mais solicitados é o dark mode. Vemos o dark mode nos aplicativos que usamos todos os dias. De aplicativos móveis a web, o dark mode se tornou vital para empresas que desejam cuidar dos olhos de seus usuários.

Eu particularmente adoro o dark mode. Se eu vejo que um app ou site tem dark mode disponível eu ativo na hora. Inclusive implementei exatamente do mesmo modo neste site.

E isso não é apenas uma preferência pessoal. Muitas empresas estão implementando o dark mode em suas aplicações, como o YouTube e o Twitter, por exemplo.

Além disso, o dark mode pode evitar muitas dores de cabeça que telas claras costumam causar em pessoas com hipersensibilidade à luz.

Então vamos colocar logo a mão na massa e parar de encher linguiça!

Implementando o Dark Mode

Pra começar vamos criar um projeto genérico em React com o create-react-app

npx create-react-app meu-app-dark-mode
cd meu-app-dark-mode

Para a nossa abordagem, vou persistir o tema escolhido no localStorage, além de usar a Context API para compartilhar o estado entre os componentes da nossa aplicação.

Vamos começar criando o nosso contexto, Crie um arquivo ThemeContext.js e vamos começar a codar!

O primeiro passo é criar o nosso contexto. Isso é essencial para que possamos mais tarde saber qual o tema em qualquer componente dentro do nosso app.

import React, { createContext, useState, useEffect } from 'react'

export const ThemeContext = createContext()

Neste mesmo arquivo vamos criar o nosso componente que vai “prover” o contexto para a aplicação. O nosso estado inicial será o tema claro, então vamos declarar o isso.

const [theme, setTheme] = useState('light')

Logo após, vamos tentar entender as preferências do usuários e ver se ele já definiu o tema antes ou se a máquina dele usa dark mode, para automaticamente nos ajustarmos a isso.

useEffect(() => {
    if (
      localStorage.getItem('theme') === 'dark' ||
      (!('theme' in localStorage) &&
        window.matchMedia('(prefers-color-scheme: dark)').matches)
    ) {
      document.querySelector('html').classList.add('dark')
      setTheme('dark')
    } else {
      document.querySelector('html').classList.remove('dark')
      setTheme('light')
    }
}, [])

No código acima o que eu fiz foi:

  1. Verificar se o item theme está setado no localStorage
  2. Verificar se o usuário já tem alguma preferência de tema na sua máquina ou no seu navegador.
  3. Caso alguma dessas dê verdadeiro, adicionamos a classe dark no body e setamos o estado do tema para dark
  4. Caso contrário removemos a classe dark e setamos o estado do tema para light

Agora vamos criar a função que vai trocar o tema de claro para escuro e vice-versa. Vamos ter acesso à ela em qualquer lugar do app, assim como o próprio valor do estado.

Essa função segue praticamente a mesma lógica do efeito acima.

function toggleThemeMode() {
   if (
      !localStorage.getItem('theme') ||
      localStorage.getItem('theme') === 'light'
   ) {
      localStorage.theme = 'dark'
      document.querySelector('html').classList.add('dark')
      setTheme('dark')
   } else {
      localStorage.theme = 'light'
      document.querySelector('html').classList.remove('dark')
      setTheme('light')
   }
}

O que nós vamos retornar disso é um Provider com um objeto que contém o valor do tema e a função que muda o tema. Isso é fornecer o contexto.

Fornecer contexto significa disponibilizar o valor de contexto criado para componentes filhos no componente pai onde foi inicializado.

Isso é feito envolvendo os componentes filhos com o “provedor do contexto” criado.

return (
    <ThemeContext.Provider value={{ theme, toggleThemeMode }}>
      {children}
    </ThemeContext.Provider>
)

Veja como ficou o nosso arquivo ThemeContext.js

//ThemeContext.js
import React, { createContext, useState, useEffect } from 'react'

export const ThemeContext = createContext()

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light')

  useEffect(() => {
    if (
      localStorage.getItem('theme') === 'dark' ||
      (!('theme' in localStorage) &&
        window.matchMedia('(prefers-color-scheme: dark)').matches)
    ) {
      document.querySelector('html').classList.add('dark')
      setTheme('dark')
    } else {
      document.querySelector('html').classList.remove('dark')
      setTheme('light')
    }
  }, [])

  function toggleThemeMode() {
    if (
      !localStorage.getItem('theme') ||
      localStorage.getItem('theme') === 'light'
    ) {
      localStorage.theme = 'dark'
      document.querySelector('html').classList.add('dark')
      setTheme('dark')
    } else {
      localStorage.theme = 'light'
      document.querySelector('html').classList.remove('dark')
      setTheme('light')
    }
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleThemeMode }}>
      {children}
    </ThemeContext.Provider>
  )
}

export default ThemeProvider

Utilizando o contexto

Mas obviamente, não acaba por aí. Agora precisamos usar isso em algum lugar do nosso app.

Como o tema normalmente é uma configuração global, basta envolver todo o nosso app com o provider que criamos. Para isso, importamos o ThemeContext criado anteriormente e adicionamos ele no App.

//App.js
import React from 'react'
import ThemeProvider from '@components/theme/ThemeProvider'

const App = () => {
  return (
    <div className="App">
      <ThemeProvider>
          //seu app vem aqui
      </ThemeProvider>
    </div>
  );
}

Agora precisamos fazer mais duas coisinhas: ler os valores do tema e mudá-los livremente. Vamos então, criar um botão para trocar o tema entre claro e escuro.

import React, { useEffect, useState, useContext } from 'react'
import { ThemeContext } from '@components/theme/themeContext'

const toggleTheme = () => {
 const context = useContext(ThemeContext)

return (
    <button onClick={context.toggleThemeMode}>
       Trocar tema!
    </button>
  )
}

Dessa forma estamos praticamente terminados! Para receber os valores do contexto em seus outros componentes basta importar o contexto, como fiz acima. O contexto é exatamente aquele objeto que criamos mais cedo com o context e o toggleThemeMode

Conclusão

Neste artigo, você aprendeu como adicionar um modo claro e um modo escuro e um botão que alterna entre ambos os modos a um aplicativo React. E também o que a Context API faz e como usá-la.

Agora, é a sua vez de desenvolver, use isso e construa algo incrível!


Autor

Gustavo Rocha
Gustavo Rocha
Sócio e CTO da onSERP Marketing, marketeiro de formação e amante dos bixinhos. Apaixonado pela profissão. Encontrei meu propósito em desenvolver soluções inovadoras para clientes.