A maioria dos componentes é construída para o cenário ideal. Eles funcionam — até que param de funcionar. O mundo real é hostil. Renderização no servidor. Hidratação. Instâncias múltiplas. Renderização simultânea. Componentes filhos assíncronos. Portais… Seu componente pode enfrentar todos esses desafios. A questão é se ele sobreviverá.
O verdadeiro teste não é se o seu componente funciona na página atual. É se ele funciona quando outra pessoa o utiliza — em condições para as quais você não previu. É aí que os componentes frágeis falham.
Eis como fazê-lo sobreviver .
Torne-o à prova de servidor.
Um provedor de temas simples que lê as preferências do usuário a partir de localStorage:
function ThemeProvider({ children }) {
const [theme, setTheme] = useState(
localStorage.getItem('theme') || 'light'
)
return <div className={theme}>{children}</div>
}
Falhas no SSR — lê o tema do localStorage
Mas localStoragenão existe no servidor. No Next.js, Remix ou qualquer framework SSR, isso causa uma falha na compilação. Mova as APIs do navegador para useEffect:
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
useEffect(() => {
setTheme(localStorage.getItem('theme') || 'light')
}, [])
return <div className={theme}>{children}</div>
}
Agora ele é renderizado no servidor sem travar.
☕
Café com Deus Pai Vol. 6 - 2026: Porções Diárias de Amor
oferece 365 mensagens diárias que convidam você a um encontro íntimo com Deus, fortalecendo a fé e nutrindo a alma.
👉
Confira na Amazon
.
Nas compras você contribui para manter o site no ar.
Torne-o à prova de hidratação.
Eu também chamo isso de à prova d’água. A versão segura para servidor funciona, mas os usuários veem um flash. O servidor renderiza light, o cliente carrega, então o efeito é executado e muda para dark:
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
useEffect(() => {
setTheme(localStorage.getItem('theme') || 'light')
}, [])
return <div className={theme}>{children}</div>
}
Aqui, flash de tema errado — useEffect é executado após a hidratação
Injete um script síncrono que defina o valor correto
antes que o navegador renderize a página e o React seja carregado. O DOM já terá a classe correta quando o React assumir o controle.
function ThemeProvider({ children }) {
return (
<>
<div id="theme">{children}</div>
<script dangerouslySetInnerHTML={{ __html: `
try {
const theme = localStorage.getItem('theme') || 'light'
document.getElementById('theme').className = theme
} catch (e) {}
`}} />
</>
)
}
Aqui, o script embutido define o tema antes que o navegador renderize a imagem.
Sem incompatibilidade, sem flash.
Torne-o à prova de instâncias
A versão à prova de hidratação tem como alvo um valor fixo . Mas e se alguém usar dois valores fixos?id="theme"ThemeProvider
function App() {
return (
<>
<ThemeProvider><MainContent /></ThemeProvider>
<AlwaysLightThemeContent />
<ThemeProvider><Sidebar /></ThemeProvider>
</>
)
}
Aqui, múltiplas instâncias — ambos os scripts têm como alvo o mesmo ID.
Ambos os scripts disputam o mesmo elemento. Use useIdpara gerar IDs estáveis e exclusivos por instância:
function ThemeProvider({ children }) {
const id = useId()
return (
<>
<div id={id}>{children}</div>
<script dangerouslySetInnerHTML={{ __html: `
try {
const theme = localStorage.getItem('theme') || 'light'
document.getElementById('${id}').className = theme
} catch (e) {}
`}} />
</>
)
}
Agora, várias instâncias coexistem em segurança.
Torne-o à prova de concorrência
Agora vamos tornar o tema controlado pelo servidor. Um componente de servidor que busca as preferências do usuário:
async function ThemeProvider({ children }) {
const prefs = await db.preferences.get(userId)
return <div className={prefs.theme}>{children}</div>
}
Assim como antes, renderizar a consulta em dois locais diferentes pode resultar em duas consultas idênticas ao banco de dados. Para eliminar as duplicatas em uma única requisição, envolva a consulta em um bloco `try-catch`.React.cache
import { cache } from 'react'
const getPreferences = cache(
userId => db.preferences.get(userId)
)
async function ThemeProvider({ children }) {
const prefs = await getPreferences(userId)
return <div className={prefs.theme}>{children}</div>
}
Nota lateral:O cache() do React remove chamadas simultâneas duplicadas.
A mesma consulta, executada de qualquer lugar, acessa o banco de dados apenas uma vez.
Torne-o à prova de contaminação
Às vezes, você deseja passar dados para os filhos como props, o que tradicionalmente significava usar :React.cloneElement
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return React.Children.map(children, (child) => {
return React.cloneElement(child, { theme })
})
}
Nota lateral:Passa o tema para os filhos através do cloneElement.
Mas com componentes de servidor React , ` this` ou `react-server` podem ser uma Promise ou uma referência opaca — não funcionarão. Use o contexto em vez disso:React.lazy"use cache"childrencloneElement
const ThemeContext = createContext('light')
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
)
}
Nota lateral:O contexto funciona em todos os lugares — servidor, cliente, assíncrono
As crianças leem o tema do início ao fim useContext— sem perfuração de adereços, sem clonagem.
Torne-o à prova de portais.
Um provedor de temas com um atalho de teclado para ativar/desativar o modo escuro:Cmd+D
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
useEffect(() => {
const toggle = (e) => {
if (e.metaKey && e.key === 'd') {
e.preventDefault()
setTheme(t => t === 'dark' ? 'light' : 'dark')
}
}
window.addEventListener('keydown', toggle)
return () => window.removeEventListener('keydown', toggle)
}, [])
return <div className={theme}>{children}</div>
}
Nota lateral:Atalho de teclado global para alternar o tema
Mas se alguém renderizar o aplicativo dentro de uma janela pop-up, iframe ou via createPortal, o atalho para de funcionar. O ouvinte está associado ao componente pai window, não ao componente em que seu componente está inserido. Use :ownerDocument.defaultView
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const ref = useRef(null)
useEffect(() => {
const win = ref.current?.ownerDocument.defaultView || window
const toggle = (e) => {
if (e.metaKey && e.key === 'd') {
e.preventDefault()
setTheme(t => t === 'dark' ? 'light' : 'dark')
}
}
win.addEventListener('keydown', toggle)
return () => win.removeEventListener('keydown', toggle)
}, [])
return <div ref={ref} className={theme}>{children}</div>
}
Nota lateral:ownerDocument.defaultView encontra a janela correta.
Agora o atalho funciona em qualquer contexto de janela.
Torne-o à prova de transições
Um painel de configurações que alterna entre visualizações simples e avançadas:
function ThemeSettings() {
const [showAdvanced, setShowAdvanced] = useState(false)
return (
<>
{showAdvanced ? <AdvancedPanel /> : <SimplePanel />}
<button onClick={() => setShowAdvanced(!showAdvanced)}>
{showAdvanced ? 'Simple' : 'Advanced'}
</button>
</>
)
}
Nota lateral:Alternância simples entre dois painéis.
Envolva-o em um componente do React 19 e nada será animado — os painéis simplesmente se encaixarão. As atualizações de estado devem passar por :<ViewTransition>startTransition
function ThemeSettings() {
const [showAdvanced, setShowAdvanced] = useState(false)
return (
<>
{showAdvanced ? <AdvancedPanel /> : <SimplePanel />}
<button onClick={() =>
startTransition(() => setShowAdvanced(!showAdvanced))
}>
{showAdvanced ? 'Simple' : 'Advanced'}
</button>
</>
)
}
Nota lateral:startTransition habilita a transição de visualização
Agora a transição ocorre de forma suave.
Torne-o à prova de atividades
Um componente de tema que injeta variáveis CSS através de uma tag:<style>
function DarkTheme({ children }) {
return (
<>
<style>{`
:root {
--bg: #000;
--fg: #fff;
}
`}</style>
{children}
</>
)
}
Nota lateral:Injeta variáveis CSS globais através da tag de estilo.
Mas se você envolver o elemento em `<div>` , o tema escuro persiste mesmo quando oculto. `<div>` preserva o DOM e tem efeitos colaterais em nível de DOM — ele modifica variáveis globalmente. O React não consegue limpar esses efeitos colaterais automaticamente. Defina ` disable-styles` para desativar os estilos quando o elemento estiver oculto:<Activity><Activity><style>:rootmedia="not all"
function DarkTheme({ children }) {
const ref = useRef(null)
useLayoutEffect(() => {
if (!ref.current) return
ref.current.media = 'all'
return () => ref.current.media = 'not all'
}, [])
return (
<>
<style ref={ref}>{`
:root {
--bg: #000;
--fg: #fff;
}
`}</style>
{children}
</>
)
}
Nota lateral:useLayoutEffect define media=’not all’ quando oculto e restaura-o quando exibido novamente.
Agora, os componentes ocultos não terão o tema escuro aplicado.
Torne-o à prova de vazamentos
Um componente de servidor que passa um userobjeto (incluindo um token de sessão) para outro componente de tema. Caso de uso válido: você precisa dos dados no servidor. Você provavelmente sabe UserThemeConfigque se trata de um componente de servidor e que é seguro passar os dados para ele.
async function Dashboard() {
const user = await getUser()
return <UserThemeConfig user={user} />
}
Nota lateral:O painel de controle encaminha o usuário (com o token) para outro componente.
No entanto, você não conhece UserThemeConfigo comportamento exato do componente, o que ele renderiza ou o que uma versão futura poderá fazer. Você não é responsável pela manutenção dele.
Além disso, como o componente UserThemeConfignão cria um objeto user, ele pode não saber que o usertoken possui uma propriedade sensível token. Você não controla esse componente, portanto, não pode presumir que ele não passará esse token para um componente cliente em algum lugar em sua árvore de componentes. O token é serializado e enviado para o cliente. Use o recurso experimental do React taintUniqueValuepara marcar o token como exclusivo do servidor. Se esse valor for passado para um componente cliente, o React lançará uma exceção. Para bloquear um objeto inteiro em vez de um único valor, use ` taintObjectReference.
import { experimental_taintUniqueValue } from 'react'
async function Dashboard() {
const user = await getUser()
experimental_taintUniqueValue(
'Do not pass the user token to the client.',
user,
user.token
)
return <UserThemeConfig user={user} />
}
Nota lateral:O parâmetro `taintUniqueValue` impede que o token do usuário seja enviado ao cliente.
Se o código desse componente (ou uma futura refatoração feita por outro membro da equipe) tentar passar o token para um componente cliente, o React lançará uma exceção com a sua mensagem. O caso de uso válido permanece; o token nunca vaza.user.token
Prepare-o para o futuro *
É importante entender o conceito de ser defensivo. Não se trata de um padrão que se aplica a todas as situações.
Um tema que gera cores de destaque aleatórias na tela:
function ThemeProvider({ baseTheme, children }) {
const colors = useMemo(
() => getRandomColors(baseTheme),
[baseTheme]
)
return <div style={colors}>{children}</div>
}
Nota lateral:useMemo armazena em cache as cores geradas.
Mas useMemoisso é uma dica de desempenho, não uma garantia semântica . O React descarta valores em cache durante a atualização de memória de alto nível (HMR) e reserva-se o direito de fazê-lo para componentes fora da tela ou recursos que ainda não existem. Se o React descartar o cache, seu tema piscará com cores diferentes. Use o estado quando a correção depender da persistência.
function ThemeProvider({ baseTheme, children }) {
const [colors, setColors] = useState(() => generateAccentColors(baseTheme))
const [prevTheme, setPrevTheme] = useState(baseTheme)
if (baseTheme !== prevTheme) {
setPrevTheme(baseTheme)
setColors(generateAccentColors(baseTheme))
}
return <div style={colors}>{children}</div>
}
Nota lateral:useState fornece garantia de persistência semântica
Agora as cores permanecem estáveis independentemente das otimizações internas do React.
Esses não são casos extremos. São o novo normal. Os componentes que quebram? Eles não eram frágeis. Foram construídos para o React de ontem. Estamos construindo para o de amanhã.
☕
Café com Deus Pai Vol. 6 - 2026: Porções Diárias de Amor
oferece 365 mensagens diárias que convidam você a um encontro íntimo com Deus, fortalecendo a fé e nutrindo a alma.
👉
Confira na Amazon
.
Nas compras você contribui para manter o site no ar.
