Implementando dark mode no React
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:
- Verificar se o item
theme
está setado nolocalStorage
- Verificar se o usuário já tem alguma preferência de tema na sua máquina ou no seu navegador.
- Caso alguma dessas dê verdadeiro, adicionamos a classe
dark
nobody
e setamos o estado do tema paradark
- Caso contrário removemos a classe
dark
e setamos o estado do tema paralight
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!