Way of the Script — React Typescript & Context API Patterns

Hail warriors!

It has been a while since I have last written anything, but I am back from my quest, and eager to share my stories with fellow warriors and hopefully you may find something to aid you in your journey!

Ref: https://i.ytimg.com/vi/NRHYxYpHWo4/maxresdefault.jpg

Today we shall be looking at how we can strongly type the context API, and a pattern that I have found useful in development.

This repo includes a working example related to this article:

https://github.com/xXValhallaCoderXx/medium-article-examples/tree/react-ts-context-api

Basic Example

This is the simplest example, utilizing the render props method. We will create a simple context store to manage the state of a counter.

Create a file called app-ctx.tsx this will contain the the Provider and Consumer to be used throught the application, an example of this file can be found here:

https://github.com/xXValhallaCoderXx/medium-article-examples/blob/react-ts-context-api/src/01-initial-example/app-ctx.tsx

File Breakdown

Lets go through the relevant parts of this file, to explain each part step by step.

import React, {Component, createContext} from "react";
const ctxt = createContext<any>(null);

I know we said we would be using strongly typed context API, and we are using any here, though i found by initilizing it this way, keeps the code much simpler.

We define the initial context state as null, because at times we don’t always want to initialize with default values at the time of creation.

Start Adding Types

Next we will create our core interface, which will be imported along with our Context consumer in parts of the application where we want to use it.

export interface ICtx {
state: IState;
actions: IActions;
}

Our core interface will be made of 2 objects, which we will create types for:

- State: Object which contains our contexts state values.
- Actions: Functions we invoke to manipulate our context api values.

interface IState {
count: number
}
interface IAction {
increase: () => void;
decrease: () => void;
}

Creating the Context Provider

Now we will create our React Class which will contain the Context Provider, and the logic for updating the context state.

class AppProvider extends Component<{}, IState> {
state: IState = {
count: 0
};
increase = () => this.setState({count: this.state.count + 1});
decrease = () => this.setState({count: this.state.count - 1});
render() {
return (
<ctxt.Provider
value={{
state: this.state,
actions: {
increase: this.increase,
decrease: this.decrease
}
}}>
{this.props.children}
</ctxt.Provider>
);
}
}
export {AppProvider};
export const AppConsumer = ctxt.Consumer;

Note in the value object, we define 2 objects the context state, along with the actions which the context provides to allow us to update its state. We then simply export the AppProvider class, along with the Consumer.

The file should look something like this.

Using the Context

We then would need to wrap the application somewhere higher up the component tree with the AppProvider so we can use our context consumer within the application.

class InitialExample extends Component {
render() {
return (
<AppProvider>
<CounterComponent />
</AppProvider>
);
}
}

Then within our CounterComponent we simply need to import the Consumer, and its type so we can make use of it within any component.

import { AppConsumer, ICtx } from "./app-ctx";const CounterComponent = () => {
return (
<AppConsumer>
{(ctx: ICtx) => {
return (
<div>
{ctx.state.count}
<button onClick={ctx.actions.increase}>Increase</button
<button onClick={ctx.actions.decrease}>Decrease</button
</div>
);
}}
</AppConsumer>
);
};

Note: You can see how we are using the context actions to increase and decrease the count state in our context.

Strongly Typed Context State / Actions

In the images below you can see the IDE is now providing me we the state which is available, as well as the actions within the context.

Context state is now typed :>
Actions to update state are also typed :>

I have found this pattern of using Context API extremly useful in cases where I needed some form of state management, though did not want to include any 3rd party libraries.

In the Context API file, you can add arguments which could be required to pass into your actions in the types, bringing strongly typed support to your Context API implementation in your application.

Here is a full example of the above implementation.

Lets Get Hooked

While the above implementation is great,
The render props method can get messy, and HOC’s can also be a pain when dealing with Typescript.

With the latest introduction of Hooks, the useContext API, makes this implementation much easier to use, especially when importing multiple context into the same components.

Example

In our app-ctx.tsx file the only change you need to make is the following:

Before

const ctxt = createContext<any>(null);

After

export const ctxt = createContext<any>(null);

We need to ensure that we are exporting the ctxt initialization of Context. Our application can now look like this, using the useContext API.

import React, { useContext } from "react";                       import { ICtx, ctxt } from "./app-ctx";                                               const CounterComponent = () => {                         
const ctx: ICtx = useContext(ctxt);
return (
<div>
{ctx.state.count}
<button onClick={ctx.actions.increase}>Increase</button>
<button onClick={ctx.actions.decrease}>Decrease</button>
</div>
);
};
export default CounterComponent;

With minimal changes we were able to make use of the new useContext API, making our implementation even cleaner!

An example of this can be found here.

Tis the end of our quest!

I hope this short guide will be useful to some, on how we can use Context API to provide a clean and simple way of managing parts of our application state.

Ref: http://www.hdfondos.eu/pictures/2013/1108/1/hammer-thor-picture-wallpaper-7241.jpg

I would love to hear your thoughts, or even ways on how this method could be improved!

I will get back to finishing my React Redux + Typescript series shortly, as well as post a form library which I have been working on,

Till the next time Bretheren and Sisteren!