Propriedade as em componentes React com Typescript
Tags: react, typescript, javascript, webdev
Published: 2022-12-13 00:17:37

Salve Salve! 🖖  

Vou compartilhar com vocês o pouquinho que eu sei sobre tipagens condicionais e espero conseguir fazer você entender mais sobre isso.

### Nota

*Este post não serve de documentação e não tem como objetivo ser o mais completo possível. Ele representa apenas a minha visão sobre a utilização de conditional types que obtive ao longo do tempo.*

## Introdução

Não é de hoje que o Typescript vem se tornando cada vez mais utilizado no cenário do _frontend_, muitos devs já começam seus projetos já com o Typescript habilitado (eu sou assim). 

Dando uma breve contextualizada. Typescript é um linguagem de programação fortemente tipada construída encima do Javascript. Tipar variáveis, objetos, funções, etc facilita demais no descobrimento de propriedades durante o desenvolvimento. 

Tipagens condicionais não são comumente utilizadas porém há casos de uso que elas se encaixam muito bem. Como o proprio nome já diz tudo, se trata de uma tipagem baseada em uma condição. Vou te mostrar um caso de uso que eu acho muito legal em um contexto **react**, é um pouquinho mais avançado mas espero conseguir explicar direitinho 😀 

## Show me the code!

Sem mais enrolação vamos desenvolver durante este post a tipagem da propriedade `as` do componente `Button` em _react_. Você já deve ter se deparado com algum componente de uma biblioteca de componentes como _ChakraUI_ e _MaterialUI_ a utilização dessa propriedade.

Não pretendo implementar exatamente igual a essas bibliotecas mas vou exemplificar aqui como fazer uma tipagem condicional básica para satisfazer o componente. Basicamente o componente `Button` poderá receber o nome de um elemento HTML que será o utilizado como _wrapper_ dentro do componente.

Abaixo temos o código do componente sem tipagens:

```jsx
const Button = (({ as, children, ...restProps }) => {
  const Element = as || "button";
  return (
		<Element {...restProps}>{children}</Element>
	);
})

export const App = () => {
	return (
		<Button type="button">Abrir</Button>
	);
}
```

A partir desse código criaremos nossas tipagens. Vamos começar criando a tipagem do componente `Button` como queremos utiliza-lo.

```jsx
interface IButton {
	as: ??;
	icon?: React.Element;
}
```

Acima temos a interface básica do nosso componente, a gente espera receber a propriedade `as` com valor do elemento HTML que envolverá nosso botão. Além disso adicionei uma outra propriedade que será opcional mas sem mistérios nela, é só pra exemplificar com outras propriedades.

### Desafio

Aqui nos deparamos com nosso desafio. Queremos que quando o código abaixo for chamado:

```jsx
<Button as="button">Enviar</Button>
```

No autocomplete das propriedades que este componente pode receber, deverá aparecer as propriedades do elemento *HTML* que passamos na propriedade `as`, ou seja, no exemplo, _button_. Além das outras problemas que colocamos na interface `IButton` .

### Typescript Generics

Precisamos entender mais algumas coisas a mais sobre Typescript. *Generics* é uma forma de enviar tipos dinâmicos para dentro de alguma interface. Você já deve ter utilizado ele se você já trabalhou com _axios_. Olha o código abaixo:

```jsx
await axios.get<IResponseData>('https://github.com/guiselair')
```

Neste exemplo, foi passado um _generic_ informando qual será a tipagem do retorno da requisição *get*. Utilizaremos desse principio para construir nossas tipagens condicionais.

Além disso é possível fazer com _generics_ condições, mais ou menos assim, “Se for tal tipo, pega tal tipagem, senao, pega outra” ou até “Se o tipo foi este, pega este tipo”. Não se preocupe que abaixo vou explicar melhor.

### Tipando propriedade `as`

Vamos atualizar a interface do `IButton`:

```jsx
interface IButton<E extends React.ElementType = React.ElementType> {
	as: E;
	icon?: React.ElementType;
}
```

Com o tipo acima, agora garantimos que o que for escrito dentro da propriedade `as` será um elemento *HTML* válido, caso não for um erro semelhante a este será exibido:

<aside>
**🔥 Type '"div1"' is not assignable to type 'ElementType<any> | undefined’**
</aside>

A primeira parte do nosso desafio esta pronta, *tipar* a propriedade `as` . Agora temos que definir as tipagens das propriedade deste componente levando em consideração o valor recebido pelo `as` .

### Tipando `props` do componente Button

Para pegar todas as propriedades do elemento *HTML* recebido precisamos utilizar um código meio complicadinho de entender mas vamos com calma. O código abaixo, faz uma condicional em formato de _generics_ e caso seja uma das opções (`keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>`) retorna uma tipagem do tipo `JSX.LibraryManagedAttributes` . 

Segunda a documentação da tipagem, este tipo `JSX.LibraryManagedAttributes` define uma transformação nas `props` do componente. Então basicamente, este tipo vai unir outros tipos e retornar uma união entre eles.

```jsx
type PropsOf<
  E extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>
> = JSX.LibraryManagedAttributes<E, React.ComponentPropsWithRef<E>>;
```

_Obs: Estou simplificando aqui a explicação por dois motivos, este código você não precisara decorar e este é o conhecimento que eu tenho até então sobre isso_ 😄

Agora que já conseguimos pegar todos os tipos do elemento *HTML* precisamos mesclar o tipo `IButton` com as as propriedades do elemento. Para isso vamos criar um novo _type_:

```jsx
export type IButtonProps<E extends React.ElementType> = IButton<E> &
  Omit<PropsOf<E>, keyof IButton>;
```

O código acima extende a interface `IButton` com o resultado da expressão (`Omit<PropsOf<E>, keyof IButton>` ). O auxiliar `Omit` remove da tipagem `PropsOf<E>` as chaves do tipo `IButton` para não haver conflitos de nomes de propriedades, isso poderia geral erros no Typescript.

Com este trecho pronto agora basta a gente adicionar este novo tipo como retorno do nosso componente. Isso é feito no trecho de código abaixo:

```jsx
export const Button = <E extends React.ElementType = "button">({
  as,
  ...restProps
}: IButtonProps<E>) => {
  const Element = as || "button";
  return <Element {...restProps}>{children}</Element>
};
```

E pronto!! Agora nosso componente já deve aceitar elemento HTML recebidos pela propriedade `as` e adicionar todas as propriedades deste elemento no nosso componente `Button` além das nossas propriedades personalizadas.

Abaixo esta o código completo do componente:

```tsx
import React from "react";

type PropsOf<
  E extends keyof JSX.IntrinsicElements | React.JSXElementConstructor<any>
> = JSX.LibraryManagedAttributes<E, React.ComponentPropsWithRef<E>>;

export interface IButton<E extends React.ElementType = React.ElementType> {
  as?: E;
  icon?: React.Element;
}

export type IButtonProps<E extends React.ElementType> = IButton<E> &
  Omit<PropsOf<E>, keyof IButton>;

export const Button = <E extends React.ElementType = "button">({
  as,
  ...restProps
}: IButtonProps<E>) => {
  const Element = as || "button";
  return <Element {...restProps} />;
};
```

#### Exemplo em execução

{% embed https://codesandbox.io/embed/conditional-types-typescript-0t66yi?fontsize=14&hidenavigation=1&theme=dark %}

## Top demais!! 😻

Chegamos ao fim de mais um post e então, gostou?

Espero que eu tenho conseguido ajudar um pouquinho você a compreender melhor o Typescript e tirar sua curiosidade de como pode ser feito a propriedade `as` que aparece em algumas bibliotecas. Achou que eu falei algo errado? Ficou com dúvidas? Vamos discutir melhor sobre nos comentários.

See you soon!

### Referências

1. [React polymorphic components with TypeScript | by Iskander Samatov | ITNEXT](https://itnext.io/react-polymorphic-components-with-typescript-f7ce72ea7af2)
2. [Polymorphic `as` prop for React components with TypeScript (github.com)](https://gist.github.com/kripod/4434e7cecfdecee160026aee49ea6ee8)