DEV Community

Cover image for React TypeScript - How To Set Types on Hooks (+cheat sheet)
Ibrahima Ndaw
Ibrahima Ndaw

Posted on โ€ข Originally published at ibrahima-ndaw.com

96 18

React TypeScript - How To Set Types on Hooks (+cheat sheet)

TypeScript is a great language that allows type-checking your code in order to make it more robust and understandable.

In this guide, I will lead you in setting up TypeScript types on React hooks (useState, useContext, useCallback, and so on).

Let's dive in

Set types on useState

The useState hook allows you to manage state in your React app. It's the equivalent of this.state in a Class component.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

To set types on useState hook, you need to pass into <> the type of the state. You can also use union type like this <number | null> if you don't have an initial state.

Set types on useRef

The useRef hook returns a mutable ref object that allows accessing DOM elements.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the way useRef receives types is the same as the useState hook. You just have to pass it into the <> - and, if you have multiple type annotations, just use union type as I do here.

Set types on useContext

useContext is a hook that allows accessing and consuming a given Context in a React app.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, we start by creating the IArticle interface that is the type of our context.
Next, we use it on the createContext() method to create a new context, and then initialize it with [] - you can also use null as an initial state if you want too.

With that in place, we can now handle the state of the context and set the type on useContext in order to expect an array of type IArticle as value.

Set types on useReducer

The useReducer hook helps to manage more complex states. It's an alternative to useState - but keep in mind that they are different.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, we start by declaring the action types that allow handling the counter. Next, we set respectively two types for the reducer function and the counter state.

The reducer expects a state of type ICounter and an action of type IReducer. With that, the counter can now be handle consequently.

The useReducer hook receives the reducer function and an initial state as arguments and returns two elements: the state of the counter and the dispatch action.

To set the type for the values returned by ueReducer - just pass into the <> the type of your data.

With that in place, the counter can now be incremented or decremented through useReducer.

Set types on useMemo

The useMemo hook allows you to memorize the output of a given function. It returns a memoized value.

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

To set types on useMemo - just pass into the <> the type of data you want to memorize.
Here, the hook expects a string as a returned value.

Set types on useCallback

The useCallback hook allows you to memorize a function to prevent unnecessary re-renders. It returns a memoized callback.

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

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

Here, we declare the CallbackType type that is using as type on the callback we want to memorize.
It expects to receive parameters of type string and should return a value of type void.

Next, we set that type on useCallback - and if you pass a wrong type to the callback or the array of dependencies - TypeScript will yell at you.

You can find other great content like this on my blog or follow me on Twitter to get notified.

Thanks for reading

Tiugo image

Fast, Lean, and Fully Extensible

CKEditor 5 is built for developers who value flexibility and speed. Pick the features that matter, drop the ones that donโ€™t and enjoy a high-performance WYSIWYG that fits into your workflow

Start now

Oldest comments (11)

Collapse
 
dimaslz profile image
Dimas Lรณpez โ€ข

Good post, really useful!

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw โ€ข

Thanks for reading๐Ÿ‘

Collapse
 
bronxsystem profile image
bronxsystem โ€ข โ€ข Edited

I needed this thank you good sir. Typescript new to my stack no more hacky code ~0~;;

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw โ€ข

Glad it helps

Collapse
 
wobsoriano profile image
Robert โ€ข

Good job.

Collapse
 
feelzz profile image
Peter Fields โ€ข

You always provide good content. Simple and easy to understand. Great job!

Collapse
 
ibrahima92 profile image
Ibrahima Ndaw โ€ข

Wow! Thanks a lot

Collapse
 
abraganu profile image
Abraham Garcia Nunez โ€ข โ€ข Edited

Pretty good stuff :), I will add that you need always set the return type of a function if you are working on strict mode (typescript). Another thing is that you can add read only to the reducers state

Collapse
 
kiwibennor profile image
Bennor McCarthy โ€ข โ€ข Edited

Great advice. Really easy to do and a surprising number of people don't realise you can do this.

Minor nitpicky point, but you can get away with simplifying a couple of your examples, especially if you (and the other people reading your code) are comfortable with type inference:

const [counter, setCounter] = React.useState(0); // Infers number
// instead of
const [counter, setCounter] = React.useState<number>(0);

const myRef = React.useRef<HTMLElement>(null); // Infers HTMLElement | null
// instead of
const myRef = React.useRef<HTMLElement | null>(null);

 // No need for `| []`. Explicitly providing the generic argument means your `[]` argument to the function is treated as `[] as IArticle[]`.
const ArticleContext = React.createContext<IArticle[]>([]);
// instead of
const ArticleContext = React.createContext<IArticle[] | []>([]);
Enter fullscreen mode Exit fullscreen mode

Hovering the mouse over the variable name in VS Code is a great way to sanity check the types that have been inferred.

(You could argue that my first two examples are a possible source of confusion for people who are new to TypeScript, so feel free to completely ignore this advice.)

Collapse
 
beraliv profile image
beraliv โ€ข

Is there a way to apply types to useContext that prohibits using hook in case, the respective Provider won't be used with this component? It prevents forgetting the usage of providers.

Collapse
 
hurricaneman profile image
hurricaneman โ€ข

Well written and very rewarding

nextjs tutorial video

Youtube Tutorial Series ๐Ÿ“บ

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series ๐Ÿ‘€

Watch the Youtube series

๐Ÿ‘‹ Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someoneโ€™s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay