Typescript HOCs with Apollo in React - Explained.
Tags: react, typescript, graphql, apollo
Published: 2019-07-14 16:22:57

Typescript HOCs with Apollo is pretty tricky. I don't know maybe it is just me but this…

![example of graphql function from react-apollo](https://thepracticaldev.s3.amazonaws.com/i/d1ds615vyh10injr4xgc.png)

This is intimidating, but it is undoubtedly necessary. How else do you want compiler to check your props in and out of the wrapped component? This is how VSCode helper describes graphql function from react-apollo. Typescript wants no harm and to protect you from yourself.

I will elaborate and extend the examples from apollo-graphql docs because they lack some use-cases like chaining HOCs or creating a query HOC with *config.name*, *config.props*.

###Let's first dive into graphql the HOC creator

{% gist https://gist.github.com/georgeshevtsov/33aacb7e3f74090a923db4ef231a49b0 %}

1. _TProps_ - Interface/Type describes so-called _InputProps_, notice it extends from _TGraphQLVariables_.
2. _TData_ - Type for the response from the query.
3. _TGraphQLVariables_ - Type for variables needed for query/mutation.
4. _TChildProps_ - This one is generated for you based on _TData_ and _TGraphQLVariables_ unless you want it to be customized.

**DataProps** are props to be generated for you from _TData_ and _TGraphQLVariables_, unless you provide a custom type. This is used for the query type of action. It packs all useful for query control properties into data object.

{% gist https://gist.github.com/georgeshevtsov/0a6fc5903abb3749b370eab43b5501de %}

**DataValue** is the one which caused me questions, mainly because it wraps _TData_ with _Partial_. This design will make you check that data is not undefined in each and every consumer of HOC. To avoid it you can provide you own _TChildProps_.

{% gist https://gist.github.com/georgeshevtsov/c78753626e18a1bdb1f6b6abe2fb36bd %}

**QueryControls** are props that usually go packed into data, among them there is (once the request is resolved) the prop which we typed as a response type, this is done via intersection of _Partial<TData>_ and _QueryControls<TData, TGraphQLVariables>_.

{% gist https://gist.github.com/georgeshevtsov/ed3e6c91be43fd18c5b4fe34d0de5998 %}

I will not go into further detail dissecting _QueryControls_, because I think this information is enough to get by for the purpose of this article, in case you are prone to more exploration, feel free to clone [react-apollo](https://github.com/apollographql/react-apollo) and dig deeper.

## Let's get into the simplest query example with TS.
Following the [official doc of apollographql](https://www.apollographql.com/docs/react/recipes/static-typing/) pattern I want to fill in all the gaps, which were not obvious to me for the first couple of times when I worked with these structures, and hopefully this article will help you not go crazy, and after reading it you will get one step closer to the typescript mastery.

### Query HOC without Variables
Let's first review the shortest variant possible.
{% gist https://gist.github.com/georgeshevtsov/882c51b117a000c6f52686323805be7f %}

**withBurgersNoChildProps** gets
* _TProps_ - {}
* _TData_ - _Response_
* _TGraphQLVariables_ - {} by default
* _TChildProps_ - if omitted will be generated for you as _Partial_ from the previous three types. This means data is an optional value, and you have to use non-null assertion operator - **"!"** everywhere. There is a way to avoid these checks.
{% gist https://gist.github.com/georgeshevtsov/4784aa908acbca183ac188e4993ea6a5 %}
**withBurgersWithChildProps** gets

* _TProps_- {}
* _TData_- Response
* _TGraphQLVariables_- {} by default
* _TChildProp_ gets _ChildProps_, see how it uses _ChildDataProps_ from react-apollo.

Let's see what is under the hood of _ChildDataProps_ type.

{% gist https://gist.github.com/georgeshevtsov/806116259c9b426c176b254ffee4e954 %}

_ChildDataProps_ makes an intersection between _TProps_ and _DataProps_ using _TData_ and _TGraphQLVariables_. Notice that there is no _Partial_ around _DataProps_ this time, how it was in the graphql definition example in the default value for _TChildProps_. This means that data is going to be definitely present in the wrapped component which is handy.

### Query HOC with Variables
Here's the example of how to pass props to your wrapped component and be able to validate them.

{% gist https://gist.github.com/georgeshevtsov/8ae3ffdbeeedc8e48caa62cb8383b0a5 %}

To get the right burger, api needs a name, otherwise the request is going to fail without it. I described _InputProps_ as an interface and extended it from Variables to avoid code duplication, it is mandatory to have Variables joined with _InputProps_, otherwise TS compiler will not know what props - _Variables_ you need for your request in a graphql hoc.

### Query HOC with config.options
*Options* is a way to map your incoming props in the HOC. For example you can map props which are named in their own way, to be treated as _variable props_ useful for the query request.

{% gist https://gist.github.com/georgeshevtsov/f21a4e4b0000b9654675c154dd5de6fa %}

Now there is no need to extend _InputProps_ from _Variables_, because the query request is going to be satisfied with a substitution. TS also checks types inside options object declaration, so it would not let you use a property of a different type from a given _Variable_.

### Query HOC with Options.name
The purpose of this is when you have multiple Query HOCs wrapped around one single component, _data_ prop returned from all of them will eventually conflict, so you give every Query a specific _name_.

For this example, all the knowledge from above has to be put to a test. Because now we will write the result of the query into a custom specified property name. **graphql** function is not going to type this one for us, so we need to type the result of the query request by ourselves.

{% gist https://gist.github.com/georgeshevtsov/e6e14b2fdf7029eb86cccc5bf4b28c79 %}

**withBurgerWithName** - gets the name of _burgerRequest_. _burgerRequest_ will now store everything which was previously stored in data, to be able to type it we need to remember how apollo types the data prop for us. We need to mimic _ChildDataPros_ type, here is a shortened version of it.
```
type ChildDataPros = TProps & { data: DataValue<TData, TGraphQLVariables> }
```
Notice how manually created _ChildProps_ reflects the structure of _ChildDataProps_ with data renamed to _burgerRequest_.

### Chained Query HOCs

Now goes the fun part - typing the chain of HOCs. Many of you might know about compose function from apollo. In typescript it throws all your typings out the window. Here's the definition of compose function.

```
function compose(...funcs: Function[]): (...args: any[]) => any;
```

According to this, compose is a _curried function_. First invocation of it accepts functions, second invocation accepts anything in any quantities, the return value is any.

![the error when using compose function with graphql hoc in typescript](https://thepracticaldev.s3.amazonaws.com/i/k29ub8lt9qfzziy0v8az.png)

The aftermath
1. _Props_ passed from outside are not validated (see _BurgerFactory_)
2. _Props_ passed from HOC to wrapped component are not typed (has any type)

Let's fix second point first.
![fix for incorrect prop typing in wrapped component](https://thepracticaldev.s3.amazonaws.com/i/qa6av99zg0oba9m3ncm8.png)

All we need to do, is to type the props in a wrapped component explicitly, which you would naturally do should you use a standalone component instead of an arrow function.


To fix the first point we just have to give up using compose with Typescript. Giving up compose leads to the most straightforward option. Let's review the example we have two HOCs prepared. One is fetching the beverage, goes by a trivial name _withBeverage_ and the other one is our good old friend _withBurger_. I will show you how to jam them together.

**withBurger** - this time no salad, things got really serious.

{% gist https://gist.github.com/georgeshevtsov/5c04b2b12734aec69cdcdcdf8370ef8d %}

**withBeverage** is familiar in outline but it satisfies the example.

{% gist https://gist.github.com/georgeshevtsov/7d561939b75bf0dcd183b3f84fcfa1ce %}

The combination without compose will look somewhat like this

```
withBeverage(withBurger(MealComponent))
```

With the configuration of above described HOCs, the compiler will give us this error

![example of a typical error when combining two hocs in TS ](https://thepracticaldev.s3.amazonaws.com/i/43xo9tsef7643e7nj4ze.png)

We are interested in the first paragraph.

```
'ComponentClass<BurgerInputProps, any>' is not assignable to parameter of type 'ComponentType<BeverageInputProps & BeverageVariables & { beverageRequest: DataValue<BeverageResponse, BeverageVariables>; }>'
```

The line starting from _ComponentType<BeverageInputProps & …_ is describing the component and its _PropTypes_ returned after invocation of _withBeverage_. It conflicts with the _PropTypes_ of the component returned from the invocation of _withBurger_. To fix this compiler error, we need to make intersections of the returned type from _withBeverage_ with the incoming props type from _withBurger_.

{% gist https://gist.github.com/georgeshevtsov/8b346da57c63e9d34e4cd27f6a30414a %}

First on line 2, I created an intersection of _BeverageInputProps_ and _BurgerInputProps_ this is needed to validate incoming props to get both requests running correctly.

On line 9, I created and intersection of _BurgerInputProps_ & _BeverageChildProps_, by now you should understand that this one is put in the placeholder of _TProps_.
Remember earlier, the conflict of returned props from _withBeverage_ and received props from _withBurger_, this way _withBurger_ will know that it expects not only specific for a burger query variable but also some data from _withBeverage_.

Well that's all folks. I am thinking about doing the same explanation article about HOCs for Mutation, but I am not sure if I make it before react-apollo will release their version with hooks for everything because then everybody including me will completely forget about HOCs.


Please feel free to ask questions, give pieces of advice, suggest better approaches in the comments.