Artigo original: The React TypeScript Cheatsheet – How To Set Up Types on Hooks

O TypeScript permite a verificação de tipos em seu código, o que deixa o código mais robusto e compreensível.

Neste guia, mostrarei como definir os tipos do TypeScript em hooks do React (useState, useContext, useCallback e assim por diante).

Vamos começar.

Definir tipos no useState

O hook useState permite gerenciar o state (em português, estado) em sua aplicação do React. É o equivalente a this.state em um componente de classe.

import * as React from "react";

export const App: React.FC = () => {
 const [counter, setCounter] = React.useState<number>(0)
 
 return (
    <div className="App">
      <h1>Result: { counter }</h1>
      <button onClick={() => setCounter(counter + 1)}>+</button>
      <button onClick={() => setCounter(counter - 1)}>-</button>
    </div>
  );
}

Para definir tipos em um hook useState, você precisa passar em <> o tipo de state. Você pode usar um tipo com união (<number | null>) se não tiver um state inicial.

Definir tipos no useRef

O hook useRef retorna um objeto de referência mutável, que permite o acesso aos elementos do DOM.

import * as React from "react";

export const App: React.FC = () => {
  const myRef = React.useRef<HTMLElement | null>(null)

  return (
    <main className="App" ref={myRef}>
      <h1>My title</h1>
    </main>
  );
}

Como se pode ver, a forma como o useRef recebe tipos é a mesma que a utilizada no hook useState. É preciso apenas passá-los com <>. Se você tiver diversas anotações de tipo, basta usar a união, como eu faço aqui.

Definir tipos no useContext

O useContext é um hook que permite acessar e consumir um determinado contexto em uma aplicação do React.

import * as React from "react";

interface IArticle {
  id: number
  title: string
}

const ArticleContext = React.createContext<IArticle[] | []>([]);

const ArticleProvider: React.FC<React.ReactNode> = ({ children }) => {
  const [articles, setArticles] = React.useState<IArticle[] | []>([
    { id: 1, title: "post 1" },
    { id: 2, title: "post 2" }
  ]);

  return (
    <ArticleContext.Provider value={{ articles }}>
      {children}
    </ArticleContext.Provider>
  );
}

const ShowArticles: React.FC = () => {
  const { articles } = React.useContext<IArticle[]>(ArticleContext);

  return (
    <div>
      {articles.map((article: IArticle) => (
        <p key={article.id}>{article.title}</p>
      ))}
    </div>
  );
};

export const App: React.FC = () => {
  return (
    <ArticleProvider>
      <h1>My title</h1>
      <ShowArticles />
    </ArticleProvider>
  );
}

Aqui, começamos criando a interface IArticle, que é o tipo do nosso contexto.
Em seguida, a utilizamos no método createContext() para criar um contexto. Depois, inicializamos o contexto com []. Você também pode usar null como o state inicial, se quiser.

Com isso feito, podemos, agora, lidar com o state do contexto e definir seu tipo em useContext para que espere um array do tipo IArticle como valor.

Definir tipos no useReducer

O hook useReducer ajuda a gerenciar states mais complexos. É uma alternativa ao useState – considere, no entanto, que eles são diferentes.

import * as React from "react";

enum ActionType {
  INCREMENT_COUNTER = "INCREMENT_COUNTER",
  DECREMENT_COUNTER = "DECREMENT_COUNTER"
}

interface IReducer {
  type: ActionType;
  count: number;
}

interface ICounter {
  result: number;
}

const initialState: ICounter = {
  result: 0
};

const countValue: number = 1;

const reducer: React.Reducer<ICounter, IReducer> = (state, action) => {
  switch (action.type) {
    case ActionType.INCREMENT_COUNTER:
      return { result: state.result + action.count };
    case ActionType.DECREMENT_COUNTER:
      return { result: state.result - action.count };
    default:
      return state;
  }
};

export default function App() {
  const [state, dispatch] = React.useReducer<React.Reducer<ICounter, IReducer>>(
    reducer,
    initialState
  );

  return (
    <div className="App">
      <h1>Result: {state.result}</h1>
      <button
        onClick={() =>
          dispatch({ type: ActionType.INCREMENT_COUNTER, count: countValue })
        }> +
      </button>
      <button
        onClick={() =>
          dispatch({ type: ActionType.DECREMENT_COUNTER, count: countValue })
        }> -
      </button>
    </div>
  );
}

Aqui, começamos declarando os tipos de ação que permite lidar com o contador. Em seguida, definimos dois tipos para a função do redutor e para o state do contador, respectivamente.

O redutor espera um state do tipo ICounter e uma action (em português, ação) do tipo IReducer. Com isso, podemos, agora, lidar com o contador.

O hook useReducer recebe a função do redutor e um state inicial como argumentos e retorna dois elementos: o state do contador e a ação de dispatch.

Para definir o tipo para os valores retornados por useReducer, basta passar em <> o tipo dos seus dados.

Com isso, o contador, agora, pode ser aumentado e reduzido usando o useReducer.

Definir tipos no useMemo

O hook useMemo permite a memoização do resultado de determinada função. Ele retorna um valor memoizado.

const memoizedValue = React.useMemo<string>(() => {
  computeExpensiveValue(a, b)
}, [a, b])

Para definir os tipos em useMemo, basta passar em <> o tipo dos dados que você deseja memoizar. Aqui, o hook expera por uma string como valor retornado.

Definir tipos no useCallback

O hook useCallback permite que você faça a memoização de uma função para evitar novas renderizações desnecessárias. Ele retorna uma função de callback memoizada.

type CallbackType = (...args: string[]) => void

const memoizedCallback = React.useCallback<CallbackType>(() => {
    doSomething(a, b);
  }, [a, b]);

Aqui, declaramos o tipo de CallbackType usado como tipo na função de callback que queremos memoizar.

Ela espera receber parâmetros do tipo string e deve retornar um valor do tipo void.

Em seguida, definimos esse tipo em useCallback – caso você passe o tipo incorreto para a função de callback ou para o array de dependências, o TypeScript dirá que há algo errado.

Você pode encontrar mais conteúdo como este no blog do autor ou seguir o autor no Twitter para receber notificações.

Obrigado pela leitura.