Quando usar useMemo e useCallback
Vou explicar quando e como usar useMemo e useCallback corretamente. É uma dúvida muito comum e importante para otimização de performance no React.
🎯 Quando usar useMemo
✅ USE quando:
- Cálculos pesados que dependem de props/state: const ExpensiveComponent = ({ data, filter }) => { // ❌ Roda toda vez que o componente re-renderiza const filteredData = data.filter(item => item.category === filter)
// ✅ Só roda quando data ou filter mudam
const filteredData = useMemo(
() => data.filter(item => item.category === filter),
[data, filter]
)
}
- Objetos/arrays que são passados como props: const Parent = ({ users }) => { // ❌ Cria novo objeto toda vez = filho re-renderiza const config = { showAvatar: true, size: 'large' }
// ✅ Mesmo objeto = filho não re-renderiza
const config = useMemo(
() => ({ showAvatar: true, size: 'large' }),
[]
)
return <UserList users={users} config={config} />
}
❌ NÃO USE quando:
- Valores primitivos simples:
// ❌ Desnecessário - string é barata
const text = useMemo(() =>
Hello ${name}
, [name])
// ✅ Mais simples e performático
const text = Hello ${name}
🎯 Quando usar useCallback
✅ USE quando:
- Funções passadas para componentes filhos: const Parent = ({ items }) => { // ❌ Nova função toda vez = filho re-renderiza const handleClick = (id) => console.log(id)
// ✅ Mesma função = filho não re-renderiza
const handleClick = useCallback(
(id) => console.log(id),
[]
)
return items.map(item =>
<ExpensiveChild key={item.id} onClick={handleClick} />
)
}
- Dependências de outros hooks: const SearchComponent = ({ query }) => { // ❌ useEffect roda toda vez const fetchData = () => api.search(query)
useEffect(() => {
fetchData()
}, [fetchData]) // fetchData muda toda vez
// ✅ useEffect só roda quando query muda
const fetchData = useCallback(
() => api.search(query),
[query]
)
useEffect(() => {
fetchData()
}, [fetchData])
}
❌ NÃO USE quando:
- Função não é passada para filhos: // ❌ Desnecessário const handleClick = useCallback(() => { setCount(c => c + 1) }, [])
// ✅ Mais simples const handleClick = () => { setCount(c => c + 1) }
📝 Exemplos Práticos do Nosso Projeto
- useMemo no useGitHubSearch:
// ✅ Evita recriar array toda vez const repos = useMemo(() => data || [], [data])
// ✅ Evita recalcular toda vez const hasNextPage = useMemo(() => repos.length === 12, [repos.length])
- useCallback no SearchBox:
// ✅ Evita recriar função quando SearchBox re-renderiza const handleInputChange = useCallback((e) => { setUsername(e.target.value) }, [setUsername])
- useMemo no RepoList:
// ✅ Evita recalcular condição complexa const showPagination = useMemo( () => !isLoading && !error && repos.length > 0 && (hasNextPage || hasPrevPage), [isLoading, error, repos.length, hasNextPage, hasPrevPage] )
// ✅ Evita recriar skeleton items const skeletonItems = useMemo(() => Array.from({ length: 6 }).map((_, i) => ( )), [] // Empty deps = só cria uma vez )
🚨 Regra de Ouro
"Não otimize prematuramente"
- Primeiro: Escreva código limpo e funcional
- Depois: Meça performance (React DevTools Profiler)
- Só então: Otimize onde realmente precisar
🔍 Como Identificar Onde Usar
- React DevTools Profiler - mostra componentes que re-renderizam muito
- Console.log em renders - vê quantas vezes roda
- Performance - se notou lentidão específica
⚖️ Custo vs Benefício
// ❌ Overhead desnecessário const simpleValue = useMemo(() => x + y, [x, y])
// ✅ Benefício real const expensiveCalculation = useMemo(() => { return heavyProcessing(largeDataSet) }, [largeDataSet])
Resumo: Use quando o benefício de evitar recálculo/re-render for maior que o overhead do hook. Na dúvida, comece sem e adicione se precisar!
Enjoyed this post?
If you found this article helpful, consider sharing it with others who might benefit.