---
title: Type-safe state modeling with TypeScript and React Hooks
teaser: 'Utilize the power of Typescript to model your React application''s state
  and context with confidence.

  '
tags: typescript,react,hooks,state,context
author: Wil Hall
published_on: 2019-08-23
---

The power of [React Hooks] allows us to manage reusable and composable state in
our React components, without the need for class components or global state
([such as Redux]). Combining that with the flexibility of TypeScript interfaces,
there are several different methods we can use to model our component state and
context in a type-safe way.

In this post I will illustrate some of the benefits to using [typescript strict
mode] with React Hooks and hope that you will consider using it for your
TypeScript+React projects. Seemingly trivial things such as [avoiding the Any
type] and ensuring you always define a return type will allow the type system to
catch your mistakes before they can become runtime errors. This is especially
helpful when debugging state management in React, where tracking down runtime
errors can be particularly challenging.

Our goal will be to explore some examples of state management using React Hooks
(specifically `useState` and `useContext`) while maintaining strict typing using
TypeScript. In addition to providing type safety to our hooks and state data,
types act as living documentation which provide clarity around how the
application's state is managed.

Let's start with a very simple example of using React Hooks to manage some state
for a user profile:

```typescript
const ProfilePage = (): ReactElement => {
  const [firstName] = useState("Foo");
  const [lastName] = useState("Bar");
  const [title] = useState("Software developer");

  return (
    <dl>
      <dt>First name:</dt> <dd>{firstName}</dd>
      <dt>Last name:</dt> <dd>{lastName}</dd>
      <dt>Title:</dt> <dd>{title}</dd>
    </dl>
  );
};
```

In the above example we have three calls to `useState`, which can be simplified
into a single hook which uses an object to hold the values. In doing this, we'll
also define an interface for that object so that we can enforce the type model
of our state:

```typescript
interface Profile {
  firstName: string;
  lastName: string;
  title: string;
}

const ProfilePage = (): ReactElement => {
  const [profile] = useState<Profile>({
    firstName: "Foo",
    lastName: "Bar",
    title: "Software developer",
  });

  return ( . . . );
};
```

One immediate benefit of creating an interface for `Profile` is that we now have
a way to refer to what our state _looks like_. The object we pass to
`useState<Profile>` could come from anywhere, and we would _know_ it was a
`Profile`.

We can also use this interface to make a type-safe custom hook that accepts
overrides for the default values:

```typescript
interface ProfileState {
  profile: Profile;
  setProfile: React.Dispatch<React.SetStateAction<Profile>>;
}

export const useProfile = (overrides?: Partial<Profile>): ProfileState => {
  const defaultProfile: Profile = {
    firstName: "Foo",
    lastName: "Bar",
    title: "Software developer",
  };

  const [profile, setProfile] = useState<Profile>({
    ...defaultProfile,
    ...overrides,
  });

  return { profile, setProfile };
};

const ProfilePage = (): ReactElement => {
  const { profile } = useProfile({
    title: "Designer",
  });

  return (. . .);
};
```

In this example we define a custom react hook `useProfile` which accepts a
`Partial<Profile>` as an argument. If you're not familiar with the TypeScript
`Partial` utility type, it defines a new type from an existing type which
represents all possible subsets of the existing type. So `Partial<Profile>`
allows us to pass any combination of the keys from the `Profile` interface
(`firstName`, `lastName`, `title`), or none at all.

In the definition of `useProfile` we define a set of static defaults, which we
can conveniently override by passing in an optional `overrides` argument. Because
we've codified all our state as either `Profile` or `Partial<Profile>`, we can
be confident that both the implementation of the `useProfile` hook and its
callers are passing around the right keys and values.

We also define an interface `ProfileState` so that we can explicitly declare the
return type of our `useProfile` hook. This has the benefit that if we ever
compose this hook within another hook or use it inside a context, we have a
concrete way to refer to its return value.

If we want to share this bucket of state across multiple components, perhaps the
most obvious way to go about it would be to reimplement our hook with
`useContext`:

```typescript
const defaultProfile: Profile = {
  firstName: "Foo",
  lastName: "Bar",
  title: "Software developer",
};

const defaultProfileState: ProfileState = {
  profile: defaultProfile,
  setProfile: (): void => {},
};

export const ProfileContext = createContext<ProfileState>(defaultProfileState);

export const useProfileContext = (): ProfileState => {
  return useContext(ProfileContext);
};

const ProfilePage = (): ReactElement => {
  const { profile } = useProfileContext();

  return (. . .);
};
```

In the above example, we have renamed our hook from `useProfile` to
`useProfileContext`, and implemented it in terms of react's `useContext` hook.
We also moved the default profile state into a constant, but otherwise, our
component implementation remains mostly the same. The logic which was previously
in our `useProfile` hook will move into our context provider:

```typescript
interface ProfileContextProviderProps {
  defaults?: Partial<Profile>;
  children?: ReactNode;
}

export const ProfileContextProvider = (
  props: ProfileContextProviderProps,
): ReactElement => {
  const [profile, setProfile] = useState<Profile>({
    ...defaultProfile,
    ...props.defaults,
  });

  return (
    <ProfileContext.Provider
      value={{
        profile,
        setProfile,
      }}>
      {props.children}
    </ProfileContext.Provider>
  );
};
```

And finally, now that we have defined our context provider, we can use it in our
app:

```typescript
const App = (): ReactElement => {
  const defaults: ProfileContextProviderProps = {
    title: "Designer",
  };

  return (
    <ProfileContextProvider defaults={defaults}>
      <ProfilePage />
    </ProfileContextProvider>
  );
};
```

At the expense of some type verbosity, we have created a strongly typed model of
our React component state. We can be confident that the values that make it into
our state are of the types we expect to be there, and we have a self-documenting
and easy way to modify the interface for our custom hook.

[react hooks]: https://reactjs.org/docs/hooks-intro.html
[typescript strict mode]: https://medium.com/webhint/going-strict-with-typescript-be3f3f7e3295
[avoiding the any type]: https://43081j.com/2019/02/typescript-avoid-using-any
[such as Redux]: https://thoughtbot.com/blog/using-redux-with-react-hooks
