Typing components in Next.JS applications
Tags: typescript, nextjs, react
Published: 2019-05-29 14:39:02


> **Note**: This post covers typing Next.JS applications that use versions prior to v9. Starting with version 9, Next.JS comes with its own types by default, and their names may differ from those used in DefinitelyTyped package. If you use Next.JS version 9 and older, please refer to the [official documentation](https://nextjs.org/docs#typescript). For earlier versions, continue reading :)

In this article, we'll talk about typing Next.JS components. We'll be using [this Next.JS](https://github.com/koss-lebedev/nextjs-typescript) application that connects to Reddit API and displays a list of top posts in a given subreddit. Right now, the main component `Posts.tsx` doesn't have any type-safety, so we're going to fix that.

The project already contains TypeScript configured, so if you don't know how to do that, please read the official guide [here](https://github.com/zeit/next-plugins/tree/master/packages/next-typescript), it's as easy as installing a few dependencies and dropping in some configuration files.

Now let's clone [the project](https://github.com/koss-lebedev/nextjs-typescript) from GitHub and get started.

## FunctionComponent

First, let's take a look at how we type React components in general.

If we write our components as functions, we can use `FunctionComponent` type from React library. This type takes a generic type argument that describes the shape of the props. Our `Posts` component takes a subreddit name and a list of posts, so `props` object is going to look like this:

{% gist https://gist.github.com/koss-lebedev/941f098c612b62c49f65ee53e6f8a09b %}

Now when we destructure props into `posts` and `subreddit`, we get full type safety. Pretty neat, right?

Now let’s look at Next.JS components.

## NextFunctionComponent

One thing that makes Next.JS components different is a static `getInitialProps` function. If we try to assign it to our regular React component, we’ll get a type error:

{% gist https://gist.github.com/koss-lebedev/58a8e2271f826a27ca69112c52f97f1d %}

To fix this problem, we need to use a special component type from Next.JS package called `NextFunctionComponent`. This type extends standard React’s `FunctionComponent` type with Next.JS-specific static lifecycle methods (well, only one method, really). So now our code will look like this:

{% gist https://gist.github.com/koss-lebedev/effad720c75f2879660334ddec1e9f46 %}

To make our types more robust, we can _infer_ the shape of `props` returned from `getInitialProps` function instead of defining them manually. To do that, first, we want to extract `getInitialProps` function into a separate variable. This step is required to avoid circular type reference when we start inferring the shape of our props:

{% gist https://gist.github.com/koss-lebedev/8f0149519fc0c535d08b2a5739121e87 %}

Next, we can use `ReturnType` helper type to get the type of the value returned from `getInitialProps` function:


{% gist https://gist.github.com/koss-lebedev/34ec6ccbe688071659f0d41bd428b8d6 %}

Since `getInitialProps` function is asynchronous, the return type is going to be a promise, so we also need to extract its value. We can define a global helper type that will use conditional type magic to unwrap our promise:

{% gist https://gist.github.com/koss-lebedev/33e85c2fdf1e517becabfd9fd6a3fa53 %}

Now we can put everything together:

{% gist https://gist.github.com/koss-lebedev/7d7617e7c61251f036159c3027c703cc %}

## NextContext

Let’s make this example more interesting by taking the name of a subreddit from a query string.

To achieve that, we can use a `context` argument that gets passed to `getInitialProps` function that we haven’t used so far. We will use `NextContext<T>` type to type this argument. The type `T` allows us to specify the parameters we know we will have in a query string (`subreddit` in our case):

{% gist https://gist.github.com/koss-lebedev/96ec62dda5a98f36edc7dc30769a025a %}

We got type-safety inside `getInitialProps` function, but now we run into another type error talking about incompatible types of `Context`. 

The reason is that by default `getInitialProps` expects a context to be of a generic type `NextContext`, but we specified a stricter, more specific type — `NextContext<{ subreddit: string }`. To resolve this issue, we need to pass a few more type arguments to `NextFunctionComponent` type. Its full signature looks like this:

{% gist https://gist.github.com/koss-lebedev/7e07924c2e1cd85c455fc369eef1ee22 %}

As you can see, `NextFunctionComponent` can take up to 3 types — `Props`, `InitialProps`, and `Context`. `InitialProps` should only contain props returned from `getInitialProps` function. `Props` should contain all the props that component has access to, which include own component props, props passed through higher order components (such as `connect` from Redux), plus the initial props. And finally, `Context` specifies the shape of the context used by our component.

When we put everything together, we’ll get a fully typed Next.JS component

{% gist https://gist.github.com/koss-lebedev/68f0c4c64e1383de7c9ead08fedc6b50 %}

You can find the source code for the fully typed component in ["final" branch](https://github.com/koss-lebedev/nextjs-typescript/tree/final).

## Conclusion

In this article, we've explored how to type Next.JS components. The approach is different from typing regular React functional components because of the special `getInitialProps` function that Next.JS uses to prepare props data server-side. For that reason, we need to use special `NextFunctionComponent` and `NextContext` types that come with Next.JS typing package.

**PS**: If you’re curious why we used type aliases everywhere instead of interfaces, make sure to check [this article](https://konstantinlebedev.com/type-aliases-vs-interfaces/).

* * *

**Shameless plug**: if you want to learn how to use Redux without writing a ton of boilerplate, check my ["State management with Rematch" course](https://www.youtube.com/playlist?list=PLNG2YBDrzK-w1VSeDpMxdGwkb4L6hDy8Z) on Youtube.

{% youtube f06elAAbb60 %}