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">electronics</option>
<option value="jewelery">jewelery</option>
<option value="men's clothing">men's clothing</option>
<option value="women's clothing">women's clothing</option>
</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">electronics</option>
<option value="jewelery">jewelery</option>
<option value="men's clothing">men's clothing</option>
<option value="women's clothing">women's clothing</option>
</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">electronics</option>
<option value="jewelery">jewelery</option>
<option value="men's clothing">men's clothing</option>
<option value="women's clothing">women's clothing</option>
</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">electronics</option>
<option value="jewelery">jewelery</option>
<option value="men's clothing">men's clothing</option>
<option value="women's clothing">women's clothing</option>
</select>
<button disabled={isLoading} type="submit">
Send
</button>
<div>Error: {JSON.stringify(error)}</div>
<div>Response: {JSON.stringify(data)}</div>
</form>
);
}
export default Forms;
Top comments (0)