react: nested context does not update any longer

Do you want to request a feature or report a bug?

Bug

What is the current behavior?

Nested context consumers do not seem to update leaving higher up updates stale:

context

https://codesandbox.io/s/1qwq93n01q

What is the expected behavior?

The critical piece of code that composes multiple consumers is this one:

let values = []
return [...contextRefs, Wrapped].reduceRight((accumulator, Context, i) => (
    <Context.Consumer>
        {value => {
            values[i] = value
            if (accumulator === Wrapped) {
                let context = mapContextToProps(...values, props)
                context = typeof context === 'object' ? context : { context }
                return <Wrapped {...props} {...context} />
            } else return accumulator
        }}
    </Context.Consumer>
))

From a dynamic array of context providers it creates a nested blob of consumers with the receiver sitting at the end (Foo in this example).

The first consumer fires and does get the value, it then returns the sub-tree (accumulator, which contains the second consumer, which contains Foo) but it doesn’t actually render, it looks like something in Context.Consumer prevents it.

In the React alphas i believe they would render regardless. I wonder how it would be possible to forward changed values to the actual receiver, i can’t call setState in there.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

React 16.3.1

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 27 (2 by maintainers)

Most upvoted comments

Given your example with

<context1.Consumer>
    {value1 =>
    <context1.Consumer>
        {value2 =>
        <context1.Consumer>
            {value3 => `${value1} ${value2} ${value3}`}
        </context1.Consumer>
        }
    </context1.Consumer>
    }
</context1.Consumer>

If you’re curious how I unrolled it (or, rather, rolled it?):

gif that goes through the process

Note: I don’t recommend anyone to write code like this in applications, it’s really hard to read. Might be handy for some very specialized libraries.

Looks like the version without mutation works.

    const consumers = [context1, context2].reduceRight(
      (inner, ctx) => (...args) => (
        <ctx.Consumer>
          {value => inner(...args, value)}
        </ctx.Consumer>
      ),
      (...values) => <View values={values} />
    )();

https://codesandbox.io/s/13zw6lxm1j

@damianobarbati It looks like your <Link /> components are rendered outside of the router context (see Layout.js and index.js). Currently as you have it the Nav component is rendered above the Router, meaning the Links will fallback to the default value passed to createContext.

Thanks @hamlim, that solved! 👍🏻

If it’s easier to do with a loop then use a loop 🙂 I don’t think it’s necessary to use reduce in every case.

If you list each context in a separate statement like I did then it should become visible what the loop body should be like. Then reduce vs loop is only a choice of form.

Very interesting, thanks a lot, i’ll see if this works.

No. I believe none of them do any mutations in the process, as you seem to need to do in here. I know at least mine does not do any mutation at all (it used to do it in the initial implementation but it is now purely pure, pun intended).

Not saying this is the problem, but it looks shady to me:

screen shot 2018-04-11 at 4 24 10 pm

Render method (including context consumer’s render prop) is supposed to be pure. Using it with a closure like this can lead to weird bugs.