Sharing Context with React Portals
Published: 2018-10-24 07:09:49

---
title: Sharing Context with React Portals
published: true
description: 
tags: 
---


State management in React is a widely discussed topic on the internet. While the most common approach for large applications seems to be Redux, there has been a lot of attention on the improvements of the React Context API. Using render props, we can now access state being held far up the tree with ease. For a single page application, this is ideal because the whole application is one tree.

However, if you are mounting several trees of components throughout the page (in my case I'm using Ruby on Rails and Webpacker React to render the components), the different trees cannot use the same provider as a common ancestor. Hence, they cannot share the same state. So how do we get the different trees to play nicely together?

### React Context + React Portals

Using a syntax very similar to:

```javascript
ReactDOM.render(<MyComponent />, domElement);
```

We can use

```javascript
const PortalComponent = React.createPortal(<MyComponent />, domElement);
```

to render multiple components in the same React tree, among different isolated parts of the DOM. Using `React.createPortal()` we can create a new React component to be rendered at the specified DOM node.



What's different about `createPortal()` when compared to `ReactDOM.render()`, is that the component is not rendered at the point of calling `createPortal()`. We use the return value of `React.createPortal()` (a new React component), and render it in our tree. Here's an example where we are rendering components with different entry points throughout the page:

```javascript
/* first let's define a context to share */
/* important_value_context.jsx */

import React from 'react'

const Context = React.createContext({})

export class ImportantValueProvider extends React.PureComponent {

  state = {
    importantValue: 'Cake is good',
    updateImportantValue: this.updateImportantValue,
  }

  updateImportantValue = importantValue => this.setState({importantValue})

  render() {
    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    )
  }
}

export default Context


/* main.jsx */

import React from 'react'
import ReactDOM from 'react-dom'

import {ImportantValueProvider} from 'path/to/important_value_context'
import CoolComponent from 'path/to/cool_component'
import SuperCoolComponent from 'path/to/super_cool_component'

const Main = () => {

  const ComponentA = () => ReactDOM.createPortal(
    <CoolComponent />,
    document.getElementById('banner'),
  )

  const ComponentB = () => ReactDOM.createPortal(
    <SuperCoolComponent />,
    document.getElementById('footer'),
  )

  return (
    <ImportantValueProvider>
      <ComponentA />
      <ComponentB />
    </ImportantValueProvider>
  )
}

ReactDOM.render(
  <Main />,
  document.getElementById('main'),
)
```

Instead of making two calls to `ReactDOM.render()`, we create two portals and render both under our top-level Provider. `ComponentA` and `ComponentB` will be rendered in two different points in the DOM, but they share the same React tree, thanks to portals. They can both import `ImportantValueContext` and reference its consumer to communicate with the same instance of `ImportantValueContext`.

```javascript
/* cool_component.jsx */

import React from 'react'

import ImportantValueContext from 'path/to/important_value_context'

class CoolComponent extends React.PureComponent {
  render() {
    return (
      <ImportantValueContext.Consumer>
        {sharedState => (
          <div>
            <input
              value={sharedState.importantValue}
              onChange={e => sharedState.updateImportantValue(e.target.value)}
            />
          </div>
        )}
      </ImportantValueContext.Consumer>
    )
  }
}

export default CoolComponent


/* super_cool_component.jsx */

import React from 'react'

import ImportantValueContext from 'path/to/important_value_context'

class SuperCoolComponent extends React.PureComponent {
  render() {
    return (
      <ImportantValueContext.Consumer>
        {sharedState => (
          <h1>{sharedState.importantValue}</h1>
        )}
      </ImportantValueContext.Consumer>
    )
  }
}

export default SuperCoolComponent
```

Now `CoolComponent` and `SuperCoolComponent` can share state with the same ImportantValueProvider. That's fantastic news if you plan on sprinkling components into an existing webpage... if you're into that sort of thing.

NOTE: This applies to working with Redux providers as well, though using context was my use case so I put emphasis on that instead.