Notes on TypeScript: React Hooks
Tags: react, typescript, types
Published: 2019-03-16 17:04:42

---
title: Notes on TypeScript: React Hooks
published: true
description: TypeScript and React Hooks
tags: react, typescript, types
series: Notes on TypeScript
---

### Introduction

These notes should help in better understanding `TypeScript` and might be helpful when needing to lookup up how to leverage TypeScript in a specific situation. All examples are based on TypeScript 3.2.

### React Hooks

In this part of the "Notes on TypeScript" series we will take a look at how React Hooks can be typed with TypeScript and along the way learn more about Hooks in general.

We will be consulting the [official React documentation on hooks](https://reactjs.org/docs/hooks-reference.html) and is a very valuable source when needing to learn more about Hooks or needing specific answers to specific problems.
In general hooks have been added to React in 16.8 and enable developers to use state inside function components, which was only possible in class components up to that point. The documentation states that there are basic and additional hooks.
Basic hooks are `useState`, `useEffect`, `useContext` and additional hooks include `useReducer`, `useCallback`, `useMemo`, `useRef`.

#### useState

Let's begin with `useState`, a basic hook, that as the name implies should be used for state handling.

```js
const [state, setState] = useState(initialState);
```

Looking at the above example we can see that `useState` returns a state value as well as a function to update it. But how we do type `state` and `setState`?
Interestingly TypeScript can infer the types, that means by defining an `initialState`, the types are inferred for both the state value as well as the update function.

```js
const [state, setState] = useState(0);
// const state: number
const [state, setState] = useState("one");
// const state: string
const [state, setState] = useState({
  id: 1,
  name: "Test User"
});
/*
  const state: {
    id: number;
    name: string;
  }
*/
const [state, setState] = useState([1, 2, 3, 4]);
// const state: number[]
```

The above examples demonstrate quite well, that we don't need to do any manual typing. But what if we don't have an initial state? The above examples would break when trying to update the state.
We can define the types manually when needed, using `useState`.

```js
const [state, setState] = useState<number | null>(null);
// const state: number | null
const [state, setState] = useState<{id: number, name: string} | null>(null);
// const state: {id: number; name: string;} | null
const [state, setState] = useState<number | undefined>(undefined);
// const state: number | null
```

It might be also interesting to note, that opposed to `setState` in class components, using the update hook function requires to return the complete state.

```js
const [state, setState] = useState({
  id: 1,
  name: "Test User"
});
/*
  const state: {
    id: number;
    name: string;
  }
*/

setState({name: "New Test User Name"}); // Error! Property 'id' is missing
setState(state => {
  return {...state, name: "New Test User Name"}
}); // Works!
```

Another interesting thing to note, is that we can lazily initiate the state via passing a function to `useState`.

```js
const [state, setState] = useState(() => {
  props.init + 1;
});

// const state: number
```

Again, TypeScript can infer the state type.

This means we don't need to do very much work when working with `useState`, only in cases where we don't have an initial state, as the actual state shape might be computed when initially rendering.

#### useEffect

Another basic hook is `useEffect`, which is useful when working with side effects, like logging, mutations or subscribing to event listeners. In general `useEffect` expects a functions that runs an effect which can optionally return a clean up function, which is useful for unsubscribing and removing listeners f.e. Additionally `useEffect` can be provided with a second argument, containing an array of values, ensuring the effect function only runs when one of these values has changed. This ensures that we can control when a effect is run.

```js
useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source]
);
```

Taking the original example from the documentation, we can notice that we don't need any extra typings when using `useEffect`.
TypeScript will complain when we try to return something that is not a function or an undefined inside the effect function.

```js
useEffect(
  () => {
    subscribe();
    return null; // Error! Type 'null' is not assignable to void | (() => void)
  }
);
```

This also works with `useLayoutEffect`, which only differs regarding when the effects are run.

#### useContext

`useContext` expects a context object and returns the value for the provided context. A re-render is triggered when the provider updates the context. Taking a look at the following example should clarify:

```js
const ColorContext = React.createContext({ color: "green" });

const Welcome = () => {
  const { color } = useContext(ColorContext);
  return <div style={{ color }}>Welcome</div>;
};
```

Again, we don't need to do very much regarding the types. The types are inferred.

```js
const ColorContext = React.createContext({ color: "green" });
const { color } = useContext(ColorContext);
// const color: string
const UserContext = React.createContext({ id: 1, name: "Test User" });
const { id, name } = useContext(UserContext);
// const id: number
// const name: string
```

#### useReducer
Sometimes we are dealing with more complex states, that might depend on the previous state as well. `useReducer` accepts a function that calculates a specific state depending on the previous state and an action. The following example is taken from the [official documentation](https://reactjs.org/docs/hooks-reference.html#usereducer).

```js
const [state, dispatch] = useReducer(reducer, initialArg, init);
```

If we look at the example in the documentation, we notice that we will need to do some additional typing work. Check the slightly adapted example:

```js
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter({initialState = 0}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
```

Currently `state` can't be inferred correctly. But we can change this, by adding types for the reducer function. By defining `state` and `action` inside the reducer function, we can now infer the `state` provided by `useReducer`. Let's adapt the example.

```js
type ActionType = {
  type: 'increment' | 'decrement';
};
type State = { count: number };
function reducer(state: State, action: ActionType) {
  // ...
}
```

Now we can ensure that the types are inferred inside `Counter`:

```js
function Counter({initialState = 0}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  // const state = State
  // ...
}
```

When trying to dispatch a non existent type, we will be greeted with an error.

```js
dispatch({type: 'increment'}); // Works!
dispatch({type: 'reset'});
// Error! type '"reset"' is not assignable to type '"increment" | "decrement"'
```

`useReducer` can also be lazily initialized when needed, as sometimes the initial state might has to be calculated first:

```js
function init(initialCount) {
  return {count: initialCount};
}

function Counter({ initialCount = 0 }) {
  const [state, dispatch] = useReducer(red, initialCount, init);
  // const state: State
  // ...
}
```

As can be seen in the above example, types are inferred with a lazily initialized  `useReducer` due to the correctly typed `reducer` function.

There is not much more we need to know regarding `useReducer`.


#### useCallback

Sometimes we need to memoize callbacks. `useCallback` accepts an inline callback and an array of inputs for updating the memoization only when one of these values has changed. Let's take a look at an example:

```js
const add = (a: number, b: number) => a + b;
const memoizedCallback = useCallback(
  (a) => {
    add(a, b);
  },
  [b]
);
```

Interestingly, we can call memoizedCallback with any type and will not see TypeScript complain:

```js
memoizedCallback("ok!"); // Works!
memoizedCallback(1); // Works!
```

In this specific case, `memoizedCallback` works with strings or numbers although the `add` function expects two numbers. To fix this, we need to be more specific when writing the inline function.

```js
const memoizedCallback = useCallback(
  (a: number) => {
    add(a, b);
  },
  [b]
);
```

Now, we need to pass a number or else the compiler will complain.

```js
memoizedCallback("ok");
// Error! Argument of type '"ok"' is not assignable to argument of type 'number'
memoizedCallback(1); // Works!
```

#### useMemo

`useMemo` is very similar to `useCallback`, but returns a memoized value instead of a memoized callback. The following is from the documentation.

```js
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```

So, if we build an example based on the above, we notice that we don't to need to do anything regarding types:

```js

function calculate(a: number): number {
  // do some calculations here...
}

function runCalculate() {
  const calculatedValue =  useMemo(() => calculate(a), [a]);
  // const calculatedValue : number
}
```

### useRef

Finally, we will look at one more hook: `useRef`.
When using `useRef` we gain access to a mutable reference object. Further, we can pass an initial value to `useRef`, which is used to initialized a `current` property exposed by the mutable ref object. This is useful when trying to access some components inside a function f.e. Again, let's use the example from the [documentation](https://reactjs.org/docs/hooks-reference.html#useref).

```js
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus(); // Error! Object is possibly 'null'
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
```

We can see that TypeScript is complaining, because we initialized `useRef` with `null`, which is a valid case, as sometimes set the reference might happen at a later point in time.
This means, we need to be more explicit when using `useRef`.

```js
function TextInputWithFocusButton() {
  const inputEl = useRef<HTMLInputElement>(null);
  const onButtonClick = () => {
    inputEl.current.focus(); // Error! Object is possibly 'null'
  };
  // ...
}
```

Being more specific when using `useRef` via defining the actual type `useRef<HTMLInputElement>` still doesn't remove the error. Actually checking if the `current` property exists, will prevent the compiler from complaining.

```js
function TextInputWithFocusButton() {
  const inputEl = useRef<HTMLInputElement>(null);
  const onButtonClick = () => {
    if (inputEl.current) {
      inputEl.current.focus(); // Works!
    }
  };
  // ...
}
```

`useRef` can also be used as an [instance variable](https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables).
If we need to be able to update the `current` property, we need to use `useRef` with the generic type `Type | null`:

```js
function sleep() {
  const timeoutRefId = useRef<number | null>();

  useEffect(() => {
    const id = setTimeout(() => {
      // ...
    });
    if (timeoutRefId.current) {
      timeoutRefId.current = id;
    }
    return () => {
      if (timeoutRefId.current) {
        clearTimeout(timeoutRefId.current);
      }
    };
  });
  // ...
}
```

There are a couple of more interesting things to learn about React hooks, but are not TypeScript specific. If there is more interest in the topic, consult the [official React documentation on hooks](https://reactjs.org/docs/hooks-reference.html).
We should have a good understanding of how to type React Hooks at this point.

If you have any questions or feedback please leave a comment here or connect via Twitter: [A. Sharif](https://twitter.com/sharifsbeat)