Notes on TypeScript: Render Props
Tags: react, typescript, types
Published: 2019-01-21 09:45:51

---
title: Notes on TypeScript: Render Props
published: true
description: Notes on using Render Props and TypeScript
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.


### More Notes on TypeScript

[Notes on TypeScript: Pick, Exclude and Higher Order Components](https://dev.to/busypeoples/notes-on-typescript-pick-exclude-and-higher-order-components-40cp)

### Render Props

`Render Props` is a popular pattern for enhancing a React Component with additional functionality. It can be interchanged with a higher order component, and choosing the render props pattern or a higher order component is a matter of flavour and depends on the specific use case.

To get a better understanding of the topic, let's build a component that uses a render prop. In the previous ["Notes on TypeScript"](https://dev.to/busypeoples/notes-on-typescript-pick-exclude-and-higher-order-components-40cp) we built a component that provided an `Input` component with `onChange` and `value` properties.
We can rewrite this higher order component to a render prop implementation.

```js
class OnChange extends React.Component {
  state = {
    value: this.props.initialValue
  };
  onChange = event => {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    this.setState({ value });
  };
  render() {
    return this.props.render({
      value: this.state.value,
      onChange: this.onChange
    });
  }
}
```

Using the refactored `OnChange` inside your React application:

```js
<OnChange
  initialValue="hello"
  render={onChangeProps => <Input {...props} {...onChangeProps} />}
/>
```

We can reuse most of the previously defined types.

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

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

type ExpandedOnChangeProps = {
  initialValue: string | boolean,
  render: (onChangeProps: onChangeProps) => JSX.Element
};

type OnChangeState = {
  value: string
};
```

Our `Input` component has not changed, we can also reuse that component for this example.

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

So now that we have everything in place, let's see how `OnChange` would be typed.
Interestingly, there's not very much we need to do, to type the `onChange` component.

```js
class OnChange extends React.Component<ExpandedOnChangeProps, OnChangeState> {
  state = {
    value: this.props.initialValue
  };
  onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const target = event.target;
    this.setState({ value: target.value });
  };
  render() {
    return this.props.render({
      value: this.state.value,
      onChange: this.onChange
    });
  }
}
```

Compared to the higher order component implementation, we only need to define props and state for `OnChange`, in this case using the already existing `ExpandedOnChangeProps` and `OnChangeState` and defining the class component as follows:

`class OnChange extends React.Component<ExpandedOnChangeProps, OnChangeState>`.

We might want to reuse the functionality in multiple places inside our application. By defining a new component, f.e. `ControlledInput`, we can combine our `OnChange` and `Input` and let developers define the `initialValue` as well as `name` and `type`.

```js
type ControlledInputProps = InputProps & { initialValue: string };

const ControlledInput = ({ initialValue, ...props }: ControlledInputProps) => (
  <OnChange
    initialValue={initialValue}
    render={onChangeProps => <Input {...props} {...onChangeProps} />}
  />
);
```

Now `ControlledInput` can be used inside another component and TypeScript will complain when either `name`, `type` or `initialValue` is missing.

```js
<ControlledInput initialValue="testdrive" type="text" name="test" />
```

### Advanced

We might want to enable to either pass the render callback via render or children prop. This requires us to make some changes to our `OnChange` component. If we recall, our `ExpandedOnChangeProps` has the following shape:

```js
type ExpandedOnChangeProps = {
  initialValue: string | boolean,
  render: (onChangeProps: onChangeProps) => JSX.Element
};
```

One way to enable passing callbacks as children prop is to change the definition to the following:

```js
type ExpandedOnChangeProps = {
  initialValue: string,
  render?: (onChangeProps: onChangeProps) => JSX.Element,
  children?: (onChangeProps: onChangeProps) => JSX.Element
};
```

But the above definition has problems, as both or none of the variants could be provided now. What we actually want is to ensure that one of these properties is defined, which is possible defining an explicit `RenderProp` type:


```js
type RenderProp =
  | { render: (onChangeProps: OnChangeProps) => JSX.Element }
  | { children: (onChangeProps: OnChangeProps) => JSX.Element };
```

Which means we can rewrite our `ExpandedOnChangeProps` definition to:

```js
type ExpandedOnChangeProps = {
  initialValue: string
} & RenderProp;
```

Finally we need to update the render function to handle both possible cases:

```js
class OnChange extends React.Component<ExpandedOnChangeProps, OnChangeState> {
  state = {
    value: this.props.initialValue
  };
  onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const target = event.target;
    this.setState({ value: target.value });
  };
  render() {
    if ("render" in this.props) {
      return this.props.render({
        value: this.state.value,
        onChange: this.onChange
      });
    }

    if ("children" in this.props) {
      return this.props.children({
        value: this.state.value,
        onChange: this.onChange
      });
    }

    throw new Error("A children or render prop has to be defined");
  }
}
```

By using `"render" in this.props`, we can check if `render` is defined else check if a `children` property is defined. In case neither properties are defined we throw an error.
Our previously defined `ControlledInput` could be rewritten to:

```js
const ControlledInput = ({
  initialValue,
  ...props
}: InputProps & { initialValue: string }) => (
  <OnChange initialValue={initialValue}>
    {onChangeProps => <Input {...props} {...onChangeProps} />}
  </OnChange>
);
```

We should have a basic understanding of how render props can be typed with TypeScript now.

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