Last active
April 28, 2021 16:08
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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