Skip to content

Instantly share code, notes, and snippets.

@erickvieira
Last active April 28, 2021 16:08
Show Gist options
  • Save erickvieira/3d44032f6926fa46a711ec07c66b1100 to your computer and use it in GitHub Desktop.
Save erickvieira/3d44032f6926fa46a711ec07c66b1100 to your computer and use it in GitHub Desktop.
Exemplo de implementação de um form não controlado com AntD 4.x
import { mdiDeleteForever } from "@mdi/js";
import { Form, Input, Space, Divider, Button } from "antd";
import MaterialIcon from "components/MaterialIcon";
import { Rule } from "rc-field-form/lib/interface";
// Estabelecendo uma interface simples para servir de objeto de estudo
interface Example {
displayName: string;
topics: string[];
user: {
username: string;
email: string;
};
}
/**
* As props do componente de formulário são mais simples devido ao fato desse
* ser "não controlado" (ou seja, os inputs não recebem valores de estados e não
* há listeners de onChange, como tradicionalmente é feito em forms controlados).
*
* É interessante notar que o objeto que o formulário precisa gerar é uma
* implementação da interface `Example`. Esse objeto será gerado ao submeter o
* formulário.
*
* Ou seja, nosso componente `ExampleForm` funciona em qualquer cenário onde
* seja necessário criar ou editar (devido ao initialValues, que é capaz de
* imputar dados prévios nos inputs do form ao ser construído) um objeto que
* implemente a interface `Example`. Qualquer componente que instancie nosso
* `ExampleForm` só precisa saber que ele recebe um `initialValues` e tem um
* callback de `onFinish` que é executado pelo `<Form />` ao clicar no botão de
* submit.
*/
export interface ExampleFormProps {
initialValues?: Example;
onFinish?: (formValue: Example) => void;
}
/**
* Regra de validação abstraída que verifica se o campo foi preenchido ao
* submeter o formulário. Caso alguma das regras do array `rules` de qualquer
* um dos `Form.Item` do formulário atual seja desrespeitada, a função
* `onFinish` não é disparada e os feedbacks escritos em `message` (como no
* caso a seguir) são exibidos em destaque logo a baixo dos inputs aos quais
* estiverem vinculados.
*/
const requiredFieldRule: Rule = {
required: true,
message: "Este campo é obrigatório.",
};
function ExampleForm({ initialValues, onFinish = () => {} }: ExampleFormProps) {
const [formInstance] = Form.useForm<Example>();
return (
<Form
form={formInstance}
initialValues={initialValues}
onFinish={onFinish}
layout="vertical"
>
{/* Cada `Form.Item` representa 1 dos atributos da interface `Example` */}
<Form.Item
label="Nome completo"
name={["displayName"]}
rules={[requiredFieldRule]}
>
<Input />
</Form.Item>
{/*
Um `Form.Item` pode apontar também para um "path" dentro da interface
`Example`, ou seja, para propriedades aninhadas dentro de objetos
filhos de `Example`. No caso a seguir, estamos apontando para o campo
`username` dentro do objeto `user`.
*/}
<Form.Item
label="Nome de usuário"
name={["user", "username"]}
rules={[requiredFieldRule]}
>
<Input />
</Form.Item>
<Form.Item
label="E-mail"
name={["user", "email"]}
rules={[requiredFieldRule]}
>
<Input type="email" />
</Form.Item>
<Divider>Tópicos de interesse</Divider>
{/*
É possível também referenciar listas que estejam aninhadas em
`Example`. No caso a seguir, a propriedade `topics` é um array de
strings e pode ser facilmente acumulado utilizando um `Form.List`, que
detém também os métodos de manipulação da lista, tais como `add`,
`remove` e até mesmo `move`, que é capaz de reordenar a lista.
*/}
<Form.List name={["topics"]}>
{(fields, actions) => (
<>
{fields.map((field) => (
<Space key={field.key}>
<Form.Item
name={[field.name]}
rules={[requiredFieldRule]}
fieldKey={field.fieldKey}
>
<Input placeholder="Digite aqui o tópico de interesse" />
</Form.Item>
<Button
title="Remover tópico"
danger
icon={<MaterialIcon path={mdiDeleteForever} />}
onClick={() => actions.remove(field.name)}
/>
</Space>
))}
<Button onClick={() => actions.add("")}>Adicionar tópico</Button>
</>
)}
</Form.List>
{/*
É importante salientar a independência e a versatilidade que o
formulário adquire por ser do tipo "não controlado". Todas as
validações dos fields e regras de negócio extras podem ficar
encapsuladas no nosso componente `ExampleForm`, criando apenas uma
assinatura básica que pode ser compartilhada entre diferentes
implementações do mesmo formulário, ou até combinadas em um mesmo
componente capaz de se adaptar a diferentes cenários.
*/}
<Button onClick={formInstance.submit} type="primary">
Submeter formulário
</Button>
</Form>
);
}
/*
Por fim, ressalto mais um ponto importante: sem dúvidas a curva de
aprendizagem existe, porém, após entender as peculiaridades de implementação
desse tipo de formulário, é bem provável que a construção e possíveis
manutenções no mesmo ocorram de forma mais rápida (até porque o código
efetivamente escrito em cada formulário é significativamente menor do que
seria numa solução baseada em forms controlados). Outro grande benefício é
gerar componentes mais testáveis que exigem pouca mockagem de valores e eventos.
*/
export default ExampleForm;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment