Note: The design pattern presented in this blog shows how to implement a predictable state container using the React Context API. Typescript assists in building and consuming state correctly. TLDR; Codesandbox

For my current project I had to rethink of a way to implement state management inside a single-page React application. I’m pretty familiar with Redux and have used it for multiple projects, but this time I wanted to take a different course. Redux is a popular and clever designed state container but at the same time one of the most criticised libraries in the React ecosystem. Almost all critique is related to the amount of boilerplate required to get the data flowing and that, even though the core concepts are ‘simple’, the learning curve is quite steep. Additionally, there are limitations related to using Redux in the way intended and getting around them requires bringing aboard other dependencies.

This project is my first interfacing with GraphQL. We first started out experimenting with manually firing queries to the endpoint and keeping track of the data and loading state in Redux. This quickly became a (projected) dread of repetition. Moreover, this is my first for-realz project in Typescript, and I quickly became swamped by the complexity required to get type inference right. The project requires fast iteration and has an uncertain future that most certainly demands flexibility in UX and data flow; I didn’t want to make the architecture too rigid from the get-go.

Instead, I opted to venture into the latest-and-greatest React has to offer. I blanked out the project and started writing the application using Query components from Apollo’s react-apollo library. I was immediately charmed by the render-prop pattern and the encapsulation provided by this component. There is no need to worry about firing of the data load at the right moment, keeping track of the loading state or handling any unexpected errors. The component does this for you, your job is only to render the outcome, with typescript letting you know when you forget something (I recommend using graphql-code-generator to type your GraphQL components). Here’s an example of what that looks like:

<Query<GetProjectCards.Query> query={getProjectCards}>

{({ loading, error, data }) => {

if (loading) {

return <LoadingCard />;

}

if (error || !data) {

return <ErrorCard />;

} return data.projects.map(project => (

<ProjectCard project={project} key={project.id} />

));

}}

</Query>;

The render-prop pattern is starting to become very popular lately, and, in my opinion, rightly so. Together with Formik and react-router the application turned into composition heaven. Components (and containers) are part of a tree and data flows from top to bottom. I don’t find it difficult to pull the application apart and put it back together differently. I think it’s straight forward to move components around. Typescript support is silky-smooth. Using React how it’s designed to be used. And once I got used to the nested code structure I came to appreciate the beauty of it.

Back to state management. For the MVP we managed to get around using basic React component state and some minor prop-drilling. But with more complicated UX designs around the corner there is a definite need for a more advanced solution. We looked into using Apollo’s local state but ran into issues with Typescript. Type generation for @client fields is especially fragile, with basic support only added recently. In the end, it doesn’t feel quite right to mix client state into the world of GraphQL.

What if we could use the render-prop pattern to manage state from higher-level containers and inject it into components wherever we need them? This is exactly what became possible with the updated Context API released in React 16.3. The implementation is surprisingly straight-forward.

Using a StateContainer to manage local state

Suppose we have a Todo application where we want to manage the todos in a higher-order state component. With the render-prop pattern, we would consume the state with something like this:

import { StateConsumer } from "./StateContainer"; <StateConsumer>

{({ state, actions }) => (

<TodoList

todos={state.todos}

handleAdd={actions.addTodo}

/>

)}

</StateConsumer>;

What is your first impression? Instead of wrapping your component with a HOC such as connect (and repetitively defining mappings such as react-redux’s mapStateToProps and mapDispatchToProps) you can extract and use the required state and actions directly. Moreover, it would be great if Typescript can assist in code-completion and type checking.

Start with defining an interface IState for the state you want to encapsulate. You could also use index signatures to define map-like structures.

interface ITodo {

id: number;

text: string;

} interface IState {

todos: ITodo[];

}

Define another interface called IContext which contains your State interface along with anything else you want components to consume (e.g. actions, selectors).

interface IContext {

state: IState;

actions: {

addTodo: (text: string) => void

};

}

You can now create a Context using the React.createContext API. Context is how React gives us a way to provide data from a high-level component and consume it in lower-level components.

const StateContext = React.createContext<IContext>({} as IContext);

The argument is a default context value which is consumed if there is no data provider, but since we intent to always provide a value we can give an empty object and cast it to the IContext state to escape type errors.

We can now create a StateContainer class which defines the initial state, implements any actions and provides the context value to it’s children.

const initialTodos = [

{ id: 1, text: "Do the laundry" },

{ id: 2, text: "Dance around the room" },

{ id: 3, text: "Buy a motorbike" },

{ id: 4, text: "Jump on the couch" }

]; let nextId = 5; class StateContainer extends React.PureComponent<{}, IState> {

state = {

todos: initialTodos

}; addTodo = (text: string) => {

this.setState(prevState => {

const todo: ITodo = {

id: nextId++,

text

}; return {

todos: [todo, ...prevState.todos]

};

});

}; render() {

const context = {

state: this.state,

actions: {

addTodo: this.addTodo

}

}; return (

<StateContext.Provider value={context}>

{this.props.children}

</StateContext.Provider>

);

}

} export default StateContainer;

export const StateConsumer = StateContext.Consumer;

At the end of the module we export the container class and the consumer to be implemented in the application. Finally, we can add the state provider to the main App component as follows:

import App from "./App"; render(

<StateContainer>

<App />

</StateContainer>,

document.getElementById("root")

);

That’s all there is to it! Full Typescript support. It’s a very simple design pattern but allows for great flexibility and personal taste. You can see a more contrived example here.

Additional notes