react: 16.6 Context API not working in class component

Do you want to request a feature or report a bug? Quite possibly a bug (or maybe confusion about the current API)

I am using the new Context API as well as the new static contextType in React 16.6. I am passing context down a couple components deep but when I attempt to access the context within the component, the object is empty (~only the default value passed into createContext is being displayed~). This is happening in a current feature I am working on at my job, so I cannot display that code, but I did create a Codesandbox with the gist of the problem.

Here is a demonstration of the behavior: https://codesandbox.io/s/r4myz959ro

I would expect to be able to access the current values of the context. This way, if those values change, I would always have the most recent values. Now, maybe this is expected behavior, however, it would be confusing if it is.

React 16.6 ReactDOM 16.6

About this issue

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

Most upvoted comments

@dericgw the problem you’re describing has nothing to do with React, it’s the behavior of circular ES6 module imports. Here’s an example showing the same problem without React.

The index file imports View, which imports the index file and the AnotherLevelDeep, which itself requires the index file again. It’s a circular dependency.

The reason it works with the Context.Consumer API is that the render method is lazily evaluated, while the class definition (including static properties) are evaluated when the module is loaded.

Did u manage to resolve it? I’m facing similar issue.

context is an empty object {} even if I did something like this

const Context = React.createContext();

class Child extends React.Component {
  render() {
    const renderedContext = this.context;
    console.log({ this: this });
    console.log(renderedContext);
    console.log({ asd: Child.contextType });
    return <div />;
  }
}
Child.contextType = Context;

export const Test = props => (
  <Context.Provider value={{ hello: 'world' }}>
    <Child />
  </Context.Provider>
);

I’m quite sure I have no cyclic rendering, make sure I’m on 16.6.0 but nothing seems to work.

Can’t get this to work either, enclosing in <.Consumer> works fine, but the static contextType object is always empty. Using latest React Native (with navigation)

@javascrewpt My issue is why do I have to create context in a separate file? Where in the docs does it say this? The example used in the docs has the context created in the same file: https://reactjs.org/docs/context.html#when-to-use-context

When the docs say one thing and the code behaves differently, this is usually one of three things:

  • A bug
  • A mistake in documentation
  • A lack of understanding of the documentation

I am willing to accept any of the above. While you have provided a workaround, of sorts, it does not address the root cause.

Now, on to your proposed solution. As I have previously stated/questioned, what if my top-level component tracks state and also passes methods to change that state (please see the original codesandbox, which I’ve update: https://codesandbox.io/s/r4myz959ro)? Your simple solution is no longer valid. I would have to completely refactor my solution to separate out the context provider into another top-level component that tracks state, etc. This may be the correct way to do it. But, then again, where is this documented? And, we are back to the original question and the root of the problem.

The documentation makes it seem like my approach should work. It does not mention (anywhere that I can see) why it would not work. Is this a bug, a mistake in documentation, or did I interpret something incorrectly?

I am not trying to dismiss your help and I appreciate you attempting to find a solution for the problem. However, if you are just trying to prove a point by telling me that you, “don’t believe I am using it correctly” or that, “there is no bug and it works as intended”, please do not feel that you need to help anymore.

I’m sorry if I came off too aggressive, I wasn’t trying to dismiss your issue, it’s just my lack of knowledge about this that made me question the problem. And you elaborated perfectly and I understand now your concern. I’m looking forward to hearing from some experts about this, seems an interesting issue.

@javascrewpt While I do believe that is one way to use the Context API, have you looked at this: https://reactjs.org/docs/context.html#classcontexttype ?

The contextType property on a class can be assigned a Context object created by React.createContext(). This lets you consume the nearest current value of that Context type using this.context. You can reference this in any of the lifecycle methods including the render function.

By the looks of that, I should be able to do what I am attempting to do.

In this issue, we’ve seen two kinds of problems:

  • Circular dependencies, leading contextType to be undefined or empty object instead of the context. (This can happen in legacy environments that don’t have real ESM with which it would be an error.)
  • Old React versions which don’t support this API. (Such as somebody forgetting to update ReactDOM, or using old RN.)

We added a better warning for the circular dependency case in https://github.com/facebook/react/pull/15142 which will be included in next release. That should make the first case much easier to diagnose.

For doc problems, I filed https://github.com/reactjs/reactjs.org/issues/1842 to track them. The second problem will also gradually go away as more people update React.

I don’t think there’s anything else actionable in this issue so I’m closing. Thanks everyone for feedback.

For my case, I forgot to upgrade react-dom to 16.6.0, I manage to get it working after upgrade!

@clintjansen I think you might need react-native to official support 16.6.0

This is not currently working in Expo (version v32.0.0). See https://snack.expo.io/@badbod99/calm-candies. Code based on React Context example, updated for React Native.

Setting contextType as ‘ThemedButton.contextType = ThemeContext;’ does not result in this.context having current context value from ThemeContext.Provider above.

Using ‘<ThemeContext.Consumer>’ does work.

Not a circular reference issue nor a documentation problem. Context creation is in separate file.

@javascrewpt That is a solution, but the approach seems to avoid the original issue. Also, the top-level component could be used to store state and also provide methods that manipulate that state. In your last example, you would need to jump through more hoops to account for that. From what I can tell, this is the whole purpose of the changes to the context API in 16.6:

We’ve heard feedback that adopting the new render prop API can be difficult in class components. So we’ve add a convenience API to consume a context value from within a class component.

Quoted from: https://reactjs.org/blog/2018/10/23/react-v-16-6.html?no-cache=1#static-contexttype

I appreciate your help. I would like to address the root of the issue or gain clarification of the intentions of the API. If the API is not supposed to work like I am attempting to use it, then I would propose (and even help) in clarifying the documentation so that others do not run into the same issue.

I had the exact same problem, running react native & using react-navigation.

  • Without react-navigation you see the value that comes from the provider
  • With react-navigation you see the default-value setup at context creation time.
  • The behavior is exactly the same if I use: ** contextType=AuthContext ** MyClass.contextType = AuthContext ** Or if I use directly the consumer in the hello component’s render function.

As an observation having independent files or not changed nothing in all my tests.

What fixed it for me, using react-navigation was to have my ContextProvider as the highest level of the application:

export const ContainedApp = createAppContainer(AppNavigator);
export default class GlobalHome extends React.Component {
    render () {
        return (
        <GlobalStateProvider>
            <ContainedApp/>
        </GlobalStateProvider>
        )
    }
}

I had same problem, but I used react 16.6.0 and react-dom 16.5.0 😃 With react 16.6.0 and react-dom 16.6.0 it works as expected.

IMO we should warn if it looks like a circular dependency (empty object or undefined).

I ran into the same issue as @dericgw, and came to the same conclusion as https://github.com/facebook/react/issues/13969#issuecomment-433253469 by @aweary, that it is a circular dependency problem. I had to move the Context creation out into a separate file, that both the parent component (that holds the Provider), and the child component (that consumes) imports from. this solves the dependency problem for me, at least.

I don’t know how common this problem is, but I agree with @dericgw that if you follow the documentation to the point, you run into this problem, which means there should probably be a note about it in the docs. It seems like @sophiebits are in charge of the docs, do you agree with me?

Ah, indeed, indeed. I should’ve checked the changelog. I would’ve noted that the this.context syntax arrived with 16.6. Thanks for pointing me in the right direction.

The better warning is included with 16.8.6.

Hi! React Context works perfectly on my application on development environment with Webpacker but when I precompile my assets to serve my application in production environment it throw me TypeError: undefined is not an object (evaluating 'n.context.user.username') error. I tried implementing all the suggested solutions but it still didn’t work. I upgrade the versions: to "react": "^16.7.0", "react-dom": "^16.6.0" and defining the Context creation in a separate file. The app will work fine if we use <UserContext.Consumer/> but want to stick to try our best to stick to the contextType method. Would appreciate any help. Thank you.

Here are the links for the code:

Context Creation https://github.com/VantageSG/Vantage/blob/master/app/javascript/contexts/UserContext.jsx

Context Provider https://github.com/VantageSG/Vantage/blob/master/app/javascript/components/App.jsx

Context Consumer https://github.com/VantageSG/Vantage/blob/master/app/javascript/components/navBar/desktopNavBar.jsx

moving createContext call to a seperate file/module fixed the problem for us.

    "react": "^16.7.0",
    "react-native": "^0.57.8",

@jacksongabbard it seems like the codesandbox you shared is still using the 16.5.2 versions of React and React-dom and not 16.6.0, here is a codesandbox using 16.6 showing the context being assigned and rendered correctly: https://codesandbox.io/s/m31mv7wox9

Thanks, didn’t know that. What I did figure was when you export context from index.js and import it in that component, it’s an empty object. Then I tried to export a regular object with a few properties and import it the same and it was an empty object again. So I added an additional file and export just the context with the default values and now it seems to work. https://codesandbox.io/s/xjj2nq5r34

My take from this is, there was a problem with exporting/importing context rather than with how React handles context. Perhaps you should investigate that separately.

Exporting/importing removes circular dependency. This answer explains it perfectly https://stackoverflow.com/questions/56384761/cannot-access-class-contexttype-within-a-child-component

Already reported (and they replied). They’ve not yet updated expo to 16.8, so it doesn’t yet support static contextType. Seems they have been waiting for RN 0.59 before making the next jump. No date as of yet.

@elias551 Have you tried the fix suggested here https://github.com/facebook/react/issues/13969#issuecomment-433253469? I had the same issue and moving the context creation into its own file fixed it for me. So my file structure was like:

- constants.js (exporting the newly created context)
- ProviderComponent.jsx (importing the context from constants.js)
- ...
- .../.../ConsumerComponent.jsx (importing the context from constants.js)

I too was bitten by this and once I hoisted the context creation into its own file (i.e., one Context.js file and a separate ContextProvider.js file), everything worked. It also obviated the need for wrapping some components with <Context.Consumer> as this.context is now readily available to the component’s render method.

But I think the real issue here is the documentation (as noted above). Currently, it reads almost as a stream of consciousness, meandering between what not to do and ending up with potential pitfalls. There is a lack of prescriptive information that outlines clear use-cases and patterns to the developer.

Agreed, this is a new API and patterns are still emerging so I’m afraid we’ll be dealing with a depth-first tree-traversal of incorrect application models until stumbling on the right track as outlined in this thread (and I am very appreciative for the smart comments posted here that made my stuff work).