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

Projects
None yet
@bvaughn
Contributor

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?

@acdlite

acdlite approved these changes Sep 25, 2018 edited

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

This comment has been minimized.

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

This comment has been minimized.

Member

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

This comment has been minimized.

Contributor

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!

PR feedback:
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

ci/circleci Your tests passed on CircleCI!
Details

@bvaughn bvaughn deleted the bvaughn:contextType branch Sep 25, 2018

@acdlite

This comment has been minimized.

Member

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

This comment has been minimized.

Contributor

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

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

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

This comment has been minimized.

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

This comment has been minimized.

Member

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

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 (facebook#13728)
* Support class component static contextType attribute
@lushc

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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.

@bvaughn

This comment has been minimized.

Contributor

bvaughn commented Oct 19, 2018

I think we're still misunderstanding each other. 😄

I'm asking why you would use this form:

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

Instead of something based on the render prop API:

const addContext = (propName, Context) => Orig => props => (
  <Context.Consumer>
    {value => <Orig {...props} {...{ [propName]: value }} />}
  </Context.Consumer>
);
@arackaf

This comment has been minimized.

arackaf commented Oct 19, 2018

Oh sorry - yeah, that's much nicer. Completely. Thanks for clarifying!

@bvaughn

This comment has been minimized.

Contributor

bvaughn commented Oct 19, 2018

Cool, no problem! I was just wondering if I was missing something 😄

@NE-SmallTown

This comment has been minimized.

Contributor

NE-SmallTown commented Oct 20, 2018

I think maybe one problem is HOC is not intrusive but render prop is

@gaearon gaearon referenced this pull request Oct 23, 2018

Merged

Add 16.6.0 changelog #13927

@kana-sama

This comment has been minimized.

kana-sama 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.

@the-spyke

This comment has been minimized.

the-spyke commented Oct 24, 2018

To me it looks rather strange, when you assing a value which is not a context or contextType. Why not:

import { DataConsumer } from "../data_context.js";

class MyComp extends React.Component {
  static contextConsumer = DataConsumer;
...
@gaearon

This comment has been minimized.

Member

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

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

This comment has been minimized.

Mati365 commented Oct 24, 2018

@gaearon contextType can be accessed in constructor of component?

@bvaughn

This comment has been minimized.

Contributor

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

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