Quando evitare di usare useEffect in React per ottimizzare il codice

Nei progetti React, spesso usiamo useEffect per riflesso, ma questo approccio può talvolta complicare inutilmente il codice. In questo articolo, vedremo i casi in cui non hai bisogno di useEffect e come ottimizzare il tuo codice evitando di utilizzarlo impropriamente.

Comprendere il ruolo di useEffect

L'hook useEffect è utile per gestire effetti collaterali come sottoscrizioni o operazioni che devono essere eseguite al di fuori del flusso di rendering principale. Tuttavia, alcuni casi d'uso comuni possono essere semplificati senza useEffect.

1. Inizializzare lo stato senza useEffect

Spesso si vuole inizializzare uno stato basato su props o valori iniziali al montaggio del componente. Invece di usare useEffect per impostare questo stato, puoi farlo direttamente con useState, rendendo il codice più chiaro e prevenendo chiamate inutili.

Cattiva pratica 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(`Hello, ${name}`);
12 }, [name]);
13
14 return <h1>{greeting}</h1>;
15}

Buona pratica senza useEffect:

Qui, possiamo semplicemente inizializzare lo stato greeting usando una funzione in useState:

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

Questo approccio rende il codice più conciso ed evita di aggiungere un effetto superfluo che potrebbe rallentare la tua applicazione.

2. Calcolare valori derivati

Se hai bisogno di calcolare un valore in base allo stato o alle props, prova a utilizzare una variabile derivata o un memo invece di useEffect.

Esempio con useEffect (da evitare):

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}

Soluzione senza useEffect:

Qui, il valore total può essere calcolato direttamente durante il rendering, senza dipendere da 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}

Questo metodo non solo è più pulito, ma elimina la necessità di gestire stati aggiuntivi.

3. Utilizzare useMemo per calcoli pesanti

Per calcoli costosi o dipendenti da più variabili, usa useMemo per evitare di rieseguire la logica ad ogni rendering, senza dover utilizzare useEffect per gestire lo stato.

Esempio 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>Result: {result}</p>;
16}
17
18function heavyComputation(num: number): number {
19 // Simula un calcolo costoso
20 return num * 2; // Esempio semplificato
21}

Approccio ottimizzato 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>Result: {result}</p>;
11}
12
13function heavyComputation(num: number): number {
14 // Simula un calcolo costoso
15 return num * 2; // Esempio semplificato
16}

Qui, useMemo permette di memorizzare il risultato senza dover gestire un effetto, rendendo il componente più prevedibile e performante.

4. Sincronizzare le proprietà di un componente controllato

Per gestire gli input controllati, non hai bisogno di usare useEffect per sincronizzare i valori degli input con lo stato del componente genitore. Usa semplicemente le props e le callback per gestire questo stato efficacemente.

Esempio con useEffect (inutile):

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}

Soluzione senza 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}

L'uso diretto delle props semplifica il codice e migliora le prestazioni riducendo il numero di aggiornamenti di stato non necessari.

5. Manipolare il DOM direttamente nel rendering

Se vuoi applicare stili o classi basati sullo stato, spesso è inutile passare attraverso useEffect. React gestisce molto bene questo tipo di sincronizzazione durante il rendering.

Esempio 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}

Soluzione senza 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}

Conclusione

Evitare useEffect permette spesso di semplificare il codice e migliorare le prestazioni della tua applicazione React. Rivalutando le ragioni per utilizzare un effetto, potrai creare componenti più chiari ed evitare errori legati a dipendenze mal configurate. La chiave è identificare i casi in cui lo stato, i valori derivati o useMemo sono sufficienti per soddisfare le tue esigenze senza effetti collaterali.

Condividi questo articolo


Sébastien Timoner

Sébastien TIMONER

Lead Developer
Esperto in Sviluppo su Misura
Aix-en-Provence, France

Esperto nello sviluppo web e nella gestione di team tecnici, mi specializzo nella creazione e ottimizzazione di soluzioni digitali performanti. Grazie a una profonda padronanza di tecnologie moderne come React.js, Node.js, TypeScript e Symfony, garantisco il successo di progetti SaaS complessi, dalla progettazione alla messa in produzione, per aziende di diversi settori, all'interno di offroadLabs.

In offroadLabs, offro servizi di sviluppo su misura, combinando competenza tecnica e approccio collaborativo. Che si tratti di creare una soluzione SaaS innovativa, modernizzare un'applicazione esistente o accompagnare la crescita professionale di un team, mi impegno a fornire soluzioni robuste e performanti, adattate alle esigenze specifiche di ogni progetto.

Sono disponibile per incarichi intorno ad Aix-en-Provence o in full remote.