Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useRef, useEffect } from "react";
/**
* usePrevious
* Retorna o valor da renderização anterior.
* Retorna `undefined` na primeira renderização.
*/
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
/**
* usePreviousDistinct
* Atualiza apenas quando o valor realmente muda
* (pula re-renderizações onde o valor permanece o mesmo).
*/
function usePreviousDistinct<T>(
value: T,
isEqual: (a: T, b: T) => boolean = (a, b) => a === b
): T | undefined {
const prevRef = useRef<T | undefined>(undefined);
const currentRef = useRef<T>(value);
if (!isEqual(currentRef.current, value)) {
prevRef.current = currentRef.current;
currentRef.current = value;
}
return prevRef.current;
}Quando usar isso: Você precisa comparar os valores atuais e anteriores para detectar a direção da mudança, acionar animações, implementar desfazer ou pular efeitos redundantes.
"use client";
import { useState } from "react";
// Detectar direção da animação
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
const direction =
prevCount === undefined
? "initial"
: count > prevCount
? "up"
: count < prevCount
? "down"
: "same";
return (
<div>
<p>
Contador: {count} (era: {prevCount ?? "N/A"})
</p>
<p>Direção: {direction}</p>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
<button onClick={() => setCount((c) => c - 1)}>-1</button>
</div>
);
}
// Detectar alterações de rota
function RouteChangeDetector({ pathname }: { pathname: string }) {
const prevPathname = usePrevious(pathname);
useEffect(() => {
if (prevPathname && prevPathname !== pathname) {
console.log(`Navegou de ${prevPathname} para ${pathname}`);
// Rastrear visualização de página, rolar para o topo, etc.
}
}, [pathname, prevPathname]);
return null;
}
// Desfazer simples para um campo de texto
function UndoableInput() {
const [text, setText] = useState("");
const prevText = usePrevious(text);
const undo = () => {
if (prevText !== undefined) {
setText(prevText);
}
};
return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Digite algo..."
/>
<button onClick={undo} disabled={prevText === undefined}>
Desfazer
</button>
<p style={{ color: "#999" }}>Anterior: {prevText ?? "nenhum"}</p>
</div>
);
}O que isso demonstra:
pathname entre renderizaçõesuseRef persiste entre as renderizações sem causar re-renderizações. O useEffect é executado após a renderização, então durante a renderização ref.current ainda contém o valor da renderização anterior.ref.current contém o valor da renderização N-1. Após a conclusão da renderização N, o efeito atualiza ref.current para o valor da renderização N.ref.current é undefined porque nenhum valor anterior existe ainda.| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
value | T | - | O valor a ser rastreado |
| Retorna | T ou undefined | - | Valor da renderização anterior, ou undefined na primeira renderização |
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
value | T | - | O valor a ser rastreado |
isEqual | (a: T, b: T) => boolean | === | Verificação de igualdade personalizada |
| Retorna | T ou undefined | - | Valor distinto anterior |
Com valor inicial: Evite undefined na primeira renderização:
function usePrevious<T>(value: T, initialValue: T): T {
const ref = useRef<T>(initialValue);
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}Pilha de histórico: Rastreie múltiplos valores anteriores para desfazer em vários níveis:
function usePreviousValues<T>(value: T, maxHistory: number = 10): T[] {
const historyRef = useRef<T[]>([]);
useEffect(() => {
historyRef.current = [value, ...historyRef.current].slice(0, maxHistory);
}, [value, maxHistory]);
return historyRef.current.slice(1); // Exclui o valor atual
}Comparação de objetos: Para objetos, use uma função de comparação profunda:
const prevUser = usePreviousDistinct(user, (a, b) =>
JSON.stringify(a) === JSON.stringify(b)
);T | undefined faz com que o consumidor lide com o caso da renderização inicial. Use a variação "com valor inicial" para evitar isso.T é inferido do argumento, portanto, um parâmetro de tipo explícito geralmente não é necessário.usePreviousDistinct aceita um comparador personalizado tipado como (a: T, b: T) => boolean.usePreviousDistinct com uma comparação profunda ou memoize o valor upstream.undefined é retornado. Se undefined for um valor válido para seus dados, você não poderá distinguir "sem valor anterior" de "o valor anterior era undefined". Correção: Use a variação "com valor inicial" ou envolva em um objeto { hasPrevious, value }.usePrevious rastreia apenas um passo para trás. Correção: Use a variação de pilha de histórico para desfazer em vários níveis.| Pacote | Nome do Hook | Notas |
|---|---|---|
usehooks-ts | usePrevious | Implementação idêntica |
@uidotdev/usehooks | usePrevious | Mínimo |
ahooks | usePrevious | Suporta comparação personalizada |
react-use | usePrevious | Baseado em ref simples |
| Documentação do React | - | Recomendado como um exemplo de custom hook |
undefined e o useEffect que o atualiza é executado após a renderização.undefined é o retorno correto.ref.current ainda contém o valor da renderização N-1 porque useEffect ainda não foi executado.ref.current para o valor atual, pronto para a renderização N+1.usePrevious atualiza a cada renderização, mesmo que o valor não tenha mudado.usePreviousDistinct atualiza apenas quando o valor realmente muda (com base em uma verificação de igualdade), pulando re-renderizações redundantes de componentes pais.Use uma variação de pilha de histórico:
function usePreviousValues<T>(value: T, max = 10): T[] {
const historyRef = useRef<T[]>([]);
useEffect(() => {
historyRef.current = [value, ...historyRef.current].slice(0, max);
}, [value, max]);
return historyRef.current.slice(1);
}Use a variação "com valor inicial":
function usePrevious<T>(value: T, initialValue: T): T {
const ref = useRef<T>(initialValue);
useEffect(() => { ref.current = value; }, [value]);
return ref.current;
}usePrevious captura essa nova referência, mesmo que os dados sejam os mesmos.usePreviousDistinct com uma comparação profunda ou memoize o valor upstream com useMemo.undefined, o que é ambíguo.{ hasPrevious: boolean, value: T }.count com prevCount: se count > prevCount a direção é "up" (para cima), se for menor é "down" (para baixo).prevCount é undefined, então a direção é "initial" (inicial).T | undefined, forçando o consumidor a lidar com o caso da primeira renderização.T é inferido do argumento, então um parâmetro de tipo explícito geralmente não é necessário.const prev = usePrevious(42);
// prev é number | undefinedO comparador é tipado como (a: T, b: T) => boolean:
const prevUser = usePreviousDistinct(user, (a, b) =>
a.id === b.id && a.name === b.name
);Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥