Notes on TypeScript: Pick, Exclude and Higher Order Components
Tags: react, typescript, types
Published: 2019-01-20 17:32:49

---
title: Notes on TypeScript: Pick, Exclude and Higher Order Components
published: true
description: Notes on TypeScript and React
tags: react, typescript, types
series: Notes on TypeScript
---

### Introduction

These notes should help in better understanding `TypeScript` and might be helpful when needing to lookup up how to leverage TypeScript in a specific situation. All examples are based on TypeScript 3.2.

### Pick and Exclude

These notes focus on typing higher order components in React, but it's interesting to understand `omit` and `Exclude` as we will need both functions to handle the different higher order component (hoc) implementations. With `Pick` we can, as the name implies, pick specific keys from a provided type definition. For example we might be using an object spread and want to select specific properties and spreading the rest. Let's take a look at the following example to get a better idea:

```js
const { name, ...rest } = props;
```

We might want to do something with name inside a function but only pass on the rest props.

```js
type ExtractName = {
  name: string
}

function removeName(props) {
  const {name, ...rest} = props;
  // do something with name...
  return rest:
}
```

Let's add types to `removeName` function.

```js
function removeName<Props extends ExtractName>(
  props: Props
): Pick<Props, Exclude<keyof Props, keyof ExtractName>> {
  const { name, ...rest } = props;
  // do something with name...
  return rest;
}
```

There is a lot going on here, first we extended our generic `Props` to include the name property.
Then we extracted the `name` property and returned the rest properties. To tell TypeScript how our generic rest types are structured we need to remove all ExtractName properties (name in this specific case). This is what `Pick<Props, Exclude<keyof Props, keyof ExtractName>>` does. Let's break this further down, to be getter a better understanding. `Exclude` removes specific keys:

```js
type User = {
  id: number;
  name: string;
  location: string;
  registeredAt: Date;
};

Exclude<User, "id" | "registeredAt"> // removes id and registeredAt
```

We can achieve the same thing with `Pick`:

```js
Pick<User, "name" | "location">
```

We can rewrite our above definition:

```js
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
type Diff<T, K> = Omit<T, keyof K>;
```

Now that we have a `Diff` function we can rewrite our `removeName` function:

```js
function removeName<Props extends ExtractName>(
  props: Props
): Diff<Props, ExtractName> {
  const { name, ...rest } = props;
  // do something with name...
  return rest;
}
```

We should have a basic understanding of how `Pick` and `Exclude` function and also added `Omit` and `Diff` that we will use when will type hocs in the following section.

### Higher Order Components

We will consult the [official React docs](https://reactjs.org/docs/higher-order-components.html) for better understanding some conventions and then type the different hoc variants.
There is an important convention that we need to consider: _Pass Unrelated Props Through to the Wrapped Component_ ([see docs](https://reactjs.org/docs/higher-order-components.html#convention-pass-unrelated-props-through-to-the-wrapped-component)).

Our first example is based on an example from the docs, where we want to log props by providing a component that logs a wrapped component.

```js
function withLogProps(WrappedComponent) {
  return class LogProps extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log("Currently available props: ", this.props);
    }
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}
```

We can leverage `React.ComponentType` which is a React specific type that will allow us to pass in a component class or function as a wrapped component. As we're not extending or narrowing any props in our `withLogProps` higher order component, we can pass the generic props through.

```js
function withLogProps<Props>(WrappedComponent: React.ComponentType<Props>) {
  return class LogProps extends React.Component<Props> {
    componentWillReceiveProps() {
      console.log("Currently available props: ", this.props);
    }
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}
```

Next, let's see how we can type a higher order component that expects additional props to show a message when an error has occurred.

```js
function withErrorMessage(WrappedComponent) {
  return function() {
    const { error, ...rest } = props;
    return (
      <React.Fragment>
        <WrappedComponent {...rest} />
        {error && <div>{error}</div>}
      </React.Fragment>
    );
  };
}
```

The `withErrorMessage` looks similar to the initial example we built.

```js

function withErrorMessage<Props>(WrappedComponent: React.ComponentType<Props>) {
  return function(props: Props & ErrorLogProps) {
    const { error, ...rest } = props as ErrorLogProps;
    return (
      <React.Fragment>
        <WrappedComponent {...rest as Props} />
        {error && <div>{error}</div>}
      </React.Fragment>
    );
  };
}
```

There are some interesting aspects here, that we need to clarify.
Our hoc expands the expected props by expecting an `error` aside from all the props expected from the wrapped component, this can be achieved by combining the generic wrapped component props with the required error message prop: `Props & ErrorLogProps`.
The other interesting aspect is that we need to explicitly define which props are `ErrorLogProps` by typecasting our destructured props:`const { error, ...rest } = props as ErrorLogProps`
TypeScript will still complain when passing through the rest props, so we need to typecast the rest props as well: `<WrappedComponent {...rest as Props} />`. This might change in the future, but of _3.2_, this is needed to prevent TypeScript from complaining.

There are situations where we want to provide specific functionalities and values to a wrapped component as well as preventing these functions and values being overridden by provided props.
Our next higher order component should narrow down the API.

Let's assume we have an `Input` component the expects

```js
const Input = ({ value, onChange, className }) => (
  <input className={className} value={value} onChange={onChange} />
);
```

The higher order component should provide the `value` and `onChange` properties.

```js
function withOnChange(WrappedComponent) {
  return class OnChange extends React.Component {
    state = {
      value: "";
    };
    onChange = e => {
      const target = e.target;
      const value = target.checked ? target.checked : target.value;
      this.setState({ value });
    };
    render() {
      return (
        <WrappedComponent
          {...this.props}
          onChange={this.onChange}
          value={this.state.value}
        />
      );
    }
  };
}
```

Let's define the needed prop types first.

```js
type InputProps = {
  name: string,
  type: string
};

type WithOnChangeProps = {
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
  value: string | boolean
};
```

This means we can define our `Input` component by combining these prop type definitions.

```js
const Input = ({
  value,
  onChange,
  type,
  name
}: InputProps & WithOnChangeProps) => (
  <input type={type} name={name} value={value} onChange={onChange} />
);
```

Adding types to the `withOnChange` component, we can apply everything we have learned so far.

```js
type WithOnChangeState = {
  value: string | boolean;
};

function withOnChange<Props>(WrappedComponent: React.ComponentType<Props>) {
  return class OnChange extends React.Component<
    Diff<Props, WithOnChangeProps>,
    WithOnChangeState
  > {
    state = {
      value: ""
    };
    onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
      const target = event.target;
      const value = target.type === "checkbox" ? target.checked : target.value;
      this.setState({ value });
    };
    render() {
      return (
        <WrappedComponent
          {...this.props as Props} // we need to be explicit here
          onChange={this.onChange}
          value={this.state.value}
        />
      );
    }
  };
}
```

By using our previously defined `Diff` type we can extract all keys that we want to prevent from being overridden. This enables us to provide our `Input` component with the `onChange` and `value` properties.

```js
const EnhancedInput = withOnChange(Input);

// JSX
<EnhancedInput type="text" name="name" />;
```

There are situations where we need to expand the props, f.e. we would like to enable developers using `withOnChange` to provide an initial value. We can rewrite our component by enabling to provide an `initialValue` property.

```js
type ExpandedOnChangeProps = {
  initialValue: string | boolean;
};

function withOnChange<Props>(WrappedComponent: React.ComponentType<Props>) {
  return class OnChange extends React.Component<
    Diff<Props, WithOnChangeProps> & ExpandedOnChangeProps,
    WithOnChangeState
  > {
    state = {
      value: this.props.initialValue
    };
    onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
      const target = event.target;
      const value = target.type === "checkbox" ? target.checked : target.value;
      this.setState({ value });
    };
    render() {
      const { initialValue, ...props } = this.props as ExpandedOnChangeProps;
      return (
        <WrappedComponent
          {...props as Props} // we need to be explicit here
          onChange={this.onChange}
          value={this.state.value}
        />
      );
    }
  };
}
```

There are two interesting things to note here. We expanded the `OnChange` class props by defining `Diff<Props, WithOnChangeProps> & ExpandedOnChangeProps`, the other important is that we have to remove `initialValue` from the props passed down to our wrapped component. We have seen this done in our initial example, by spreading the generic props and removing the `initialValue`:

`const { initialValue, ...props } = this.props as ExpandedOnChangeProps;`

Another possible case, where we might want to provide a higher order component is when we want to define a generic component, that expects a wrapped component as well as additional configurations or functionalities. Let's write a component that expects a fetch function and a component and returns a component that depending on the result of the fetch either displays nothing, a loading indicator, an error message or in case of a successful fetch the wrapped component.

```js
function withFetch(fetchFn, WrappedComponent) {
  return class Fetch extends React.Component {
    state = {
      data: { type: "NotLoaded" }
    };
    componentDidMount() {
      this.setState({ data: { type: "Loading" } });
      fetchFn()
        .then(data =>
          this.setState({
            data: { type: "Success", data }
          })
        )
        .catch(error =>
          this.setState({
            data: { type: "Error", error }
          })
        );
    }
    render() {
      const { data } = this.state;
      switch (data.type) {
        case "NotLoaded":
          return <div />;
        case "Loading":
          return <div>Loading...</div>;
        case "Error":
          return <div>{data.error}</div>;
        case "Success":
          return <WrappedComponent {...this.props} data={data.data} />;
      }
    }
  };
}
```

There's some work we need to do to prevent TypeScript from complaining.
The first thing we can do is define the actual component state:

```js
type RemoteData<Error, Data> =
  | { type: "NotLoaded" }
  | { type: "Loading" }
  | { type: "Error", error: Error }
  | { type: "Success", data: Data };

type FetchState<Error, Data> = {
  data: RemoteData<Error, Data>
};
```

We can define the promise result type that our `withFetch` component should expect when calling the provided function, that way we can guarantee that the returned promise result type matches the expected data property in our wrapped component.

```js
function withFetch<FetchResultType, Props extends { data: FetchResultType }>(
  fetchFn: () => Promise<FetchResultType>,
  WrappedComponent: React.ComponentType<Props>
) {
  return class Fetch extends React.Component<
    Omit<Props, "data">,
    FetchState<string, FetchResultType>
  > {
    state: FetchState<string, FetchResultType> = {
      data: { type: "NotLoaded" }
    };
    componentDidMount() {
      this.setState({ data: { type: "Loading" } });
      fetchFn()
        .then(data =>
          this.setState({
            data: { type: "Success", data }
          })
        )
        .catch(error =>
          this.setState({
            data: { type: "Error", error }
          })
        );
    }
    render() {
      const { data } = this.state;
      switch (data.type) {
        case "NotLoaded":
          return <div />;
        case "Loading":
          return <div>Loading...</div>;
        case "Error":
          return <div>{data.error}</div>;
        case "Success":
          return <WrappedComponent {...this.props as Props} data={data.data} />;
      }
    }
  };
}
```

There are more examples we can write, but as introductory into the topic, these examples should be a building block to study the topic further.

If you have any questions or feedback please leave a comment here or connect via Twitter: [A. Sharif](https://twitter.com/sharifsbeat)