Frontend

Gestire i Form in React

Autore

Manuel Ricci

La gestione dei form in React è un aspetto cruciale nello sviluppo di applicazioni web. React offre approcci flessibili per gestire i dati di input, validazione e invio dei form. Qui ci concentreremo su due approcci principali: la gestione dei form con i soli componenti di React e l'utilizzo della libreria esterna React Hook Form. Entrambi gli approcci saranno illustrati con esempi in TypeScript, che aggiunge tipizzazione statica per migliorare la leggibilità e la manutenibilità del codice.

Gestione dei form in React senza librerie esterne

L'approccio standard in React per la gestione dei form si basa sull'utilizzo dello stato e degli eventi. Vediamo un esempio di un semplice form per la registrazione di un utente con nome e password.

1import React, { useState } from 'react';
2
3type FormValues = {
4  username: string;
5  password: string;
6};
7
8export const SignupForm: React.FC = () => {
9  const [formValues, setFormValues] = useState<FormValues>({ username: '', password: '' });
10
11  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
12    const { name, value } = e.target;
13    setFormValues(prevState => ({
14      ...prevState,
15      [name]: value,
16    }));
17  };
18
19  const handleSubmit = (e: React.FormEvent) => {
20    e.preventDefault();
21    console.log(formValues);
22    // Qui potresti inviare i dati a un server, ad esempio.
23  };
24
25  return (
26    <form onSubmit={handleSubmit}>
27      <label htmlFor="username">Username</label>
28      <input
29        id="username"
30        name="username"
31        type="text"
32        value={formValues.username}
33        onChange={handleChange}
34      />
35      <label htmlFor="password">Password</label>
36      <input
37        id="password"
38        name="password"
39        type="password"
40        value={formValues.password}
41        onChange={handleChange}
42      />
43      <button type="submit">Submit</button>
44    </form>
45  );
46};

Questo esempio mostra come utilizzare gli hook di React (useState) per tracciare lo stato del form. L'evento onChange viene utilizzato per aggiornare lo stato ad ogni inserimento dell'utente, mentre onSubmit gestisce l'invio del form.

Gestione dei form con React Hook Form

React Hook Form è una libreria che semplifica la gestione dei form in React, riducendo la quantità di codice necessaria e migliorando le performance. Utilizza un approccio basato su hook non controllati, minimizzando il numero di re-render.

Vediamo come potremmo riscrivere l'esempio precedente utilizzando React Hook Form.

1import React from 'react';
2import { useForm } from 'react-hook-form';
3
4type FormValues = {
5  username: string;
6  password: string;
7};
8
9export const SignupFormWithHookForm: React.FC = () => {
10  const { register, handleSubmit, formState: { errors } } = useForm<FormValues>();
11
12  const onSubmit = (data: FormValues) => {
13    console.log(data);
14    // Invio dei dati a un server, ecc.
15  };
16
17  return (
18    <form onSubmit={handleSubmit(onSubmit)}>
19      <label htmlFor="username">Username</label>
20      <input
21        id="username"
22        {...register('username', { required: true })}
23      />
24      {errors.username && <p>Username is required</p>}
25      
26      <label htmlFor="password">Password</label>
27      <input
28        id="password"
29        {...register('password', { required: true })}
30      />
31      {errors.password && <p>Password is required</p>}
32      
33      <button type="submit">Submit</button>
34    </form>
35  );
36};

In questo esempio, useForm è un hook fornito da React Hook Form che gestisce le registrazioni degli input e i sottomissioni del form. L'operatore di spread ...register('fieldName') viene utilizzato per collegare gli input allo stato interno del form, gestendo automaticamente eventi come onChange, onBlur e ref.

Confrontando i due approcci, React Hook Form offre una sintassi più concisa e prestazioni migliorate grazie all'utilizzo di componenti non controllati. Inoltre, semplifica la validazione dei dati con feedback immediato all'utente.

Componenti controllati

I componenti controllati in React sono componenti il cui stato è controllato da React. Tipicamente, per un input di un form, ciò significa che il valore dell'input è gestito tramite lo stato di React e ogni modifica a tale valore viene gestita tramite un event handler come onChange.

Ecco un esempio di un componente controllato:

1import React, { useState } from 'react';
2
3export const ControlledComponent: React.FC = () => {
4  const [value, setValue] = useState('');
5
6  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
7    setValue(e.target.value);
8  };
9
10  return (
11    <input type="text" value={value} onChange={handleChange} />
12  );
13};

In questo esempio, lo stato value tiene traccia del valore dell'input, e ogni volta che l'utente digita qualcosa, l'evento onChange chiama handleChange, che aggiorna lo stato con il nuovo valore. L'input è quindi "controllato" dallo stato di React.

Componenti non controllati

I componenti non controllati, invece, sono componenti che conservano i propri dati interni e non sono controllati direttamente dallo stato di React. Per questi componenti, si utilizza un approccio basato sul ref di React per accedere ai valori degli input.

Ecco un esempio di componente non controllato:

1import React, { useRef } from 'react';
2
3export const UncontrolledComponent: React.FC = () => {
4  const inputRef = useRef<HTMLInputElement>(null);
5
6  const handleSubmit = (e: React.FormEvent) => {
7    e.preventDefault();
8    if (inputRef.current) {
9      console.log(inputRef.current.value);
10    }
11  };
12
13  return (
14    <form onSubmit={handleSubmit}>
15      <input type="text" ref={inputRef} />
16      <button type="submit">Submit</button>
17    </form>
18  );
19};

In questo esempio, usiamo useRef per creare un riferimento (inputRef) all'elemento input. Il valore dell'input può essere accesso direttamente tramite inputRef.current.value quando necessario, ad esempio al momento della sottomissione del form. L'input non è direttamente controllato dallo stato di React, quindi è un esempio di componente non controllato.

Differenze principali

  • Componenti controllati: lo stato dell'input è gestito da React attraverso lo stato del componente. Questo fornisce un maggiore controllo sull'input, permettendo di manipolarne il valore o di validarlo facilmente.
  • Componenti non controllati: si fa affidamento al DOM per gestire lo stato dell'input, utilizzando un ref per accedervi quando necessario. Questo può rendere più semplice l'implementazione di alcuni form, ma offre meno controllo diretto sugli input.

Infine, quando si parla di componenti controllati e non controllati non si può non menzionare un errore molto frequente che si commette.

L'errore a cui faccio riferimento in React generalmente si verifica quando un componente input (come <input>, <textarea>, o <select>) cambia da controllato a non controllato, o viceversa, durante la sua vita.

Questo succede se, ad esempio, il valore dell'input è inizialmente impostato su un valore definito (ad esempio una stringa) rendendolo un componente controllato, e poi in qualche momento diventa undefined o null, o se non viene gestito correttamente il suo stato rendendolo non controllato.

Ecco un esempio di codice che potrebbe causare tale errore:

1import React, { useState } from 'react';
2
3const ExampleComponent: React.FC = () => {
4  // Supponi di avere uno stato che inizialmente non è definito
5  const [value, setValue] = useState<string | undefined>(undefined);
6
7  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
8    setValue(event.target.value);
9  };
10
11  return (
12    <div>
13      <input type="text" value={value} onChange={handleChange} />
14    </div>
15  );
16};
17
18export default ExampleComponent;

Se esegui questo codice, potresti non vedere immediatamente un errore, ma se value fosse cambiato da undefined a una stringa (o viceversa) durante l'esecuzione dell'app, React ti avviserebbe di un cambio da componente controllato a non controllato (o viceversa). Questo perché undefined e null non sono valori validi per un input controllato.

Per evitare questo errore, assicurati che il componente sia sempre controllato o sempre non controllato. Se vuoi che sia controllato, non impostare mai il valore su undefined o null. Al posto di undefined o null, puoi usare una stringa vuota ('') per rappresentare l'assenza di valore in un input controllato:

1const [value, setValue] = useState<string>(''); // Imposta lo stato iniziale su stringa vuota

Mantenendo il valore dell'input come stringa vuota invece di undefined o null, il componente rimane controllato, evitando l'errore di cambiare tra controllato e non controllato.

Perché il passaggio da undefined a stringa da fastidio a React?

Il passaggio da undefined a una stringa (o viceversa) per il valore di un input in React non è ideale perché viola il principio dei componenti controllati.

Con principio dei componenti controllati si intende la gestione dello stato e del comportamento degli elementi di form come <input>, <textarea> e <select> tramite lo stato di React, piuttosto che affidarsi al DOM per mantenere lo stato.

Questo principio consente di avere un "single source of truth" per i dati dell'applicazione, il che rende più facile gestire lo stato complessivo, validare l'input dell'utente, e controllare il comportamento dell'interfaccia utente in modo programmatico.

Caratteristiche dei Componenti Controllati

  1. Single Source of Truth: Lo stato dell'applicazione che guida l'UI è conservato nello stato di React anziché nel DOM. Ciò significa che il valore dell'input è sempre sincronizzato con lo stato del componente React, rendendo il componente React l'unica fonte di verità.

  2. Gestione dello Stato Centralizzata: Poiché il valore viene gestito tramite lo stato di React, la logica di aggiornamento per gli input controllati può essere centralizzata, facilitando la gestione di form complessi e la condivisione dello stato tra componenti.

  3. Validazione in Tempo Reale: La validazione dell'input dell'utente può essere implementata facilmente durante l'input, permettendo di fornire feedback immediato sull'input dell'utente, poiché ogni cambiamento passa attraverso un gestore di eventi che può eseguire logica di validazione prima di aggiornare lo stato.

  4. Flusso di Dati Unidirezionale: Segue il paradigma di React di un flusso di dati unidirezionale, dove i dati seguono una direzione chiara e prevedibile, migliorando la manutenibilità e riducendo le possibilità di bug legati allo stato.

Ricapitolando

Un componente input in React può essere:

  • Controllato: il suo valore è gestito tramite lo stato di React. Per essere considerato controllato, un input deve avere il suo valore impostato esplicitamente dallo stato e deve essere aggiornato tramite un gestore di eventi come onChange.
  • Non controllato: il suo valore è gestito direttamente dal DOM, e non da React. L'accesso al suo valore avviene tramite un ref.

Quando un componente input passa da avere un valore undefined o null a una stringa, indica una transizione da un input non controllato a uno controllato. Analogamente, passare da una stringa a undefined o null indica un passaggio da controllato a non controllato. React si aspetta che un componente rimanga coerente come controllato o non controllato per tutta la sua durata.

Ci sono alcune ragioni specifiche per cui React desidera evitare questi cambiamenti:

  1. Prevedibilità e Coerenza: Mantenere un componente controllato o non controllato per tutta la sua vita rende il comportamento del componente più prevedibile e il codice più coerente.

  2. Flusso di Dati: In React, il flusso di dati dovrebbe essere esplicito e unidirezionale. Un componente controllato consente a React di gestire questo flusso, dato che il valore dell'input è sincronizzato con lo stato del componente. Cambiare da controllato a non controllato rompe questo modello, rendendo il flusso di dati meno chiaro.

  3. Prestazioni: Il passaggio tra controllato e non controllato può causare comportamenti inaspettati e potenzialmente reintegrazioni inutili del componente, influenzando le prestazioni dell'applicazione.

Per questi motivi, è buona pratica assicurarsi che ogni input in un'applicazione React sia esplicitamente controllato o non controllato. Questo si traduce nell'inizializzare sempre gli input controllati con una stringa vuota (o un altro valore valido) piuttosto che undefined o null, assicurando che il loro stato sia sempre gestito in modo prevedibile e coerente.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.