Objectiu
Quan creeu aplicacions amb React, tard o d'hora necessitareu gestionar la interacció de l'usuari mitjançant formularis. Són essencials per a funcions com:
- La inscripció d'usuaris.
- L'inici de sessió.
- La subscripció a butlletins.
- Qualsevol altra forma d'interacció.
Construir formularis en React té reptes propis. Hi ha moltes parts mòbils a tenir en compte:
- Estat del formulari
Els formularis tenen camps on els usuaris poden introduir dades.
- Com obtenim els valors dins i fora dels camps de formulari?
- Com gestionem els canvis en cada camp?
- Validació de dades
És important assegurar-se que les dades introduïdes als camps estan en el format esperat.
- Enviament del formulari
Quan les dades són vàlides:
- Com les enviem a una base de dades o a un magatzem de dades?
- Què passa després d'enviar-les?
Perquè els vostres formularis funcionin com cal, totes aquestes parts s'han de gestionar correctament.
🔨 Instal·lació.
En aquest enllaç tens el projecte de suport: https://gitlab.com/xtec/typescript/react-forms
📋 Índex
- 🟢 Formulari senzill amb un
input
i unbotó
- 🔢 Afegir un
input
numèric - 🔍 Formulari amb cerca
- ✉️ React Hook Forms
- 📌 Recursos Addicionals
🟢 1. Formulari senzill amb un input
i un botó
En aquest primer exemple, crearem un formulari senzill amb un camp de text i un botó.
El contingut de l'input
es mostrarà per pantalla quan fem clic al botó.
import { useState } from "react";
export default function SimpleForm() {
const [nom, setNom] = useState<string>(""); // Tipem l'estat com a string
const [result, setResult] = useState<string>("");
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault(); // Evitar el comportament per defecte
setResult(`Bon dia ${nom}!`);
}
return (
<main className="container mt-5">
<div className="card shadow">
<div className="card-body">
<form onSubmit={handleSubmit}>
<label htmlFor="simple-input">Com et dius ?</label>
<input id="simple-input" type="nom" value={nom}
onChange={(e) => setNom(e.target.value)}
placeholder="Anonymous"
/>
<button type="submit">Enviar</button>
</form>
{result && (
<p className="mt-3 text-success">{result}</p>
)}
</div>
</div>
</main>
);
}
📌 Conceptes destacats:
useState
: Controla l'estat del valor de l'input
.onSubmit
: Evita que la pàgina es recarregui.onChange
: Actualitza l'estat amb el contingut de l'input.
El que potser et sorprendrà si vens de Javascript és perquè necessites aquest event si realment només vols validar les dades al final de tot.
onChange={(e) => setNom(e.target.value)}
El que passa és que si es vol mantenir la integritat de les dades entre el client i el servidor (bidireccional) cal que es comprovi en cada canvi.
També és curiosa la manera que usem per a retornar la resposta a la pàgina:
{result && (
<p className="mt-3 text-success">{result}</p>
)}
Si result existeix (no és un valor buit) es mostra el seu contingut dins d'una etiqueta p
HTML.
🔢 2. Afegir un input
numèric
Els inputs poden ser de diferents tipus (text
, number
, password
, etc.). Afegirem un input numèric i farem càlculs senzills amb el valor introduït.
import React, { useState } from "react";
export default function MoneyInputForm() {
const [amount, setAmount] = useState<number | "">(""); // Estat inicial buit o número
const [result, setResult] = useState<string>(""); // Estat per mostrar el resultat
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (amount === "" || amount < 0) {
setResult("⚠️ Introdueix un valor vàlid superior o igual a 0.");
} else {
setResult(`Has introduït: ${amount.toFixed(2)} €`);
}
}
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = event.target.value;
if (value === "") {
setAmount(""); // Si està buit, mantenim un string buit
} else if (!isNaN(Number(value))) {
setAmount(Number(value)); // Convertim el valor a número
}
}
return (
<div className="container">
<form onSubmit={handleSubmit}>
<label htmlFor="money-input" className="form-label">
Introdueix una quantitat de diners:
</label>
<input
id="money-input" type="number"
value={amount}
onChange={handleChange}
placeholder="Exemple: 50.75"
className="form-control w-auto my-2"
min="0" step="0.01"
/>
<button type="submit" className="btn btn-primary">
Enviar
</button>
</form>
{result && (
<p className={`mt-3 ${amount !== "" && amount >= 0 ? "text-success" : "text-danger"}`}>
{result}
</p>
)}
</div>
);
}
📌 Conceptes destacats:
type="number"
: Fa que només s'acceptin valors numèrics.Number(e.target.value)
: Converteix el valor de l'input (que és un string) en un número.
3. 🔍 Formulari amb cerca
A continuació, tens un formulari senzill que permet cercar ítems en una llista.
import { useState } from "react";
const items: { name: string; icon: string }[] = [
{ name: "🍎 Poma", icon: "🍎" },
{ name: "🍌 Plàtan", icon: "🍌" },
{ name: "🍓 Maduixa", icon: "🍓" },
{ name: "🥝 Kiwi", icon: "🥝" },
{ name: "🍈 Meló", icon: "🍈" },
{ name: "🍍 Pinya", icon: "🍍" },
{ name: "🍒 Cirera", icon: "🍒" },
{ name: "🍊 Taronja", icon: "🍊" },
];
export default function SearchForm() {
const [query, setQuery] = useState<string>(""); // Estat del camp de cerca
// Filtrar ítems que coincideixen amb la consulta
const filteredItems = items.filter((item) =>
item.name.toLowerCase().includes(query.toLowerCase())
);
return (
<div className="container mt-4">
<h3 className="mb-3">Cerca a la llista de fruites:</h3>
<label htmlFor="search-input" className="form-label">
Escriu el nom d'una fruita:
</label>
<input
id="search-input"
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Exemple: Poma"
className="form-control"
/>
<ul className="mt-3 list-group">
{filteredItems.map((item, index) => (
<li key={index} className="list-group-item">
{item.icon} {item.name}
</li>
))}
</ul>
</div>
);
}
📌 Conceptes destacats:
-
Tipatge amb TypeScript
- La llista
items
s'ha tipat com un array d'objectes{ name: string; icon: string }
.
- La llista
-
Estat del formulari
query
manté el valor actual del camp de cerca.
-
Filtrat
- El mètode
.filter()
compara la cerca (query
) amb el nom de cada fruita, independentment de majúscules o minúscules.
- El mètode
-
Interfície Visual
- Utilitzem
list-group
de Bootstrap per mostrar els elements filtrats de forma atractiva i clara.
- Utilitzem
Si escrius "Poma" al camp de cerca, veuràs el següent resultat:
🍎 Poma
Activitat
1. - Crea un formulari que agafi el nom i la data de neixament d'una persona.
Si és menor de 18 anys, sortirà el text:
<p className="text-warning">
Encara ets massa jove per entrar al club de les tortugues, Miquel"
</p>
Si té 18 o mes anys:
```html
<p>Benvigut al club, Miquel 🐢🐢🐢 !</p>
I si té més de 100 anys:
<p className="fs-5 mt-3">🌈 Ets una super tortuga, Miquel!</p>
Pistes:
Revisa els últims exemples de la sessió de https://xtec.dev/typescript/react/tsx/
Pots utilitzar aquest codi per calcular la edat a partir de la data de naixement:
function calculateAge(birthDate: string): number {
const birth = new Date(birthDate);
const today = new Date();
const age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
return age - 1;
}
return age;
}
import { useState } from "react";
export default function TurtleClubForm() {
const [name, setName] = useState<string>(""); // Nom
const [birthDate, setBirthDate] = useState<string>(""); // Data de naixement (YYYY-MM-DD)
const [age, setAge] = useState<number | null>(null); // Edat calculada
function calculateAge(birthDate: string): number {
const birth = new Date(birthDate);
const today = new Date();
const age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
return age - 1;
}
return age;
}
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const calculatedAge = calculateAge(birthDate);
setAge(calculatedAge);
}
return (
<div className="container">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Nom:
</label>
<input id="name" type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Escriu el teu nom"
className="form-control"
/>
</div>
<div className="mb-3">
<label htmlFor="birthDate" className="form-label">
Data de naixement:
</label>
<input id="birthDate" type="date"
value={birthDate}
onChange={(e) => setBirthDate(e.target.value)}
className="form-control"
/>
</div>
<button type="submit" className="btn btn-primary">
Comprovar
</button>
</form>
{age !== null && (
<div className="container m-5 text-center">
<p className="fs-1 m-2">🐢</p>
{age < 18 ? (
<p className="text-warning">
Encara ets massa jove per entrar al club de les tortugues. {name}!
</p>
) : (
<p>Benvigut al club, {name} 🐢🐢🐢 !</p>
)}
{age > 100 && (
<p className="fs-5 mt-3">🌈 Ets una super tortuga, {name}!</p>
)}
</div>
)}
</div>
);
}
4. 🚀 React Hook Form
Una de les solucions més populars per construir formularis en aplicacions React és la biblioteca React Hook Form.
Aquesta biblioteca compta amb més de 39.000 estrelles a GitHub"."
React Hook Form es basa en hooks de React i està dissenyada per utilitzar-se com un hook. Això significa que no cal importar components específics per construir un formulari.
Simplement fas servir el hook personalitzat (useForm
) i apliques els seus mètodes a un formulari HTML estàndard.
Instal·lació del paquet
bun install react-hook-form
Exemple: Formulari de Subscripció al Butlletí
Suposem que a l'institut ens han demanat un formulari de subscripció a un servei.
Aquest formulari conté:
- Un camp per al nom
- Un camp per al correu electrònic
- Un camp de selecció de títol
- Una àrea de text per a missatges
1️⃣ Importa i inicialitza el hook useForm
import { useForm } from "react-hook-form";
function NewsletterSignupForm() {
// Inicia el hook useForm
const {
register,
formState: { errors },
handleSubmit,
} = useForm({
defaultValues: { name: "", email: "", message: "" },
});
return (
<form>
{/* Elements del formulari HTML */}
</form>
);
}
- El hook
useForm
retorna un conjunt de mètodes per treballar amb formularis. - Accepta un objecte opcional amb propietats com:
defaultValues
: defineix els valors per defecte dels camps del formulari.resolver
: especifica un mètode per validar les dades.
Mètodes clau del hook useForm
register
: Registra els camps del formulari perquè React Hook Form pugui rastrejar els seus valors i l'estat del formulari.formState
: Conté l'estat actual del formulari, inclosos els errors de validació.handleSubmit
: Gestiona l'enviament del formulari.
📖 Per més detalls, consulta la documentació oficial de React Hook Form.
2️⃣ Convertir un camp HTML a React Hook Form
Formulari HTML tradicional
<form>
<label htmlFor="name">Nom:</label>
<input type="text" id="name" name="name" required />
{/* Altres camps del formulari */}
</form>
Formulari amb React Hook Form
<form>
<label htmlFor="name">Nom:</label>
<input {...register("name", { required: true, maxLength: 20 })} />
{errors.name && <p>Aquest camp és obligatori</p>}
{errors.name?.type === "maxLength" && (
<p>El nom no pot tenir més de 20 caràcters</p>
)}
{/* Altres camps del formulari */}
</form>
Explicació del codi
<input />
: És un element HTML estàndard.register
: Notifica a React Hook Form que ha de rastrejar aquest camp:- Nom del camp: Ha de ser únic dins del formulari.
- Regles de validació: Aquí, el nom és obligatori i no pot superar els 20 caràcters.
errors
: Si les dades no són vàlides, React Hook Form enregistra els errors i ens permet mostrar missatges d'error personalitzats.
Exemple d'error
{errors.name?.type === "maxLength" && (
<p>El nom no pot tenir més de 20 caràcters</p>
)}
Això verifica si el tipus d'error coincideix amb la regla de validació (en aquest cas, maxLength
).
🔗 Enviar el Formulari amb React Hook Form
Per gestionar l'enviament del formulari, utilitzem el mètode handleSubmit
. Passem una funció (per exemple, onSubmit
) que s'executarà només si el formulari és vàlid.
function NewsletterSignupForm() {
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Camps del formulari */}
</form>
);
}
🔗 Activitat.
2.- És el teu torn! Prova de convertir la resta de camps del formulari HTML en camps gestionats per React Hook Form.
Tens una solució parcial:
https://gitlab.com/xtec/typescript/react-forms/-/blob/main/src/pages/Form4.tsx?ref_type=heads
Que la hem extret de: