Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

16.6 Context API not working in class component #13969

Closed
dericcain opened this issue Oct 25, 2018 · 37 comments
Closed

16.6 Context API not working in class component #13969

dericcain opened this issue Oct 25, 2018 · 37 comments
Assignees

Comments

@dericcain
Copy link

dericcain commented Oct 25, 2018

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

@dericcain
Copy link
Author

dericcain commented Oct 25, 2018

@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.

@dericcain
Copy link
Author

dericcain commented Oct 25, 2018

@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.

@dericcain
Copy link
Author

@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.

@gregordotjs
Copy link

gregordotjs commented Oct 25, 2018

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.

@aweary
Copy link
Contributor

aweary commented Oct 26, 2018

@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.

@aweary aweary closed this as completed Oct 26, 2018
@lxcid
Copy link

lxcid commented Oct 29, 2018

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.

@lxcid
Copy link

lxcid commented Oct 29, 2018

Is it possible that it only work on production build? or @babel/preset-env and @babel/preset-react is transpiling till it broke context?

I have try many many variation upgrade to 16.7.0-alpha.0, just render the context provider and class component with context type.

I could not get it to work at all though…

@clintjansen
Copy link

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)

@lxcid
Copy link

lxcid commented Oct 29, 2018

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

@jacksongabbard
Copy link

jacksongabbard commented Oct 29, 2018

Unless I'm very much mistaken, @dericgw is right that there is an issue here. @aweary, your point that this is related to modules seems incorrect. I have created a copy of this that is all in one file with no module hijinx in the mix. It still does not work: https://codesandbox.io/s/2xm84n3p1p

@hamlim
Copy link
Contributor

hamlim commented Oct 29, 2018

@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

@jacksongabbard
Copy link

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.

@clintjansen
Copy link

clintjansen commented Oct 29, 2018

contexttype
I'm having this issue, using 16.6 and no problem with circular dependency.
But I think it's due to how react-navigation works
Tested with only React Native, and it didnt work either.
Does RN need an update for this to work as well?

Oof, just noticed Expo is using RN 0.55 which is why it probably is not working for me.

@JReinhold
Copy link

I ran into the same issue as @dericgw, and came to the same conclusion as #13969 (comment) 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?

@ghost
Copy link

ghost commented Dec 7, 2018

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).

@gaearon
Copy link
Collaborator

gaearon commented Dec 7, 2018

File an issue about the docs in the docs repo please? It’s not really being tracked here. Thanks.

https://github.com/reactjs/reactjs.org

@gaearon
Copy link
Collaborator

gaearon commented Dec 7, 2018

cc @sebmarkbage on documentation rewrite feedback

@gaearon gaearon reopened this Dec 7, 2018
@gaearon
Copy link
Collaborator

gaearon commented Dec 7, 2018

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

@gaearon gaearon self-assigned this Dec 7, 2018
@JReinhold
Copy link

JReinhold commented Dec 11, 2018

I think the warning would be a good idea. This is a weird scenario that is hard to debug if the developer don't know the internal structures of dependencies, imports, etc., and I think that the offending pattern is a somewhat common mistake (I could be wrong on that though).

But it would probably be a long warning explaining circular dependencies, if we don't add it to the docs and just point the developer there, so I've opened an issue in the docs repo - reactjs/react.dev#1481.

Are there any cases where the developer actually wants the context to be undefined or an empty object, and thus making the warning annoying?

@captain-yossarian
Copy link

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.

@joe06102
Copy link

joe06102 commented Jan 4, 2019

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.

I had the same problem, but resolved after upgrading to react 16.6.0+.
But shouldn't there be any comment in the documentation to remind us that this.context is only supported after 16.6.0+ ?

@badbod99
Copy link

badbod99 commented Feb 6, 2019

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.

@elias551
Copy link

elias551 commented Feb 27, 2019

EDIT: Fixed, no more errors, thanks to next comment

I tried to use static property contextType in classes, but I get an empty object in render or in any lifecycle method.

Based on @javascrewpt example : https://codesandbox.io/s/8k9z6ww150

Context.Consumer component works fine, but contextType never works. I reproduced the example in the documentation from here : https://reactjs.org/docs/context.html#classcontexttype

The result is the same when defining static contextType = Context or MyClass.contextType = Context

Using react / reactDOM 16.8.3

@ConorKelleher
Copy link

@elias551 Have you tried the fix suggested here #13969 (comment)? 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)

@elias551
Copy link

@conor-kelleher thanks for the quick answer ! Sorry I was confused about the explaination I did not understand it correctly.

Still, I have a similar problem in my project, and the Context is correctly defined in a separate module. I suppose I'm having a similar issue, there are index files at each folder auto exporting symbols, so I should check that.

@sfourdrinier
Copy link

sfourdrinier commented Feb 28, 2019

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>
        )
    }
}

@elias551
Copy link

elias551 commented Mar 15, 2019

@sfourdrinier it solved my problem, thanks.

I'm currently working on this project that auto imports every class and functions with index.js at root of any folder exporting every symbol recursively through directories. It is very confusing and error prone for me... You have to always be careful to import things in the right order and I did not find any tooling warning you consistently on potential cyclic dependencies.

If your context is null using static contextType field, there is a great chance that it is caused by a similar problem.

@gaearon
Copy link
Collaborator

gaearon commented Mar 18, 2019

I'm currently working on this project that auto imports every class and functions with index.js at root of any folder exporting every symbol recursively through directories. It is very confusing and error prone for me

Oh dear. Any ideas why it was set up this way? This sounds like a recipe for trouble.

@gaearon
Copy link
Collaborator

gaearon commented Mar 18, 2019

This is not currently working in Expo (version v32.0.0)

@badbod99 Please report it to Expo.

@badbod99
Copy link

badbod99 commented Mar 19, 2019

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.

@gaearon
Copy link
Collaborator

gaearon commented Mar 19, 2019

Ahh that makes sense. Thanks for explaining!

@elias551
Copy link

I'm currently working on this project that auto imports every class and functions with index.js at root of any folder exporting every symbol recursively through directories. It is very confusing and error prone for me

Oh dear. Any ideas why it was set up this way? This sounds like a recipe for trouble.

I think the reason is purely aesthetic, the project favors imports with short paths, this way you have

import { functionA, functionB } from "utils"

instead of

import { functionA } from "utils/UtilA"
import { functionB } from "utils/UtilB"

If A depends of function B, then you have to remember to import with a relative path or else you have a cyclic dependency.

Furthermore, the linter enforces to sort imports by alphebetical order... which can sometimes break imports with very few informations about which import is causing the error.

@gaearon
Copy link
Collaborator

gaearon commented Mar 19, 2019

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 #15142 which will be included in next release. That should make the first case much easier to diagnose.

For doc problems, I filed reactjs/react.dev#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.

@gaearon gaearon closed this as completed Mar 19, 2019
@gaearon
Copy link
Collaborator

gaearon commented Mar 28, 2019

The better warning is included with 16.8.6.

@ersel
Copy link

ersel commented May 30, 2019

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

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

@ali41f
Copy link

ali41f commented Jan 19, 2020

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

@klementtan
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests