Cuándo evitar usar useEffect en React para optimizar tu código

En los proyectos React, a menudo usamos useEffect por reflejo, pero este enfoque puede a veces complicar tu código innecesariamente. En este artículo, veremos casos donde no necesitas useEffect y cómo optimizar tu código evitando usarlo incorrectamente.

Entender el papel de useEffect

El hook useEffect es útil para manejar efectos secundarios como suscripciones u operaciones que deben ejecutarse fuera del flujo principal de renderizado. Sin embargo, algunos casos de uso comunes pueden simplificarse sin useEffect.

1. Inicializar el estado sin useEffect

A menudo quieres inicializar un estado basado en props o valores iniciales al montar el componente. En lugar de usar useEffect para definir este estado, puedes hacerlo directamente con useState, lo que hará tu código más claro y prevendrá llamadas innecesarias.

Mala práctica con useEffect:

typescript
1import { useState, useEffect } from "react";
2
3interface GreetingProps {
4 name: string;
5}
6
7function Greeting({ name }: GreetingProps) {
8 const [greeting, setGreeting] = useState<string>("");
9
10 useEffect(() => {
11 setGreeting(`Hola, ${name}`);
12 }, [name]);
13
14 return <h1>{greeting}</h1>;
15}

Buena práctica sin useEffect:

Aquí, podemos simplemente inicializar el estado greeting usando una función en useState:

typescript
1import { useState } from "react";
2
3interface GreetingProps {
4 name: string;
5}
6
7function Greeting({ name }: GreetingProps) {
8 const [greeting] = useState<string>(() => `Hola, ${name}`);
9
10 return <h1>{greeting}</h1>;
11}

Este enfoque hace el código más conciso y evita añadir un efecto superfluo que podría ralentizar tu aplicación.

2. Calcular valores derivados

Si necesitas calcular un valor en función del estado o las props, intenta usar una variable derivada o un memo en lugar de useEffect.

Ejemplo con useEffect (a evitar):

typescript
1import { useState, useEffect } from "react";
2
3interface CartItem {
4 price: number;
5}
6
7interface CartProps {
8 items: CartItem[];
9}
10
11function Cart({ items }: CartProps) {
12 const [total, setTotal] = useState<number>(0);
13
14 useEffect(() => {
15 setTotal(items.reduce((sum, item) => sum + item.price, 0));
16 }, [items]);
17
18 return <p>Total: ${total}</p>;
19}

Solución sin useEffect:

Aquí, el valor total puede calcularse directamente durante el renderizado, sin depender de useEffect:

typescript
1interface CartItem {
2 price: number;
3}
4
5interface CartProps {
6 items: CartItem[];
7}
8
9function Cart({ items }: CartProps) {
10 const total = items.reduce((sum, item) => sum + item.price, 0);
11
12 return <p>Total: ${total}</p>;
13}

Este método no solo es más limpio, sino que elimina la necesidad de manejar estados adicionales.

3. Usar useMemo para cálculos pesados

Para cálculos costosos o dependientes de varias variables, usa useMemo para evitar reejecutar la lógica en cada renderizado, sin necesidad de useEffect para manejar el estado.

Ejemplo con useEffect:

typescript
1import { useState, useEffect } from "react";
2
3interface ExpensiveCalculationProps {
4 num: number;
5}
6
7function ExpensiveCalculation({ num }: ExpensiveCalculationProps) {
8 const [result, setResult] = useState<number>(0);
9
10 useEffect(() => {
11 const computation = heavyComputation(num);
12 setResult(computation);
13 }, [num]);
14
15 return <p>Resultado: {result}</p>;
16}
17
18function heavyComputation(num: number): number {
19 // Simula un cálculo costoso
20 return num * 2; // Ejemplo simplificado
21}

Enfoque optimizado con useMemo:

typescript
1import { useMemo } from "react";
2
3interface ExpensiveCalculationProps {
4 num: number;
5}
6
7function ExpensiveCalculation({ num }: ExpensiveCalculationProps) {
8 const result = useMemo(() => heavyComputation(num), [num]);
9
10 return <p>Resultado: {result}</p>;
11}
12
13function heavyComputation(num: number): number {
14 // Simula un cálculo costoso
15 return num * 2; // Ejemplo simplificado
16}

Aquí, useMemo permite memorizar el resultado sin tener que manejar un efecto, lo que hace el componente más predecible y eficiente.

4. Sincronizar las propiedades de un componente controlado

Para manejar entradas controladas, no necesitas usar useEffect para sincronizar los valores de los inputs con el estado del componente padre. Simplemente usa las props y las funciones de callback para manejar este estado eficientemente.

Ejemplo con useEffect (innecesario):

typescript
1import { useState, useEffect } from "react";
2
3interface TextInputProps {
4 value: string;
5 onChange: (value: string) => void;
6}
7
8function TextInput({ value, onChange }: TextInputProps) {
9 const [text, setText] = useState<string>(value);
10
11 useEffect(() => {
12 setText(value);
13 }, [value]);
14
15 return <input value={text} onChange={(e) => onChange(e.target.value)} />;
16}

Solución sin useEffect:

typescript
1interface TextInputProps {
2 value: string;
3 onChange: (value: string) => void;
4}
5
6function TextInput({ value, onChange }: TextInputProps) {
7 return <input value={value} onChange={(e) => onChange(e.target.value)} />;
8}

El uso directo de las props simplifica el código y mejora el rendimiento al reducir el número de actualizaciones de estado innecesarias.

5. Manipular el DOM directamente en el renderizado

Si quieres aplicar estilos o clases basados en el estado, a menudo es innecesario usar useEffect. React maneja muy bien este tipo de sincronización durante el renderizado.

Ejemplo con useEffect:

typescript
1import { useState, useEffect } from "react";
2
3interface HighlightedTextProps {
4 isHighlighted: boolean;
5 text: string;
6}
7
8function HighlightedText({ isHighlighted, text }: HighlightedTextProps) {
9 const [className, setClassName] = useState<string>("");
10
11 useEffect(() => {
12 setClassName(isHighlighted ? "highlight" : "");
13 }, [isHighlighted]);
14
15 return <span className={className}>{text}</span>;
16}

Solución sin useEffect:

typescript
1interface HighlightedTextProps {
2 isHighlighted: boolean;
3 text: string;
4}
5
6function HighlightedText({ isHighlighted, text }: HighlightedTextProps) {
7 return <span className={isHighlighted ? "highlight" : ""}>{text}</span>;
8}

Conclusión

Evitar useEffect permite a menudo simplificar tu código y mejorar el rendimiento de tu aplicación React. Al reevaluar las razones para usar un efecto, podrás crear componentes más claros y evitar errores relacionados con dependencias mal configuradas. La clave está en identificar los casos donde el estado, los valores derivados o useMemo son suficientes para satisfacer tus necesidades sin efectos secundarios.

Comparte este artículo


Sébastien Timoner

Sébastien TIMONER

Desarrollador Líder
Experto en Desarrollo a Medida
Aix-en-Provence, France

Experto en desarrollo web y gestión de equipos técnicos, me especializo en la creación y optimización de soluciones digitales de alto rendimiento. Gracias a un profundo dominio de tecnologías modernas como React.js, Node.js, TypeScript y Symfony, garantizo el éxito de proyectos SaaS complejos, desde el diseño hasta la implementación, para empresas de diversos sectores, dentro de offroadLabs.

En offroadLabs, ofrezco servicios de desarrollo a medida, combinando experiencia técnica y enfoque colaborativo. Ya sea para crear una solución SaaS innovadora, modernizar una aplicación existente o acompañar el desarrollo de habilidades de un equipo, me comprometo a proporcionar soluciones robustas y eficientes, adaptadas a las necesidades específicas de cada proyecto.

Estoy disponible para proyectos en la zona de Aix-en-Provence o en modalidad totalmente remota.