React na Prática: Melhorando Formulários com Hooks.
Trabalhando com formulários Um caso comum é a lógica de lidar com formulários, que pode ser extraída para um hook customizado. // form.tsx import { FormEventHandler, useState } from "react"; function Forms() { const [title, setTitle] = useState(""); const [price, setPrice] = useState(""); const [description, setDescription] = useState(""); const [image, setImage] = useState(""); const [category, setCategory] = useState(""); const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const handleSubmit: FormEventHandler = async (e) => { try { setIsLoading(true); e.preventDefault(); const response = await fetch("https://fakestoreapi.com/products", { method: "POST", body: JSON.stringify({ title, price, description, image, category, }), }); const data = await response.json(); setData(data); } catch (e) { setError(e); } finally { setIsLoading(false); } }; return ( Title: setTitle(value)} /> Price: setPrice(value)} /> Description: setDescription(value)} /> Image: setImage(value)} /> Category: setCategory(value)} > electronics jewelery men's clothing women's clothing Send Error: {JSON.stringify(error)} Response: {JSON.stringify(data)} ); } export default Forms; No cerne, esse componente lida com a ideia de criar um produto, tem um estado para cada campo de formulário e para os estados do formulário. A reescrita desse componente pode ser feita em etapas. Podemos aproveitar parte do que fizemos no exemplo de lidando com requisições: Extraindo estados da requisição // useMutate.tsx import { useCallback, useState } from "react"; export type UseMutateParams = { mutation: (args: A) => Promise; }; export function useMutate({ mutation }: UseMutateParams) { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [data, setData] = useState(null); const mutate = useCallback( async (args: A) => { try { setIsLoading(true); const data = await mutation(args); setData(data); } catch (e) { setError(e); } finally { setIsLoading(false); } }, [mutation], ); return { isLoading, error, data, mutate, }; } // Form.tsx import { FormEventHandler, useState } from "react"; import { useMutate } from "../hooks/useMutate.tsx"; function Forms() { const [title, setTitle] = useState(""); const [price, setPrice] = useState(""); const [description, setDescription] = useState(""); const [image, setImage] = useState(""); const [category, setCategory] = useState(""); const { mutate, data, isLoading, error } = useMutate({ mutation: async (body) => { const response = await fetch("https://fakestoreapi.com/products", { method: "POST", body: JSON.stringify(body), }); return response.json(); }, }); const handleSubmit: FormEventHandler = async (e) => { e.preventDefault(); await mutate( JSON.stringify({ title, price, description, category, image, }), ); }; return ( Title: setTitle(value)} /> Price: setPrice(value)} /> Description: setDescription(value)} /> Image: setImage(value)} /> Category: setCategory(value)} > electronics jewelery men's clothing women's clothing Send Error: {JSON.stringify(error)} Response: {JSON.stringify(data)} ); } export default Forms; Descontrolando campos de formulário import { FormEventHandler } from "react"; import { useMutate } from "../hooks/useMutate.tsx"; function Forms() { const { mutate, data, isLoading, error } = useMutate({ mutation: async (body) => { const response = await fetch("https://fakestoreapi.com/products", { method: "POST", body: JSON.stringify(body), }); return response.json(); }, }); const handleSubmit: FormEventHandler = async (event) => { event.preventDefault(); const form = event.target as HTMLFormElement; const formData = new FormData(form); await mutate( JSON.stringify({ title: formData.get("title"), price: formData.get("price"), description: formData.get("description"), category: formData.get("category"), image: formData.get("image"), }), ); }; return ( Title: Price: Description:

Trabalhando com formulários
Um caso comum é a lógica de lidar com formulários, que pode ser extraída para um hook customizado.
// form.tsx
import { FormEventHandler, useState } from "react";
function Forms() {
const [title, setTitle] = useState("");
const [price, setPrice] = useState("");
const [description, setDescription] = useState("");
const [image, setImage] = useState("");
const [category, setCategory] = useState("");
const [data, setData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<any>(null);
const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
try {
setIsLoading(true);
e.preventDefault();
const response = await fetch("https://fakestoreapi.com/products", {
method: "POST",
body: JSON.stringify({
title,
price,
description,
image,
category,
}),
});
const data = await response.json();
setData(data);
} catch (e) {
setError(e);
} finally {
setIsLoading(false);
}
};
return (
<form
style={{ display: "flex", flexFlow: "column", gap: 10 }}
onSubmit={handleSubmit}
>
<label htmlFor="name">Title:label>
<input
type="text"
id="title"
name="title"
value={title}
onChange={({ target: { value } }) => setTitle(value)}
/>
<label htmlFor="price">Price:label>
<input
type="number"
id="price"
name="price"
value={price}
onChange={({ target: { value } }) => setPrice(value)}
/>
<label htmlFor="description">Description:label>
<textarea
id="description"
name="description"
value={description}
onChange={({ target: { value } }) => setDescription(value)}
/>
<label htmlFor="image">Image:label>
<input
type="url"
id="image"
name="image"
onChange={({ target: { value } }) => setImage(value)}
/>
<label htmlFor="category">Category:label>
<select
id="category"
name="category"
value={category}
onChange={({ target: { value } }) => setCategory(value)}
>
<option value="electronics">electronicsoption>
<option value="jewelery">jeweleryoption>
<option value="men's clothing">men's clothingoption>
<option value="women's clothing">women's clothingoption>
select>
<button disabled={isLoading} type="submit">
Send
button>
<div>Error: {JSON.stringify(error)}div>
<div>Response: {JSON.stringify(data)}div>
form>
);
}
export default Forms;
No cerne, esse componente lida com a ideia de criar um produto, tem um estado
para cada campo de formulário e para os estados do formulário.
A reescrita desse componente pode ser feita em etapas. Podemos aproveitar parte do que fizemos no exemplo de lidando com requisições:
Extraindo estados da requisição
// useMutate.tsx
import { useCallback, useState } from "react";
export type UseMutateParams<T, A> = {
mutation: (args: A) => Promise<T>;
};
export function useMutate<T, A>({ mutation }: UseMutateParams<T, A>) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<any>(null);
const [data, setData] = useState<T | null>(null);
const mutate = useCallback(
async (args: A) => {
try {
setIsLoading(true);
const data = await mutation(args);
setData(data);
} catch (e) {
setError(e);
} finally {
setIsLoading(false);
}
},
[mutation],
);
return {
isLoading,
error,
data,
mutate,
};
}
// Form.tsx
import { FormEventHandler, useState } from "react";
import { useMutate } from "../hooks/useMutate.tsx";
function Forms() {
const [title, setTitle] = useState("");
const [price, setPrice] = useState("");
const [description, setDescription] = useState("");
const [image, setImage] = useState("");
const [category, setCategory] = useState("");
const { mutate, data, isLoading, error } = useMutate({
mutation: async (body) => {
const response = await fetch("https://fakestoreapi.com/products", {
method: "POST",
body: JSON.stringify(body),
});
return response.json();
},
});
const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
e.preventDefault();
await mutate(
JSON.stringify({
title,
price,
description,
category,
image,
}),
);
};
return (
<form
style={{ display: "flex", flexFlow: "column", gap: 10 }}
onSubmit={handleSubmit}
>
<label htmlFor="name">Title:label>
<input
type="text"
id="title"
name="title"
value={title}
onChange={({ target: { value } }) => setTitle(value)}
/>
<label htmlFor="price">Price:label>
<input
type="number"
id="price"
name="price"
value={price}
onChange={({ target: { value } }) => setPrice(value)}
/>
<label htmlFor="description">Description:label>
<textarea
id="description"
name="description"
value={description}
onChange={({ target: { value } }) => setDescription(value)}
/>
<label htmlFor="image">Image:label>
<input
type="url"
id="image"
name="image"
onChange={({ target: { value } }) => setImage(value)}
/>
<label htmlFor="category">Category:label>
<select
id="category"
name="category"
value={category}
onChange={({ target: { value } }) => setCategory(value)}
>
<option value="electronics">electronicsoption>
<option value="jewelery">jeweleryoption>
<option value="men's clothing">men's clothingoption>
<option value="women's clothing">women's clothingoption>
select>
<button disabled={isLoading} type="submit">
Send
button>
<div>Error: {JSON.stringify(error)}div>
<div>Response: {JSON.stringify(data)}div>
form>
);
}
export default Forms;
Descontrolando campos de formulário
import { FormEventHandler } from "react";
import { useMutate } from "../hooks/useMutate.tsx";
function Forms() {
const { mutate, data, isLoading, error } = useMutate({
mutation: async (body) => {
const response = await fetch("https://fakestoreapi.com/products", {
method: "POST",
body: JSON.stringify(body),
});
return response.json();
},
});
const handleSubmit: FormEventHandler<HTMLFormElement> = async (event) => {
event.preventDefault();
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
await mutate(
JSON.stringify({
title: formData.get("title"),
price: formData.get("price"),
description: formData.get("description"),
category: formData.get("category"),
image: formData.get("image"),
}),
);
};
return (
<form
style={{ display: "flex", flexFlow: "column", gap: 10 }}
onSubmit={handleSubmit}
>
<label htmlFor="name">Title:label>
<input type="text" id="title" name="title" />
<label htmlFor="price">Price:label>
<input type="number" id="price" name="price" />
<label htmlFor="description">Description:label>
<textarea id="description" name="description" />
<label htmlFor="image">Image:label>
<input type="url" id="image" name="image" />
<label htmlFor="category">Category:label>
<select id="category" name="category">
<option value="electronics">electronicsoption>
<option value="jewelery">jeweleryoption>
<option value="men's clothing">men's clothingoption>
<option value="women's clothing">women's clothingoption>
select>
<button disabled={isLoading} type="submit">
Send
button>
<div>Error: {JSON.stringify(error)}div>
<div>Response: {JSON.stringify(data)}div>
form>
);
}
export default Forms;
Movendo a Requisição para o seu próprio Hook
// useProductCreate.tsx
import { useMutate } from "./useMutate.tsx";
async function fetchProductCreate(body) {
const response = await fetch("https://fakestoreapi.com/products", {
method: "POST",
body: JSON.stringify(body),
});
return response.json();
}
export function useProductCreate() {
return useMutate({
mutation: (formData: FormData) => {
const payload = {
title: formData.get("title"),
price: formData.get("price"),
description: formData.get("description"),
category: formData.get("category"),
image: formData.get("image"),
};
return fetchProductCreate(payload);
},
});
}
// Form.tsx
import { FormEventHandler } from "react";
import { useProductCreate } from "../hooks/useProductCreate";
function Forms() {
const { data, mutate, isLoading, error } = useProductCreate();
const handleSubmit: FormEventHandler<HTMLFormElement> = async (event) => {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
await mutate(formData);
};
return (
<form
style={{ display: "flex", flexFlow: "column", gap: 10 }}
onSubmit={handleSubmit}
>
<label htmlFor="name">Title:label>
<input type="text" id="title" name="title" />
<label htmlFor="price">Price:label>
<input type="number" id="price" name="price" />
<label htmlFor="description">Description:label>
<textarea id="description" name="description" />
<label htmlFor="image">Image:label>
<input type="url" id="image" name="image" />
<label htmlFor="category">Category:label>
<select id="category" name="category">
<option value="electronics">electronicsoption>
<option value="jewelery">jeweleryoption>
<option value="men's clothing">men's clothingoption>
<option value="women's clothing">women's clothingoption>
select>
<button disabled={isLoading} type="submit">
Send
button>
<div>Error: {JSON.stringify(error)}div>
<div>Response: {JSON.stringify(data)}div>
form>
);
}
export default Forms;