Creating Our Own Version of Context
Tags: react, javascript
Published: 2020-11-15 04:59:19

If you're a React developer, you've probably heard of [context](https://reactjs.org/docs/context.html). If you haven't, let me fill you in. Before I can explain, we'll need some (no pun intended) context. React allows you to share data between components via the use of [props](https://www.w3schools.com/react/react_props.asp). This is great but we quickly run into problems when components that are deeply nested in our component tree require data that is also needed higher up in the tree. The most straight forward solution is to [drill props](https://kentcdodds.com/blog/prop-drilling) or manually pass the data  down the tree until it gets to where it is needed - yeaaaah not fun. 
 
According to React's documentation, Context provides a way to pass data through the component tree without having to pass props down manually at every level ie. It allows us to skip drilling props like a crazy person. So how exactly does it work?
Let's have a look.

*(This is the most contrived of examples)*
## 1. Create Context

```js
import { createContext } from "react";

export const MessageContext = createContext("");

```

## 2. Wrap Section of Component Tree in Context.Provider
```js
// index.js

import React from "react";
import { MessageContext } from "./context";
import ChildA from "./components/ChildA";
import ChildB from "./components/ChildB";

export default function App() {
  return (
    <MessageContext.Provider value="Message from context">
      <div style={{ fontFamily: "sans-serif", textAlign: "center" }}>
        <ChildA />
        <ChildB />
      </div>
    </MessageContext.Provider>
  );
}

```
By wrapping this section of our component tree in the `MessageContext.Provider` tag, we're now able to access the value of the provider from any descendant components.

## 3. useContext

```js
import React, { useContext } from "react";
import { MessageContext } from "../context";

function ChildA(props) {
  const message = useContext(MessageContext);
  return (
    <div>
      <h2>ChildA</h2>
      <p>{message}</p>
    </div>
  );
}

export default ChildA;

```

Now you have an idea of how Context works, how about we create our own version. 

## Creating Our Own Context

First, let's create a component that will function as our Provider. Here are a few requirements I came up with for our Provider component:

1. Accepts any data passed via props
1. Can be overridden by descendant components 
1. Data will be passed to all descendant components

I'll post the completed code and then provide a walk through of exactly what's happening.

### `ancestor/index.js`
```js
function Ancestor(){
     
    function passProps(child) {
  
        if (Object.hasOwnProperty.call(child.props, 'children')) {
            const newChildren = Children.map(child.props.children, (_child) => {
                if (isValidElement(_child)) {
                    return passProps(_child)
                }
                return _child
            })

            return cloneElement(child, {
                ...props,
                ...child.props,
                children: newChildren,
            })
        }

        return cloneElement(child, { ...props, ...child.props })
    }
    
   
    const descendants = Children.map(children, (child) => passProps(child))
 
    
    return <>{descendants}</>
}
```

To iterate through each child present in our children prop, we make use of React's `Children` property that exposes a map function similar to `Array.map`:
```js
const descendants = Children.map(children, (child) => passProps(child))
```
 On each iteration, we pass an element to the `passProps` function. `passProps` is a recursive function that will iterate through every child and pass the props provided from our provider component.

`passProps` will first check if the child passed has a `children` property:
```js
if (Object.hasOwnProperty.call(child.props, 'children'))
```

If it does, it will iterate through each element checking if it's a valid React component. If it is valid, we return `passProps` with that element as an argument. Otherwise, we just return the element unchanged.
```js
const newChildren = Children.map(child.props.children, (_child) => {
                if (isValidElement(_child)) {
                    return passProps(_child)
                }
                return _child
            }) 
```
This creates new children. Our next step is to create a clone of the child passed to `passProps` and overwrite the children prop with our new children. React exposes a function called [cloneElement](https://reactjs.org/docs/react-api.html#cloneelement). cloneElement functions similarly to `Object.assign` allowing us to create a clone of the element and assign new values and props.
This is also where we want to enforce requirement #2. By destructuring the `child.props` after the `props` object, we ensure that any child props override props passed from our provider component.

```js
return cloneElement(child, {
                ...props,
                ...child.props,
                children: newChildren,
            })
```

In the event that the child does not have children, we simply return a copy with our child props overriding the props from our provider component - similar to what we have above.

```js
return cloneElement(child, { ...props, ...child.props })
```

Now to get this all working, we simply need to return our new and improved children.

```js
return <>{descendants}</>
```



Let's try replacing the context provider from our example with our custom provider component but this time instead of a value, we'll pass a `message` prop.

### `App.js`
```js
import React from "react";
import Ancestor from "./ancestor"

export default function App() {
  return (
    <Ancestor message="Message from the ancestor">
      <div style={{ fontFamily: "sans-serif", textAlign: "center" }}>
        <ChildA />
        <ChildB />
      </div>
    </Ancestor>
  );
}

```

How do we access the data passed down from Ancestor? Easy. We access it like any prop passed to a component. 

### `ChildA.js`

```js
import React from "react";


function ChildA({message}) {
  
  return (
    <div>
      <h2>ChildA</h2>
      <p>{message}</p>
    </div>
  );
}

export default ChildA;
```

Boom! We just made our own version of context. Albeit, a contrived version that has performance issues 😂. 

You might be wondering why you'd ever need this. You probably don't. If you ever need to avoid drilling props, just useContext. If you prefer to live life on the edge, I made this code into a [package](https://www.npmjs.com/package/react-ancestor). Give it a go `npm i react-ancestor`.


Thank you for reading! All questions and comments are appreciated 😄.

Follow me on Twitter [@reactdon](https://twitter.com/reactdon)