Dupocas
Dupocas

Reputation: 21317

What is the purpose of having a consumer rendering a provider?

I was taking a look at react-jss source when I came across the following piece of code in their main provider, which is called JssProvider

  renderProvider = (parentContext: Context) => {
    const {children} = this.props
    const context: Context = this.createContext(parentContext, this.prevContext)
    this.prevContext = context
    return <JssContext.Provider value={context}>{children}</JssContext.Provider>
  }

  render() {
    return <JssContext.Consumer>{this.renderProvider}</JssContext.Consumer>
  }
}

Source


This confused me a lot, at first I thought to be a recursive way to get access to the previous context without much effort. But I tried to reproduce it in a sandbox like this

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

const FooProvider = ({ children }) => {
  const [state, setState] = React.useState({
    value: "foo",
    setValue: value => setState(p => ({ ...p, value }))
  });

  const renderProvider = context => {
    console.log(context);
    return <Provider value={state}>{children}</Provider>;
  };

  return <Consumer>{renderProvider}</Consumer>;
};

function App() {
  const onClick = setter => {
    setter("bar");
  };
  return (
    <FooProvider>
      <Consumer>
        {context => (
          <div>
            {context.value}
            <button onClick={() => onClick(context.setValue)}>Change</button>
          </div>
        )}
      </Consumer>
    </FooProvider>
  );
}

Edit restless-waterfall-0veto

The problem is that the context provided to renderProvider is always undefined.

What is the purpose of this particular pattern? What am I missing here?

Upvotes: 3

Views: 143

Answers (1)

Nicholas Tower
Nicholas Tower

Reputation: 85012

When there are multiple of the same provider in the component tree, they will "shadow" eachother, in a similar way to how variables with the same name shadow eachother. Any time you render a consumer, it will see the value from only the nearest provider up the tree.

So what they're doing here is taking advantage of that. A provider exists near the top of the tree, and makes a value available to its descendants. One of those descendants listens to that value, but then immediately makes a modified version of that value and provides it instead. So anything that's below this second provider will only be able to see the modified value, not the original one.

I'm not exactly sure what they're using it for in their code, but i've used it previously for a couple things:

1) For overriding the theme of the page for just a certain section.

We'd have a theme provider at the top of our app which specifies constants to use for styling. But then somewhere in the middle of the tree was a component which would read in that global theme, override a few values, and then provide the modified theme to its descendants. Those descendants could only access the modified theme.

In our case, this let us show a preview of what the theme would look like if it were applied globally.

2) For aggregating metadata about our component tree.

We had a use case where components needed to know the area of the app they were being used in, so we set up a provider near the at the top of the tree which provided a value saying, basically {page: 'Login'} (or whatever). Below that was a consumer/provider pair, which would take that in and add to it, providing {page: 'Login', section: 'Credentials'}. Then one below that which augmented it again to {page: 'login', section: 'Credentials', subsection: 'email'}.

And then finally, any component that simply consumed the context (without providing a value of its own) could know where it was located in the app. It would only get the value from its nearest ancestor, so it had just the right amount of information.

The problem is that the context provided to renderProvider is always undefined.

The reason you're seeing undefined is that that's the default value of the context you're created (since you didn't pass anything in to React.CreateContext()), and you only have one level of nesting. If you had multiple of these consumer/provider pairs nested in eachother, each one would log out what it got from its nearest ancestor, and the topmost one would log out undefined. You only have that topmost one.

Upvotes: 3

Related Questions