Componentes Genéricos no React (Table) ⚛️
💡 Este post é voltado para componentes tipados usando TypeScript
Uma das principais intenções na hora de criação de componentes em um projeto, é poder reutilizar um mesmo trecho de código em vários lugares, reaproveitando a lógica ou parte da interface. Para que o reaproveitamento seja mais fácil, é importante que o componente seja o mais genérico quanto possível, inclusive em suas tipagens.
Vamos a um exemplo prático de um componente genético no React, construindo um componente de <Table />
.
Primeiro vamos falar um pouco sobre o exemplo.
O que vamos construir é parte de um site de petshop, que possui duas tabelas, a primeira exibe dados dos clientes e, a segunda exibe dados dos pets. Os tipos são os seguintes:
interface IClient {
name: string;
phone: string;
}
interface IPet {
name: string;
specie: 'dog' | 'cat' | 'bird';
}
Ficou claro que precisaremos de uma tabela que consiga exibir vários tipos de dados. Vamos fazer isso por inferir o tipo das props que foram passadas.
A base do nosso componente será essa:
interface IProps<T> {
data: T[];
columns: {
heading: string;
element: (row: T) => JSX.Element;
}[];
}
export function Table<T>(props: IProps<T>) {
// ...
}
Um scaffold bem comum para um componente React, mas com algo diferente, é possível ver que existe um tipo T
sendo recebido na função e passado para a interface IProps
, aonde esse tipo será inferido pelo TypeScript.
Então ao usarmos:
const [pets, setPets] = useState<IPet[]>([]);
const [clients, setClients] = useState<IClient[]>([]);
// ...
<Table
data={pets}
columns={[
{
heading: 'Name',
element: (row) => <p>{row.name}</p>,
},
{
heading: 'Specie',
element: (row) => <p>{row.specie}</p>,
},
]}
/>
O TypeScript entende que estamos lidando com um array de IPet
e por isso usamos as chaves name
e specie
sem erros (inclusive recebemos intelisense sobre elas). Porém, se alterarmos o valor do atributo data
para o array de clientes, o TypeScript vai nos alertar de que a propriedade "specie" não existe no tipo IClient
.
O que acontece por debaixo dos panos é que inicialmente o TypeScript não sabe o que T
significa, mas quando passamos o array pets
para a propriedade data
, ele associa data
= pets
, logo T
= IPet
. Então nossa tabela consegue inferir o tipo de qualquer dado que for passado para ela!
Nosso componente de tabela completo seria algo do tipo:
import React from 'react';
interface IProps<T> {
data: T[];
columns: {
heading: string;
element: (row: T) => JSX.Element;
}[];
}
export function Table<T>({ data, columns }: IProps<T>) {
return (
<table>
<thead>
<tr>
{columns.map(({ heading }, index) => (
<th key={`column-${index}`}>
<p>{heading}</p>
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, rowIndex) => (
<tr key={rowIndex}>
{columns.map((column, columnIndex) => (
<td key={`${rowIndex}-${columnIndex}`}>
{column.element(row)}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
💡 A parte de CSS não está no escopo desse post, então se sinta livre para estilizar a table!
Enfim, esse foi um review básico sobre componentes de tipagens genérica usando React, espero ter ajudado!
Se precisarem tirar alguma dúvida eu fico feliz em responder e bora codar 🚀