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

Support class component static contextType attribute #13728

Merged
merged 2 commits into from Sep 25, 2018

Conversation

@bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Sep 25, 2018

Note: the discussion for this proposal is in reactjs/rfcs#65.

The main motivation is that continuing to support legacy context makes React slower and larger, and we'd like to be able to help everyone migrate to the new context API as soon as possible. Since the new context API is currently too low-level and/or verbose when used in classes, we are considering adding this higher-level API that feels more familiar and will simplify the migration.


New static contextType property is supported for class components. This property can be set to the value returned by React.createContext() in order to consume the context value via this.context, e.g.:

const LocaleContext = React.createContext('blue');

class LocalizedComponent extends React.Component {
  static contextType = LocaleContext;

  render() {
    return <div>The locale is: {this.context}</div>
  }
}

Resolves #13336

DEV warnings have been added for the following cases:

  • Class component has contextType instance property.
  • Class component has both contextType and contextTypes static properties.
  • Class component has an invalid contextType static property (e.g. mistaken contextType = Context.Provider).
  • Stateless functional component has an (unsupported) contextType property.

New tests have been added to verify the following:

  • Class lifecycle nextContext params.
  • Class lifecycle this.context values.
  • Class is forcefully updated (without shouldComponentUpdate being called) when context provider updates.
  • All of the above DEV warnings.
@bvaughn bvaughn requested review from gaearon and acdlite Sep 25, 2018
const context = isContextConsumer
? getMaskedContext(workInProgress, unmaskedContext)
: emptyContextObject;
let isContextConsumer = false;

This comment has been minimized.

@acdlite

acdlite Sep 25, 2018
Member

Can we name this isLegacyContextConsumer to be more specific?

Copy link
Member

@acdlite acdlite left a comment

Can we add a test that confirms that the component that receives a context change will still re-render even if sCU returns false (also PureComponent)? I know you said the test passes but we should have it anyway to prevent a regression.

Also this PR makes it even more clear that we should implement legacy context on top new context.

@sizebot
Copy link

@sizebot sizebot commented Sep 25, 2018

ReactDOM: size: 🔺+0.5%, gzip: 🔺+0.3%

Details of bundled changes.

Comparing: 13965b4...353cc7e

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js +0.4% +0.2% 652.23 KB 654.86 KB 152.89 KB 153.21 KB UMD_DEV
react-dom.production.min.js 🔺+0.5% 🔺+0.3% 92.2 KB 92.64 KB 30.02 KB 30.11 KB UMD_PROD
react-dom.development.js +0.4% +0.2% 647.56 KB 650.2 KB 151.5 KB 151.83 KB NODE_DEV
react-dom.production.min.js 🔺+0.5% 🔺+0.2% 92.19 KB 92.63 KB 29.67 KB 29.73 KB NODE_PROD
ReactDOM-dev.js +0.4% +0.2% 664.15 KB 667.05 KB 152.11 KB 152.45 KB FB_WWW_DEV
ReactDOM-prod.js 🔺+0.3% 🔺+0.2% 284.12 KB 285.02 KB 52.49 KB 52.58 KB FB_WWW_PROD
react-dom.profiling.min.js +0.5% +0.2% 95.18 KB 95.62 KB 30.33 KB 30.4 KB NODE_PROFILING
ReactDOM-profiling.js +0.3% +0.2% 290.93 KB 291.83 KB 53.95 KB 54.05 KB FB_WWW_PROFILING
react-dom.profiling.min.js +0.5% +0.3% 95.12 KB 95.56 KB 30.76 KB 30.85 KB UMD_PROFILING

react-art

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-art.development.js +0.6% +0.3% 442.15 KB 444.78 KB 99.24 KB 99.58 KB UMD_DEV
react-art.production.min.js 🔺+0.5% 🔺+0.2% 84.64 KB 85.08 KB 25.99 KB 26.05 KB UMD_PROD
react-art.development.js +0.7% +0.4% 373.88 KB 376.52 KB 82.07 KB 82.4 KB NODE_DEV
react-art.production.min.js 🔺+0.9% 🔺+0.5% 49.6 KB 50.04 KB 15.24 KB 15.31 KB NODE_PROD
ReactART-dev.js +0.8% +0.4% 377.24 KB 380.14 KB 80.52 KB 80.85 KB FB_WWW_DEV
ReactART-prod.js 🔺+0.5% 🔺+0.4% 159.94 KB 160.81 KB 27.13 KB 27.23 KB FB_WWW_PROD

react-test-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-test-renderer.development.js +0.7% +0.4% 385.87 KB 388.51 KB 84.63 KB 84.96 KB UMD_DEV
react-test-renderer.production.min.js 🔺+0.9% 🔺+0.4% 50.8 KB 51.23 KB 15.54 KB 15.6 KB UMD_PROD
react-test-renderer.development.js +0.7% +0.4% 381.44 KB 384.07 KB 83.5 KB 83.83 KB NODE_DEV
react-test-renderer.production.min.js 🔺+0.9% 🔺+0.4% 50.51 KB 50.95 KB 15.37 KB 15.43 KB NODE_PROD
ReactTestRenderer-dev.js +0.8% +0.4% 384.94 KB 387.84 KB 82.22 KB 82.55 KB FB_WWW_DEV

react-reconciler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-reconciler.development.js +0.7% +0.4% 369.71 KB 372.35 KB 80.19 KB 80.52 KB NODE_DEV
react-reconciler.production.min.js 🔺+0.9% 🔺+0.5% 49.29 KB 49.73 KB 14.79 KB 14.86 KB NODE_PROD
react-reconciler-persistent.development.js +0.7% +0.4% 368.15 KB 370.78 KB 79.56 KB 79.88 KB NODE_DEV
react-reconciler-persistent.production.min.js 🔺+0.9% 🔺+0.5% 49.3 KB 49.74 KB 14.8 KB 14.87 KB NODE_PROD

react-native-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactNativeRenderer-dev.js +0.6% +0.3% 501.09 KB 504 KB 111.01 KB 111.34 KB RN_FB_DEV
ReactNativeRenderer-prod.js 🔺+0.4% 🔺+0.2% 214.82 KB 215.69 KB 37.47 KB 37.56 KB RN_FB_PROD
ReactNativeRenderer-dev.js +0.6% +0.3% 500.75 KB 503.66 KB 110.92 KB 111.26 KB RN_OSS_DEV
ReactNativeRenderer-prod.js 🔺+0.4% 🔺+0.2% 204.64 KB 205.51 KB 35.83 KB 35.92 KB RN_OSS_PROD
ReactFabric-dev.js +0.6% +0.3% 491.29 KB 494.2 KB 108.6 KB 108.93 KB RN_FB_DEV
ReactFabric-prod.js 🔺+0.4% 🔺+0.3% 197.05 KB 197.92 KB 34.36 KB 34.45 KB RN_FB_PROD
ReactFabric-dev.js +0.6% +0.3% 491.32 KB 494.23 KB 108.61 KB 108.94 KB RN_OSS_DEV
ReactFabric-prod.js 🔺+0.4% 🔺+0.3% 197.09 KB 197.96 KB 34.38 KB 34.47 KB RN_OSS_PROD
ReactNativeRenderer-profiling.js +0.4% +0.2% 212.29 KB 213.16 KB 37.32 KB 37.41 KB RN_OSS_PROFILING
ReactFabric-profiling.js +0.4% +0.3% 204.2 KB 205.07 KB 35.88 KB 35.97 KB RN_OSS_PROFILING
ReactNativeRenderer-profiling.js +0.4% +0.2% 220.78 KB 221.65 KB 38.82 KB 38.91 KB RN_FB_PROFILING
ReactFabric-profiling.js +0.4% +0.3% 204.16 KB 205.03 KB 35.87 KB 35.96 KB RN_FB_PROFILING

scheduler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
scheduler.development.js n/a n/a 0 B 19.17 KB 0 B 5.74 KB UMD_DEV
scheduler.production.min.js n/a n/a 0 B 3.16 KB 0 B 1.53 KB UMD_PROD

Generated by 🚫 dangerJS

@gaearon
Copy link
Member

@gaearon gaearon commented Sep 25, 2018

Should we warn if you put contextType on a function? Also have a test that verifies it doesn't accidentally work?

@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Sep 25, 2018

Can we add a test that confirms that the component that receives a context change will still re-render even if sCU returns false (also PureComponent)? I know you said the test passes but we should have it anyway to prevent a regression.

The existing test kind of obviates the need for this by confirming that sCU isn't even called. (I don't think there's a way to actually call sCU with the current implementation.)

Should we warn if you put contextType on a function? Also have a test that verifies it doesn't accidentally work?

Sure!

1. Renamed isContextConsumer to isLegacyContextConsumer.
2. Added DEV warning for unsupported contextType on stateless functional component.
@bvaughn bvaughn merged commit 4b68a64 into facebook:master Sep 25, 2018
1 check passed
1 check passed
ci/circleci Your tests passed on CircleCI!
Details
@bvaughn bvaughn deleted the bvaughn:contextType branch Sep 25, 2018
@acdlite
Copy link
Member

@acdlite acdlite commented Sep 25, 2018

(I don't think there's a way to actually call sCU with the current implementation.)

Yeah but the point is to prevent the implementation from regressing :D

@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Sep 25, 2018

That's fair! #13729

if (
typeof contextType === 'object' &&
contextType !== null &&
typeof contextType.unstable_read === 'function'

This comment has been minimized.

@sebmarkbage

sebmarkbage Sep 26, 2018
Member

Do we need this extra check? Seems like we can just throw.

This comment has been minimized.

@bvaughn

bvaughn Sep 26, 2018
Author Contributor

I guess we don't, since we also provide a nice DEV mode warning if it's not a function. I'll remove this type check.

This comment has been minimized.

@bvaughn

bvaughn Sep 26, 2018
Author Contributor

Actually, if we don't check in at least the instantiation path, we'll throw before our DEV warning. So I think it's best to leave these checks in place after all.

If you feel strongly about it, let's talk and come up with another plan. I think the DEV warnings are useful to preserve though.

Disregard. I'll just move the warning earlier. #13736

@benwiley4000
Copy link

@benwiley4000 benwiley4000 commented Sep 28, 2018

Was this API proposed only for migration purposes? I've been using new Context for awhile now, and it works great in most cases, but starts to feel boilerplately when I need to access context from inside a class method. The above API seems to capture that use case perfectly. I'd love to start using it.

@gaearon
Copy link
Member

@gaearon gaearon commented Sep 28, 2018

Sure you can start using it. Yeah it’s mostly for easier migration but you can use it in either way if you like.

@satya164
Copy link

@satya164 satya164 commented Oct 1, 2018

Was it considered to support consuming multiple context providers with the API?

static contextTypes = {
  foo: FooContext,
  bar: BarContext,
};

this.context.foo
this.context.bar
@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Oct 1, 2018

Yes, it was considered and didn't seem worth it. Consuming multiple contexts is an edge case / advance pattern that we didn't want to add overhead to support with this new API.

acdlite added a commit to plievone/react that referenced this pull request Oct 5, 2018
* Support class component static contextType attribute
@lushc
Copy link

@lushc lushc commented Oct 18, 2018

Hopefully multiple contexts will be reconsidered in the future. I'm not sure if I've been misusing them but in larger apps I tend to have a ThemeContext, UserContext, and some sort of FooContext born out of a separation of concerns. In some components I need access to both user and foo in the lifecycle methods. This is solved by composing HOCs but it'd be nice to forego that in favour of this API 👍

@brunolemos
Copy link

@brunolemos brunolemos commented Oct 18, 2018

Yes please consider supporting multiple contexts. It's not so edge. I just made the exact same suggestion here before reading the comments above: reactjs/reactjs.org#1265 (comment)

@arackaf
Copy link

@arackaf arackaf commented Oct 18, 2018

I, too, would love to see multiple contexts supported. That said, I think a decorator / HoC might make this palatable in userland, relatively simply. I think something like this might work

const addContext = (name, context) => Orig => class extends Component {
  static contextType = context;
  render(){
    return <Orig {...this.props} name={this.context} />
  }
}

And then

@addContext("userContext", userContext)
@addContext("uiContext", uiContext)
class SomeComponent extends Component {
  componentDidUpdate(prevProps, prevState){ 
    let { userContext, uiContext } = this.props;
    //...
  }
  render(){
    return (
      // ...
    )
  }
}

That's of course based on the legacy decorator spec - it'd need to change for the new spec, obviously.

@gaearon
Copy link
Member

@gaearon gaearon commented Oct 24, 2018

@kana-sama @the-spyke If you want to provide feedback on proposals before they're implemented, I suggest you to watch the RFC repo: https://github.com/reactjs/rfcs. This change has already been released. We're not monitoring this thread.

@the-spyke
Copy link

@the-spyke the-spyke commented Oct 24, 2018

We're not monitoring this thread.

@gaearon How did you find out about new messages then? Just kidding. I'm sorry, I know about RFCs, but it's too late too.

@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Oct 24, 2018

@bvaughn, it is doesn't matter, why contextType instead of render prop, because all of them are dirty workarounds of this "edge case" restriction. It is a fighting with react API, I could say.

I don't really know what you're saying here, to be honest. Did I say it doesn't matter somewhere? My "edge case" comment was referring to a single component consuming multiple contexts, not that API in general.

Dan's right that feedback like this is more appropriate for the RFC, although this change in particular has already been released so it's a bit late for us to incorporate most feedback 😄

That being said, to clarify, the new contextType approach is mostly to ease adoption for people migrating away from the old contextTypes + prop-types approach. It's simpler for them to change contextTypes -> contextType (assuming only a single context) than it is to add a wrapper with a render prop and the forwardRef API.

This also avoids the overhead of an additional fiber allocation for the wrapper, but that's a micro-optimization in most cases.

@Mati365
Copy link

@Mati365 Mati365 commented Oct 24, 2018

@gaearon contextType can be accessed in constructor of component?

@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Oct 24, 2018

@Mati365 contextType is a static property of class components. I assume you meant to ask if the context param will be passed to the constructor for components that use contextType– in which case, the answer is yes. We have a unit test for that here if you're curious.

@shijistar
Copy link

@shijistar shijistar commented Oct 27, 2018

@bvaughn How to let the component be re-rendered in case static contextType changes? I don't see any sample like render prop API dynamic-context

@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Oct 27, 2018

The component will automatically re-render if its context value changes. 👍

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

Successfully merging this pull request may close these issues.

None yet