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.

const context = isContextConsumer
? getMaskedContext(workInProgress, unmaskedContext)
: emptyContextObject;
let isContextConsumer = false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name this isLegacyContextConsumer to be more specific?

Copy link
Collaborator

@acdlite acdlite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 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
Collaborator

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 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
@bvaughn bvaughn deleted the contextType branch September 25, 2018 22:49
@acdlite
Copy link
Collaborator

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 commented Sep 25, 2018

That's fair! #13729

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

@bvaughn bvaughn Sep 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

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
Collaborator

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 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 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 pushed a commit to plievone/react that referenced this pull request Oct 5, 2018
* Support class component static contextType attribute
@lushc
Copy link

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

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/react.dev#1265 (comment)

@arackaf
Copy link

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
Copy link
Contributor Author

bvaughn commented Oct 19, 2018

I'm curious why you'd use a class component for the decorator rather than the render prop API @arackaf?

@Mati365
Copy link

Mati365 commented Oct 24, 2018

@gaearon contextType can be accessed in constructor of component?

@bvaughn
Copy link
Contributor Author

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 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 commented Oct 27, 2018

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

jetoneza pushed a commit to jetoneza/react that referenced this pull request Jan 23, 2019
* Support class component static contextType attribute
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.