Alura > Cursos de Front-end > Cursos de React > Conteúdos de React > Primeiras aulas do curso React com Typescript: aplique Hooks e Context API tipados

React com Typescript: aplique Hooks e Context API tipados

Construindo uma modal acessível - Apresentação

Introdução ao Curso de TypeScript com React

Você já se perguntou como seria utilizar o TypeScript para tipar os hooks do React? E não estamos falando apenas de useState ou useEffect, mas também de hooks mais avançados, como useRef, useMemo e useCallback. Se essa já foi a sua dúvida, você está no lugar certo.

Meu nome é Nailton, sou instrutor na escola de FrontEnd.

Audiodescrição: Nailton é um homem de pele negra, com olhos e cabelos escuros. No momento, está utilizando óculos com armação retangular e veste uma camisa amarela.

Objetivos do Curso

Seja bem-vindo a este curso de React, onde vamos aprender a tipar corretamente os hooks e também entender muito sobre a Context API e como o TypeScript nos ajuda a trabalhar e a fazer a gestão dos estados de forma global.

Aplicação Prática: PolpaApp

Qual aplicação iremos trabalhar? Estamos na aplicação do PolpaApp, uma aplicação de gerenciamento financeiro. Nela, podemos inscrever nosso nome e renda mensal, clicar em "ir para o app" e ser levados para a tela de dashboard. Nesta tela, teremos nosso nome, um orçamento diário disponível, um gráfico com gastos por categoria e uma lista de transações, representando nossa movimentação financeira do dia.

Tudo isso será construído junto com o TypeScript e a Context API, para lidar com o gerenciamento de estados de forma global.

Conteúdo do Curso

Neste curso, abordaremos como as transações afetam o orçamento diário disponível e como elas são utilizadas para calcular os gastos por categoria. Exploraremos como o TypeScript auxilia no trabalho com o contexto global e os estados globais das aplicações.

Além disso, aprenderemos a criar uma modal acessível. Por exemplo, ao clicar em "adicionar transação", uma modal será aberta, permitindo navegação através da tecla "Tab". Será possível fechá-la pressionando "Esc" ou clicando fora dela. Vamos aprender a implementar isso utilizando o Hook useRef e o Hook useImperativeHandle.

Integração com API e Considerações Finais

Também conectaremos uma API à nossa aplicação, criando-a do zero. Entenderemos como o TypeScript pode ser útil tanto no front-end quanto no back-end. Embora este curso não seja focado em back-end, é importante mostrar como tipar o retorno das funções assíncronas, o que é essencial nesse contexto.

Para acompanhar este curso, é necessário ter conhecimentos básicos de React e noções iniciais de TypeScript. Estamos ansiosos para aprender juntos ao longo deste curso. Vamos começar!

Construindo uma modal acessível - Controlando a modal com useRef

Introdução ao PolpaApp e Tela Inicial

Na aplicação do PolpaApp, temos a tela inicial. Ao avançarmos para a URL /home, visualizamos a tela da aplicação com nosso nome e o orçamento diário disponível. No entanto, essas informações estão fixas no código, ou seja, hardcoded. Precisamos trabalhar ao longo do curso para criar um contexto global para esses dados e estados, centralizando-os em um único local para consumo na aplicação.

Funcionalidade de Adição de Transações

Inicialmente, temos um botão no componente de movimentação financeira para adicionar transações. Ao clicarmos no botão de adicionar transação, uma modal é aberta, permitindo a adição de transações na aplicação. No entanto, por enquanto, essa funcionalidade não está completa e será desenvolvida. Um ponto a ser observado é que, embora a modal funcione e possamos visualizá-la, ela não fecha ao clicarmos fora dela. Ela apenas fecha ao clicarmos no botão de fechar (X) ou no botão de cancelar. Isso representa um problema de acessibilidade.

Melhorando a Acessibilidade da Modal

Podemos utilizar nossos conhecimentos em React e TypeScript para tornar essa modal mais acessível. No código, ao abrir o Visual Studio Code, procuramos dentro da pasta src, em "components", a pasta de modal e acessamos o arquivo index.tsx. Nesse arquivo, entre as linhas 11 e 18, encontramos a interface da modal com as modalProps, que incluem um ícone, um título e uma propriedade para fechar. Vamos deletar a propriedade de fechar na linha 14, pois será substituída. Também deletaremos a propriedade "está aberta" na linha 15.

Definindo a Interface ModalProps

Para começar, vamos definir a interface ModalProps sem as propriedades que serão removidas:

interface ModalProps {
  icon: React.ReactNode;
  titulo: string;
  children: React.ReactNode;
  aoClicar: () => void;
}

Refatoração do Componente de Transação Modal

No componente de transação modal, acessando o index.tsx, observamos que a modal recebe as propriedades de fechar e "está aberta". No componente de transações, na linha 62, o estado "está aberta modal" é passado de um nível superior para o componente de transação modal e, em seguida, para o componente de modal, caracterizando um prop drilling. Esse é um problema comum ao lidar com estados no React, que o Context API visa resolver.

Refatoração do Componente Modal

O primeiro passo é refatorar a modal para torná-la mais leve e limpa, permitindo manipulação de abertura e fechamento, além de torná-la acessível com TypeScript. Já deletamos da interface as propriedades de fechar e "está aberta". Agora, dentro das props do componente de modal, também deletamos essas propriedades.

Dentro do código, temos um useEffect que verifica o valor current da modal, retornado pelo useRef. Ele verifica se a modal está aberta, utilizando a referência dialogNode para mostrar ou fechar a modal. Esse useEffect pode gerar problemas de renderização, pois roda quando a aplicação sofre um efeito colateral, ou seja, quando algum estado muda. Os estados monitorados estão na linha 48: "está aberta" e "ao fechar", que foram recebidos como props e já deletados.

Como deletamos, não vamos mais precisar do useEffect, então podemos deletar da linha 26 até a linha 48, e nossa modal está ficando mais limpa. Na linha 24, deixaremos o const dialog, dialogRef, com o useRef, passando para ele, como um generics, o HTMLDialogElement. Já vimos que esses HTMLDialogElement são uma espécie de atributos intrínsecos que todo elemento que podemos utilizar dentro do JSX possui. Temos dialogElement, divElement, inputElement, e aqui estamos passando para um generics para tipar nosso useRef.

Implementação do forwardRef no Componente Modal

Vamos refatorar o componente Modal removendo o useEffect e utilizando o useRef:

const Modal = ({
  icon,
  titulo,
  children,
  aoClicar,
}: ModalProps) => {
  const dialogRef = useRef<HTMLDialogElement>(null);

  return (
    <ModalOverlay>
      <ModalContainer ref={dialogRef}>
        <ModalHeader>
          <div>
            {icon} {titulo}
          </div>
          <CloseButton>x</CloseButton>
        </ModalHeader>
        {children}
        <ButtonGroup>
          <Botao $variante="secundario">
            Cancelar
          </Botao>
          <Botao $variante="primario" onClick={aoClicar}>
            Adicionar
          </Botao>
        </ButtonGroup>
      </ModalContainer>
    </ModalOverlay>
  );
};

Expondo Métodos com forwardRef

No closeButton, temos um onClick ao fechar, que podemos deletar por enquanto, e no botão na linha 38, também temos um onClick de ao fechar. Queremos expor, por enquanto, estamos utilizando o useRef para gerar uma referência na nossa modal, passando esse dialogRef para o nosso modal container, mas também queremos expor esse elemento, no caso o dialogElement, para o nosso componente pai, que utilizaremos no app de transações.

Para isso, podemos utilizar o forwardRef. Estamos utilizando a versão 18 do React. No momento em que estiverem fazendo este curso, pode ser que já tenha sido lançada a versão 19 ou alguma posterior, na qual isso não será mais necessário. No entanto, no momento, estamos utilizando o forwardRef. Inclusive, deixaremos uma atividade explicando as diferenças de como utilizamos com o forwardRef e como provavelmente utilizaremos com a versão do React 19 e superior.

Por enquanto, estamos utilizando a versão 18, então precisamos fazer o seguinte: utilizar o forwardRef na linha 18, logo antes de abrir os parênteses da aplicação, passando um forwardRef. Para esse forwardRef, é como se colocássemos todo o componente dentro dele. Colocamos um parêntese depois do nome forwardRef e, na linha 48, um parêntese depois das chaves que fecham o componente. Dentro desse forwardRef, temos as props do nosso componente e também a ref. Passamos uma vírgula na linha 23, depois de modalprops, e temos uma prop chamada ref.

Implementação do forwardRef no Componente Modal

Vamos implementar o forwardRef no componente Modal:

const Modal = forwardRef<ModalHandle, ModalProps>(({
  icon,
  titulo,
  children,
  aoClicar,
}, ref) => {
  const dialogRef = useRef<HTMLDialogElement>(null);

  return (
    <ModalOverlay>
      <ModalContainer ref={dialogRef}>
        <ModalHeader>
          <div>
            {icon} {titulo}
          </div>
          <CloseButton>x</CloseButton>
        </ModalHeader>
        {children}
        <ButtonGroup>
          <Botao $variante="secundario">
            Cancelar
          </Botao>
          <Botao $variante="primario" onClick={aoClicar}>
            Adicionar
          </Botao>
        </ButtonGroup>
      </ModalContainer>
    </ModalOverlay>
  );
});

Definindo a Interface ModalHandle

Feito isso, para onde passaremos essa ref? Pegamos essa ref depois do modalprops. Uma coisa interessante é que o modalprops pode ser removido da linha 23. Vamos dar um "Ctrl+X" no modalprops, pegar apenas as chaves com as props e passá-las ao lado do forwardRef na linha 18. Abrimos a anotação de generics, passamos o modalprops, que é a nossa interface, e teremos também outra interface que precisaremos criar, que é uma interface para lidar com os métodos da nossa modal. Vamos aproveitar e passar uma vírgula, e passar o modalhandle antes do modalprops. O modalhandle ainda não existe, então vamos criá-lo.

Escreveremos interface modalhandle, daremos um "Ctrl+V", abriremos e fecharemos as chaves. O modalhandle terá dois métodos, na verdade, dois tipos. Vamos exportar essa interface, pois é importante, e utilizá-la em outro lugar. Na linha 18, escrevemos export interface modalhandle. Dentro do modalhandle, digitamos que ele terá dois métodos: um será open, tipado como uma função que não retorna nada, então escrevemos dois pontos, abrimos e passamos a anotação de função de seta (arrow function), e escrevemos void, indicando que a função não retorna nada. Teremos também close, então escrevemos close, dois pontos, abrimos e fechamos os parênteses, e passamos a anotação de função de seta, que são dois, sem o "e" no final, pois são duas funções, dois métodos que utilizaremos para controlar nossa modal de abrir e fechar. Vamos expor esses métodos futuramente.

Definindo a interface ModalHandle:

export interface ModalHandle {
  open: () => void;
  close: () => void;
}

Conclusão da Refatoração

O useEffect que não estamos utilizando pode ser removido da linha 1. O que fizemos foi justamente refatorar a modal, removendo o useEffect que estávamos utilizando para aplicar um efeito colateral com base em um estado que recebíamos de um componente acima. Criamos essa nova interface, modalhandle, que será muito importante agora. Precisamos expor os métodos para controlar a nossa modal, e ela receberá novas props. Vamos resolver isso em um próximo vídeo. Nos vemos lá.

Construindo uma modal acessível - Expondo métodos com useImperativeHandler

Introdução ao Uso de useRef e forwardRef

Na última aula, utilizamos o useRef para criar uma referência para nossa modal e expusemos essa referência com o forwardRef para o componente Py. Também criamos uma interface chamada ModalHandle, que possui dois métodos: open e close, ambos sem retorno. Isso é importante para expor esses métodos ao componente Py, permitindo controlar a abertura e fechamento da modal sem depender do estado, como fazíamos anteriormente. Isso elimina o prop drilling que tínhamos em nossa aplicação.

Implementação da Função fechaModal

Para expor esses métodos, utilizaremos o hook useImperativeHandle. Antes disso, criaremos uma função chamada fechaModal. Vamos começar definindo a função fechaModal como uma constante:

const fechaModal = () => {}

Esta função receberá uma arrow function e, dentro dela, acessaremos o dialog.ref. O useRef é um hook que retorna um objeto com a propriedade current, que representa o elemento no DOM. Muitas pessoas utilizam o useRef para controlar diretamente o DOM, pois entre re-renderizações, esse valor não muda, ao contrário do estado. É muito útil para trabalhar com modais e carrosséis, por exemplo.

Agora, vamos acessar o dialogRef.current dentro da função fechaModal:

const fechaModal = () => {
    dialogRef.current
}

No current, precisamos verificar se ele existe. O TypeScript exige que tudo esteja correto, então utilizamos a interrogação para verificar apenas o current. Se houver um current, executamos uma ação; caso contrário, ignoramos. Utilizamos o método close do dialog:

const fechaModal = () => {
    dialogRef.current?.close()
}

Uso de styled-components e Configuração de useImperativeHandle

Na linha 33, temos o modalContainer, que é a tag dialog. Como estamos usando styled-components, temos um componente customizado. Uma curiosidade sobre styled-components é que, ao utilizá-lo com React e TypeScript, todos os tipos e atributos que uma tag HTML recebe são aceitos por padrão. Na linha 33, ao colocar o cursor no final do dialogRef e pressionar Ctrl + Espaço, aparecem todos os métodos aceitos pelo componente, incluindo close e showModal.

Isso evita o uso extensivo de atributos intrínsecos, que passávamos como generics ao definir as propriedades do componente. Com isso em mente, criamos a função fechaModal e utilizamos o hook useImperativeHandle. Certificamo-nos de que ele foi importado na linha 1:

import React, { forwardRef, useImperativeHandle, useRef } from "react";

Esse hook aceita alguns parâmetros, incluindo um ref, que recebemos no componente na linha 24, e uma função. Vamos começar a configurar o useImperativeHandle:

useImperativeHandle(ref, () => ({

}))

Retornamos um objeto com dois métodos: open, que recebe uma função de seta e utiliza o dialogRef.current com showModal, e close, que chama a função fechaModal. Vamos adicionar o método open:

useImperativeHandle(ref, () => ({
    open: () => dialogRef.current?.showModal(),
}))

E agora, adicionamos o método close:

useImperativeHandle(ref, () => ({
    open: () => dialogRef.current?.showModal(),
    close: fechaModal,
}));

Exposição de Métodos e Controle de Eventos

Dessa forma, com o useImperativeHandle, expomos esses dois métodos para o componente Py ao utilizar a modal. As vantagens disso incluem maior controle e organização do código, além de evitar o prop drilling.

Como já mencionamos, evitamos o estado e o controle, que ficam dentro do nosso componente de modal. Podemos, então, dizer ao componente pai que ele pode utilizar determinados métodos. Podemos expor todos os métodos ou apenas os que desejarmos. No caso, estamos expondo apenas os métodos open e close para o nosso componente pai, permitindo que ele controle a abertura e fechamento da modal.

Outra coisa que precisamos fazer é que nossa modal receba outra prop. Queremos saber, por exemplo, se a pessoa usuária clicou fora da modal. Um problema que tínhamos era que a modal não fechava quando clicávamos fora. Vamos corrigir isso criando uma nova prop chamada cliqueForaModal, que terá um valor padrão de true. Vamos definir essa prop na interface ModalProps:

interface ModalProps {
  icon: React.ReactNode;
  titulo: string;
  children: React.ReactNode;
  aoClicar: () => void;
  cliqueForaModal?: boolean;
}

Implementação da Função aoClicarForaModal

Vamos criar uma função na linha 37, uma constante chamada aoClicarForaModal, que será uma arrow function. Queremos que, quando a pessoa usuária clicar fora da modal, chamemos um método para fechar a modal. Vamos fazer isso da forma mais correta possível. Dentro dos parênteses da função, teremos um evento do tipo React.MouseEvent, que será um evento de clique. Queremos clicar fora da modal, então passamos React.MouseEvent com a anotação de generics para um HTMLDialogElement:

const aoClicarForaModal = (evento: React.MouseEvent<HTMLDialogElement>) => {

}

Dentro do corpo da função, faremos um if para verificar duas condições: se cliqueForaModal é verdadeiro e se event.target é igual a dialogRef.current. Ou seja, se foi clicado fora da modal, chamaremos a função fechaModal:

const aoClicarForaModal = (evento: React.MouseEvent<HTMLDialogElement>) => {
  if (cliqueForaModal && evento.target === dialogRef.current) {
    fechaModal();
  }
}

Vamos passar essa função aoClicarForaModal no modalContainer com o evento onClick:

<ModalContainer ref={dialogRef} onClick={aoClicarForaModal}>

Integração com Botões de Ação

Como já temos a função fechaModal, podemos utilizá-la também no botão da linha 56. No evento onClick, passaremos uma função de seta que chamará o evento aoClicar, que vem do componente pai, e também chamará fechaModal:

<Botao $variante="primario" onClick={() => {
    aoClicar();
    fechaModal();
}}>
  Adicionar
</Botao>

Podemos chamar fechaModal também no botão de cancelar, pois ao cancelar a transação, a modal deve ser fechada. Passamos onClick para chamar fechaModal no closeButton, que é o botão de X:

<Botao $variante="secundario" onClick={fechaModal}>Cancelar</Botao>
<CloseButton onClick={fechaModal}>x</CloseButton>

Conclusão e Próximos Passos

Para recapitular, estamos utilizando o useImperativeHandle para expor os métodos de abrir e fechar a modal, showModal e close, para o componente pai. Podemos fazer isso com a modal ou com outros componentes, como um input, se quisermos expor algum método, como jogar o foco para o componente. Deixaremos uma atividade para explorar mais sobre o useImperativeHandle e outros exemplos.

O que falta é testarmos nosso componente de modal, mas faremos isso no próximo vídeo. Nos vemos lá.

Sobre o curso React com Typescript: aplique Hooks e Context API tipados

O curso React com Typescript: aplique Hooks e Context API tipados possui 173 minutos de vídeos, em um total de 56 atividades. Gostou? Conheça nossos outros cursos de React em Front-end, ou leia nossos artigos de Front-end.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda React acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas