Working with React children
Tags: typescript, react
Published: 2022-02-11 15:33:59

In this article I will try to explain what steps have to be taken to achieve two aspects within a React component using TypeScript:

1. Define the data type that ensures that the component in question will only accept components of a certain type as `children`.

2. Traverse all component children making only those that are of a certain type be shown and adding new props to it.

## Type of `children`

The best way to understand how we can type children inside a component in React is with an example. Let's suppose that we start with the component `ComponentA` and we want to determine that it is only going to accept `ComponentB` as children, how can we do it? Supposing that `ComponentA` is defined as a Functional Component we are going to find something like the following:

```ts
export const ComponentA: FunctionComponent<T> = ({ 
  children 
}): JSX.Element => ...
```

It is clear that the previous example is not correct for TypeScript but what we are trying to emphasize is that `T` is the representation of the data type that collects the props that our `ComponentA` receives. What does this mean? Well, we can define an interface (or type) to declare the data types that are associated to our props and use it to declare the component. So, if we now declare our component as follows:

```ts
export const ComponentA: FunctionComponent<ComponentAProps> = ({ 
  children 
}): JSX.Element => ...
```

Now we only have to declare the `ComponentAProps` data type and more specifically, define the data type that we want to assign to `children`. But what data type is the one that corresponds to this React prop? The answer is that React provides us with the ReactElement type for each of the elements that can populate the Virtual DOM so if we want to allow children to be of these types we should declare something like the following:

```ts
interface ComponentAProps {
  children: ReactElement<S> | Array<ReactElement<S>>
}
```

That is, we are declaring that as `children` we will have either a single element (which is represented as `ReactElement<S>`) or several elements (hence the use of `Array`, i.e. `Array<ReactElement<S>>`). But are we forcing these elements to be of a certain type? The answer is no, but what we can *play* with is that we again have a generic type that we can use when declaring it (in our example `S`) so if we define this generic type as the data type that defines the props of the child components TypeScript already tells us that only those child components are allowed.

As the explanation is complicated it is better to see it following with our example. Let's suppose that the child component that we want to define (let's remember that it is `ComponentB` defines in the following interface the props that it supports):

```ts
interface ComponentBProps {
  // props definition
}
```

What we can now do when declaring the `ComponentA` props is to make use of this declaration as follows:

```ts
interface ComponentAProps {
  children: ReactElement<ComponentBProps> | Array<ReactElement<ComponentBProps>>
}
```

Achieving in this way that from the point of view of TypeScript right now `ComponentA` only admits as `children` those elements that are a `ReactElement` with the `ComponentBProps` props.

> **Note:** Although from TypeScript's point of view the typing is correct the code editors like VSCode will not do the type checking while we are developing so we have managed to type it right but it is not reflected in the code.

## Visit `children`

What steps do we have to take to go through all the `children` that a component receives? Well, this is where we have to make use of the `map` method provided by the React `Children` object (you can get more information about the **React High-Level API** [here](https://es.reactjs.org/docs/react-api.html)). That is, we can do something like the following:

```ts
import { Children } from 'react'

export const ComponentA: FunctionComponent<ComponentAProps> = ({ 
  children 
}): JSX.Element => (
	<>
		{ Children.map(....)}
	</>
)
```

This method accepts two parameters, the first one being the `children` prop (the one we are going to traverse) and the second one a function that will be executed on each of the elements that conform it. Now, what type of data is each of the elements? Well, in this case React offers us the `ReactNode` type to represent it. This leaves us with the following declaration:

```ts
import { Children } from 'react'

export const ComponentA: FunctionComponent<ComponentAProps> = ({ 
  children 
}): JSX.Element => (
	<>
		{ Children.map(children, (child: ReactNode) => {})}
	</>
)
```

How can we know the data type to which each of the child nodes belongs? Well, this is where it comes into play knowing that `ReactNode` has an attribute called `type` that contains the type of data to which the node belongs. For example, if the node in question is of type `ComponentB` you can do something like the following:

```ts
import { Children } from 'react'
import { ComponentB } from './ComponentB'

export const ComponentA: FunctionComponent<ComponentAProps> = ({ 
  children 
}): JSX.Element => (
  <>
    { Children.map(children, (child: ReactNode) => {
        if (child.type === ComponentB) {
        // .... do stuff ....
        }
    })}
  </>
)
```

The problem here is that TypeScript is going to complain as it cannot be sure that the `child` node in the example has the `type` attribute so it is time to use one of the stop functions provided by React `isValidElement` which returns `true` in case the node being processed is a React element and therefore we can guarantee that it has the `type` attribute with the TypeScript will let us continue:

```ts
import { Children } from 'react'
import { ComponentB } from './ComponentB'

export const ComponentA: FunctionComponent<ComponentAProps> = ({ 
  children 
}): JSX.Element => (
  <>
    { Children.map(children, (child: ReactNode) => {
        if (isValidElement(child) && child.type === ComponentB) {
        // .... do stuff ....
        }
    })}
  </>
)
```

> If you want to learn more about the `isValidElement` function we recommend [read the official documentation](https://es.reactjs.org/docs/react-api.html#isvalidelement) about it.

## Add `props` to the `children`

As a last step what we want to do is to add new props to each of the `children` nodes that meet that are of type `ComponentB`. In this case the strategy that we are going to follow consists of making use of the function of High Level of React called `cloneElement` so what we want to obtain is an instance equal to the one that we have in the child node (we want that the same thing is rendered), but knowing in addition that to this function we can pass a second attribute that will have an attribute for each one of the `props` that we are going to inject. Thus, in the case that we want to inject the `injectedProp` property we would write something like the following:

```ts
import { Children } from 'react'
import { ComponentB } from './ComponentB'

export const ComponentA: FunctionComponent<ComponentAProps> = ({ 
  children 
}): JSX.Element => (
  <>
    { Children.map(children, (child: ReactNode) => {
        if (isValidElement(child) && child.type === ComponentB) {
    return cloneElement(child, { 
                injectedProp: // what we want to inject it (for example, a function)
          })
        }
    })}
  </>
)
```

But how do we reflect and collect these `props` injected into the `ComponentB`? The answer is by doing that JavaScript spread operator thing to pick up the rest of the `props`, which leaves us with something like the following:

```ts
export const ComponentB: FunctionComponent<ComponentBProps> = ({ 
  ...props 
}): JSX.Element => ...
```

and this way in the code of `ComponentB` we could access directly to the `injectedProp` as if it was one of the `prop` that have been declared in the component.

> If you want to learn more about the `cloneElement` function we recommend [read the official documentation](https://es.reactjs.org/docs/react-api.html#cloneelement) about it.