New React 16.3 Context API


#1

Is it just me, or does the new context API defeat the ease and flexibility of the old context? Now I have to wrap users of context within a consumer component and a wrapper function call which is tightly coupled to the provider. Why can’t I simply have a props-like data structure with an origin of arbitrary anscentry?


#2

In defense of the new stuff, the “tight coupling” I referenced isn’t as bad as it may initially appear (at least to me). For example, I saw instances created via createContext, but in reality it’s a class factory and you can instance those classes to your little heart’s desire. In other words, consumers aren’t tied to a single provider that was created with createContext. The component hierarchy is, in fact, traversed to find the closest instance of a respective provider and uses that for a data source. You can create multiple providers and have consumers pull from any one of those depending on the parenting.

const { Provider, Consumer } = React.createContext();

function MyApp (props) {
  return <div>
    <Provider value="A">
      <MyComponent /> {/* renders A */}
    </Provider>
    <Provider value="B">
      <MyComponent /> {/* renders B */}
    </Provider>
  </div>;
}

function MyComponent (props) {
  return <Consumer>{ letter => letter }</Consumer>
}

This can give you the older style, “arbitrary” provider behavior assuming you only ever use the same provider/consumer classes. But that requirement still makes it more difficult since any redistributable components that may act as consumers since they have no implicit way of knowing what kind of provider is going to be used and thus won’t be able to implement a respective consumer, at least not unless explicitly given one. And at that point we’re back to passing props through children again. But at least it’s only through changes of responsibility.

// ... same MyApp ...

import { LetterRenderer } from 'letter-consumer-lib';

function MyComponent (props) {
  // LetterRenderer may use a consumer internally but
  // won't be linked to the provider in MyApp because
  // it didn't exist when LetterRenderer was defined

  // return <LetterRenderer />

  // instead pass the linked consumer to LetterRenderer

  return <LetterRenderer consumer={ Consumer } />
}

Where LetterRenderer might look something like:

export function LetterRenderer (props) {
  const Consumer = props.consumer;
  return <Consumer>{ letter => `Your letter is "${letter}"!` }</Consumer>
}

(though more likely, I guess, the LetterRenderer would take basic props - i.e. letter vs consumer - and transfer them into its own provider-consumer)


#3

Hi,

I was trying this out when using

But get an error that the method is not found on react create context. I’m downloading the latest react version from npm. Do you guys know why it is not in the latest react? From 16 and up.


#4

Do you have React 16.3?


#5

I thought the following symbol on package.json ^ will download the latest version?


#6

do I have to specify 16.3? or will this get it for me ^16.0


#7

Look in your node_modules/react/cjs/react.development.js file and see what version is printed in the comments at the top:


#8

It says it’s 16.2 not 16.3. I cannot use the new contextt feature. In npm I cannot see version 16.3 either.


#9

You need to download the following: 16.3.0-alpha.1


#10

So I managed to implement something with it specifically when you have many different files / components in files. It’s great I’m in love! When is the final release? Alpha got announced back in February already. I know they have fast release cycles.


#11

How can I share one context with mutiple components?
I cannot bind this for the my Context. I want to avoid redux.
One change is not effecting the other.

    import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

import MyContextProvider from "./MyContextProvider"
import MyContextProvider2 from "./MyContextProvider2"

window.onload = function(){

  let MyContext = React.createContext();
  

  if(document.getElementById('root') != null && document.getElementById('root2') != null){

    ReactDOM.render( <MyContextProvider provider={MyContext.Provider}>
      <App consumer={MyContext.Consumer} />
    </MyContextProvider>, document.getElementById('root'));

    ReactDOM.render(<MyContextProvider2 provider={MyContext.Provider}>
      <App consumer={MyContext.Consumer} />
    </MyContextProvider2>, document.getElementById('root2'));


    registerServiceWorker();
  }
}

#12

I even tried with the same context import
import { Consumer, Provider } from ‘./Context.js’;

where the context.js is

import { createContext } from 'react';
const { Provider, Consumer } = createContext();
export { Consumer, Provider };

The state is not being shared:

 ReactDOM.render( <MyContextProvider provider={Provider}>
      <App consumer={Consumer} />
    </MyContextProvider>, document.getElementById('root'));

    ReactDOM.render(<MyContextProvider provider={Provider}>
      <App consumer={Consumer} />
    </MyContextProvider>, document.getElementById('root2'));

#13

After reading this, I guess I need to use redux here. What do you guys think?

Is it a Redux killer?

No. The new Context API has its limitations.

For example, it encourages the use of immutable or persistent data structures or strict comparison of context values which might prove difficult because many common data sources rely on mutation.

Another limitation is that the new Context API only allows for a Consumer to read values from a single Provider type, one consumer to one provider. Unlike the current API, which allows a Consumer to connect to multiple Providers.


#14

Each provider will have its own distinct value. Consumers will look up the React dom hierarchy until it finds a matching provider and use its value.

If you have a single value you want to share, then you’d use one provider that wraps multiple consumers. If you have multiple renders targeting different DOM elements, you might want to use a portal to manage that while being able to keep your components within the same provider.

const { Provider, Consumer } = React.createContext();

function Root2 (props) {
  return ReactDOM.createPortal(
    props.children,
    document.getElementById('root2')
  );
}

ReactDOM.render(
  <Provider value="shared">
    <Consumer>{ value => <h1>{ `First Root ${value}` }</h1></Consumer>
    <Root2>
      <Consumer>{ value => <h1>{ `Second Root ${value}` }</h1></Consumer>
    </Root2>
  </Provider>,
  document.getElementById('root')
);

#15

How would you get the context object into another consumer class outside of the render function? So that it can be used in other methods that call methods in the context from the consumer?

I want to grab the context to be used outside of the render function and I have set it on a method on load but don’t like the way it is done.

Return
<Consumer>
{(context) => (

 <input type=“Text” onLoad={ e => this.context = context} />
 
)}
<Consumer>

Where this.context is a variable in the consumer class that Uses the consumer. I think this is horrible.


#16

Okay I tried to add a HOC and it worked like so :slight_smile:
Please review.

  import React, { Component } from 'react';
import { Consumer, Provider } from './Context.js';

const WithContext = (Component) => {
return (props) => (
    <Consumer>
         {value =>  <Factory {...props} value={value} />}
    </Consumer>
  )
}

class Factory extends Component {


  componentDidMount(){
  this.props.value.sayMyNameAgain("Millad");
  }


  render() {

    return (
      <Consumer>
      {(context) => (
         <React.Fragment>
           <p>Age: {context.state.age}</p>
           <p>Name: {context.state.name}</p>
           <button  onClick={context.growAYearOlder}>🍰🍥🎂</button>
         </React.Fragment>
       )}
      </Consumer>
    );
  }
}

export default WithContext(Factory);

and then you can use it like

export default withContext(ComponentName);


#17

I think you got the idea with withContext, though I’m not sure your code makes a lot of sense. Aren’t you trying to avoid having <Consumer> in Factory, converting it’s value into a prop by using withContext?


#18

Correct! I should remove Consumer from Factory.