Nesta aula, você irá aprender a construir uma feature completa de login, incluindo validação de formulários com Yup e gerenciamento de estado com React Hook Form. O objetivo é construir uma interface de login reutilizável e fácil de usar com validações de formulários eficientes e gerenciamento de estado simplificado.
O vídeo dessa aula está publicada no bootcamp CrazyStack, se você ainda não garantiu sua vaga clique aqui
Antes de começarmos, é importante ter uma compreensão básica de hooks e Yup. Se você ainda não tem essa compreensão, recomendo fazer uma pesquisa básica sobre esses conceitos antes de prosseguir.
Começaremos importando as dependências necessárias e criando um schema de validação com Yup. O schema define as regras de validação que serão aplicadas aos dados do formulário antes de enviá-los para o servidor. Em seguida, utilizaremos o React Hook Form para gerenciar o estado do formulário e registrar os campos de entrada.
Em seguida, adicionaremos as validações Yup aos campos do formulário usando o método "register" e "handleSubmit" fornecidos pelo React Hook Form. Ao clicar no botão "Enviar", o formulário será submetido e as validações Yup serão aplicadas aos dados do formulário. Se houver algum erro, será exibido uma mensagem de erro para o usuário.
Finalmente, adicionaremos um recurso de "loading" ao botão de envio para fornecer feedback visual ao usuário durante a submissão do formulário.
Com isso, você terá uma feature completa de login usando React Hook Form e Yup, além de ter aproveitado o formulário genérico construído na aula anterior. Essa solução é escalável e fácil de manter, além de ser altamente reutilizável em outros projetos.
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import { SubmitHandler, useForm } from "react-hook-form";
export interface LoginFormData {
email: string;
password: string;
}
export type SubmitLoginHandler = SubmitHandler<LoginFormData>;
export const loginSchema = yup.object().shape({
email: yup.string().email("Email inválido").required("Email é obrigatório"),
password: yup.string().required("Senha é obrigatória"),
});
export const useLoginLib = () => {
const formProps = useForm({ resolver: yupResolver(loginSchema) });
return { ...formProps };
};
O código acima define uma biblioteca de login utilizando react-hook-form
e yup
para validação de formulários.
A interface LoginFormData
define o formato dos dados do formulário de login, que consiste em um email e uma senha.
A constante loginSchema
é uma especificação do yup
que define as regras de validação dos dados do formulário de login. Ela requer que o email seja válido e obrigatório e que a senha seja obrigatória.
A função useLoginLib
é a biblioteca em si. Ela usa o useForm
do react-hook-form
e o yupResolver
para informar ao react-hook-form
que a validação dos dados do formulário será feita com o loginSchema
do yup
.
Assim, a biblioteca retorna as propriedades do formulário gerado pelo useForm
, que poderá ser utilizado em um componente de formulário de login.
import { useAuth } from "shared/libs";
import { useLoginLib, SubmitLoginHandler } from "./login.lib";
export const useLogin = () => {
const { login } = useAuth();
const { handleSubmit, register, formState } = useLoginLib();
const handleLogin: SubmitLoginHandler = async (data) => {
await login(data);
};
return { formState, register, handleSubmit, handleLogin };
};
Este código é uma função de hook, useLogin
, que retorna informações úteis para a renderização e manipulação de um formulário de login. Ele usa o hook useAuth
da biblioteca compartilhada shared/libs
e o hook useLoginLib
que importa o hook useForm
e a definição de uma interface e um esquema de validação Yup para os dados do formulário de login.
A função useLogin
faz uso dos hooks importados para definir a função handleLogin
, que será chamada quando o formulário for enviado. Esta função chama a função login
da biblioteca compartilhada useAuth
e passa os dados do formulário para ela. Além disso, a função useLogin
retorna os valores retornados pelos hooks useLoginLib
e useAuth
, incluindo formState
, register
, handleSubmit
e handleLogin
.
formState
é o estado atual do formulário, incluindo informações como se o formulário está sendo enviado, se há erros de validação, entre outros. register
é a função que permite registrar inputs no formulário para que sejam validados. handleSubmit
é a função que controla o envio do formulário e handleLogin
é a função que será chamada quando o formulário for enviado.
import { Form } from "shared/ui";
import { useLogin } from "./login.hook";
export const LoginForm = () => {
const { formState, handleSubmit, register, handleLogin } = useLogin();
const formProps = {
formState,
register,
handleCustomSubmit: handleLogin,
handleSubmit,
formControls: [
{ type: "email", label: "Email", name: "email" },
{ type: "password", label: "Senha", name: "password" },
],
buttonLabel: "Entrar",
};
return <Form {...formProps} />;
};
O código acima é uma implementação de um formulário de login usando a biblioteca React Hook Form e o componente genérico Form.
Ele começa importando o componente Form
da biblioteca shared/ui
e o hook useLogin
de ./login.hook
. Em seguida, a função LoginForm
é definida.
Dentro da função LoginForm
, o hook useLogin
é invocado para retornar as informações necessárias para configurar o formulário:
-
formState
: objeto que contém informações sobre o estado atual do formulário, como se ele está sendo enviado, se há erros, entre outros. -
handleSubmit
: função que é invocada quando o formulário é submetido. -
register
: função que permite registrar os inputs no formulário para serem gerenciados pelo React Hook Form. -
handleLogin
: função que é chamada quando o formulário é submetido e contém a lógica de autenticação do usuário.
Em seguida, as propriedades do formulário são definidas em formProps
. Essas propriedades incluem:
-
formState
: objeto que contém informações sobre o estado atual do formulário. -
register
: função que permite registrar os inputs no formulário. -
handleCustomSubmit
: função que é invocada quando o formulário é submetido. Nesse caso, é a funçãohandleLogin
. -
handleSubmit
: função que é invocada quando o formulário é submetido. -
formControls
: array de objetos que representam os controles (inputs) do formulário. Cada objeto possui informações sobre o tipo de controle, o rótulo, o nome e outras informações. -
buttonLabel
: texto que será exibido no botão de envio do formulário.
Por fim, o componente Form
é retornado e suas propriedades são passadas para ele através do spread operator ({...formProps}
).
Tá mas... como eu transformo tudo isso num componente ainda mais genérico?
Para transformar em um componente genérico, você pode fazer algumas alterações nas suas libs, hooks e componentes. Aqui estão algumas sugestões:
Em
login.lib
, você pode separar as validações Yup para uma lib separada e importá-la nologin.lib
. Desta forma, é possível reutilizar a mesma validação em outros formulários.Em
login.hook
, você pode criar um hook genérico que possa ser usado em outros formulários. Por exemplo, você pode criar um hookuseFormWithAuth
que receba a função de autenticação como uma propriedade e usar a mesma estrutura que você usou emuseLogin
.No componente
LoginForm
, você pode criar um componente genérico que possa ser reutilizado em outros formulários. Por exemplo, você pode criar um componenteFormWithAuth
que receba o formulário em si, as validações, o formato da resposta e a função de autenticação. Este componente pode então passar essas propriedades para o componenteForm
que você criou anteriormente.
Essas são apenas algumas sugestões e existem várias maneiras de tornar o código genérico, mas a ideia é passar as propriedades relevantes para o componente genérico, como o formulário em si, as validações, o formato da resposta e a função de autenticação, para que ele possa ser reutilizado em outros formulários.
O código ficaria da seguinte forma:
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import { SubmitHandler, useForm } from "react-hook-form";
import { Form } from "shared/ui";
interface FormData {
[key: string]: string;
}
interface FormConfig<T> {
fields: { type: string; label: string; name: keyof T }[];
buttonLabel: string;
validationSchema: yup.ObjectSchema<yup.Shape<{}, T>>;
onSubmit: SubmitHandler<T>;
}
export const useGenericForm = <T extends FormData>(config: FormConfig<T>) => {
const formProps = useForm({ resolver: yupResolver(config.validationSchema) });
const formState = formProps.formState;
const register = formProps.register;
const handleSubmit = formProps.handleSubmit;
return { formState, register, handleSubmit, config };
};
export const GenericForm = <T extends FormData>(props: {
config: FormConfig<T>;
}) => {
const { formState, register, handleSubmit, config } = useGenericForm(
props.config
);
const formProps = {
formState,
register,
handleSubmit,
formControls: config.fields,
buttonLabel: config.buttonLabel,
};
return <Form {...formProps} />;
};
E para utilizar esse componente, basta passar a configuração do formulário como propriedade, por exemplo:
import { useAuth } from "shared/libs";
import { GenericForm } from "./generic.form";
const loginSchema = yup.object().shape({
email: yup.string().email("Email inválido").required("Email é obrigatório"),
password: yup.string().required("Senha é obrigatória"),
});
const handleLogin = async (data: LoginFormData) => {
await useAuth().login(data);
};
const LoginForm = () => {
const config = {
fields: [
{ type: "email", label: "Email", name: "email" },
{ type: "password", label: "Senha", name: "password" },
],
buttonLabel: "Entrar",
validationSchema: loginSchema,
onSubmit: handleLogin,
};
return <GenericForm config={config} />;
};
E a seguir temos o exemplo de uso do LoginForm anterior:
import Router from "next/router";
import type { NextPage } from "next";
import { Head, Flex, Text } from "shared/ui";
import { useAuth } from "shared/libs";
import { useEffect } from "react";
import { LoginForm } from "features/auth/login";
export const Login: NextPage = () => {
const { isAuthenticated } = useAuth();
useEffect(() => {
if (isAuthenticated) {
Router.push("/home");
}
}, [isAuthenticated]);
return (
<>
<Head
title="Belezix Admin | Login"
description="Página de login do painel Admin do Belezix"
/>
<Flex minW="100%" justifyContent="center">
{!isAuthenticated && (
<Flex mt="15%">
<LoginForm />
</Flex>
)}
</Flex>
</>
);
};
Este código define a página de login para o painel administrativo de um aplicativo. O código usa bibliotecas da interface do usuário (shared/ui
) e a biblioteca de autenticação (shared/libs
).
A página de login é criada como um componente NextPage
, que é uma forma de definir páginas dinâmicas usando a biblioteca Next.js. O componente NextPage
é importado do next
e é definido como Login
.
O componente useAuth
é usado para obter informações sobre o estado de autenticação do usuário. isAuthenticated
é uma das informações retornadas pelo useAuth
.
O gancho useEffect
é usado para verificar se o usuário está autenticado a cada renderização. Se o usuário estiver autenticado, o Router.push
é usado para redirecioná-lo para a página inicial do aplicativo (/home
).
A página de login é composta de um cabeçalho (Head
), um contêiner (Flex
) e um formulário de login (LoginForm
). O cabeçalho Head
é usado para definir o título e a descrição da página. O contêiner é usado para centralizar o formulário na tela.
O formulário de login é exibido apenas se o usuário não estiver autenticado. Se o usuário estiver autenticado, a página não será exibida.
O formulário é importado do arquivo features/auth/login
e é renderizado dentro do contêiner. A renderização do formulário é controlada pelos parâmetros passados ao componente Form
usando a sintaxe de espalhamento (...formProps
).
Como a metodologia Feature Sliced Design se encaixa nisso?
A metodologia Feature Sliced Design se baseia em desenvolver as funcionalidades do seu aplicativo de forma separada e autônoma, para depois juntá-las e formar o produto final. No exemplo de login form, a pasta features/auth/login
contém todos os componentes, hooks e validações necessários para o formulário de login. Dessa forma, se for necessário fazer alguma alteração no formulário, será possível fazê-lo apenas nessa pasta, sem precisar mexer em outras partes do aplicativo. Além disso, se a equipe precisar desenvolver uma nova feature, será mais fácil fazê-lo de forma autônoma sem interferir nas outras funcionalidades já existentes.
O arquivo LoginForm
é o componente visual responsável por exibir o formulário, enquanto o hook useLogin
é responsável por lidar com o submit do formulário e com a validação dos dados. Já o hook useLoginLib
é responsável por inicializar e configurar o hook useForm
do React Hook Form, passando a validação através do Yup.
Dessa forma, ao separar o formulário de login em uma pasta específica, é possível desenvolver, testar e manter a feature de forma mais organizada, mantendo a responsabilidade de cada arquivo claramente separada.