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

contextType: convenience API for reading context in a class #65

Merged
merged 1 commit into from
Oct 23, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions text/0000-contexttype.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
Start Date: 2018-10-19
- RFC PR: (leave this empty)
- React Issue: (leave this empty)

# Summary

`contextType` is a convenience API for accessing the React [context](https://reactjs.org/docs/context.html) from class components. It works similar to `Context.Consumer` but lets you handle the common case where you only need a single context more ergonomically.

It **does not replace** `Context.Consumer`. It is a shortcut for the most common case that helps reduce component tree nesting.

# Basic example

```js
// Works with new context API
const ThemeContext = React.createContext('dark');

class Button extends React.Component {
static contextType = ThemeContext; // Alternative to using Consumer

componentDidMount() {
console.log(this.context); // You can read it in lifecycles
}

render() {
const theme = this.context; // And in render
return <div className={theme} />;
}
}
```

This component subscribes to context similar to `ThemeContext.Consumer`, but lets you avoid an extra nesting layer when you want to access context from a lifecycle method.

# Motivation

The new context API introduced in 16.3 is a low-level primitive. It was designed to work the same way both with class components and with function components, and it solves name clashes and other issues with the legacy context API.

However, the legacy context API is still more ergonomic for the case when you need to access context in a lifecycle method. With the render prop context API, you can still do it but it is awkward:

```js
class ThemedButton extends React.Component {
componentDidMount() {
console.log(this.props.theme);
}

render() {
return <div className={this.props.theme} />;
}
}

const Button = React.forwardRef((props, ref) => (
<ThemeContext.Consumer>
{theme => <ThemedButton {...props} theme={theme} ref={ref} />
</ThemeContext.Consumer>
);
```

This particular case (when we need to access a single context from lifecycle methods) is sufficiently common compared to other cases that it's worth building a convenience API for. It is also valuable for some [interop](https://github.com/facebook/react/issues/13336) scenarios.

Importantly, this API is designed to speed up the adoption of the new context. Supporting legacy context makes React larger and slower, and we see the need for more pragmatic solutions to help people move away for it.

>Note
>
>This isn't the first "convenience API" in React. It is similar to how React has a convenience `createRef` API even though a more powerful callback ref API exists. The goal is to help simplify the 80% case with a more direct API, at the cost of some redundancy.

# Detailed design

When a class component has `contextType` defined, it is assumed to be a Context object (return value of `React.createContext`). React flags this component as a context consumer, and provides the current value of this context as `this.context`. It is available both in the render phase and in the lifecycle methods.

## Why Didn't We Do This Way Back?

Arguably this could have been a part of the initial `React.createContext` proposal. We'd still, however, need a low-level API for function components or combining multiple contexts. So this couldn't be the only API. At the time `React.createContext` was proposed, we were hoping to be able to make more targeted optimizations to context propagation. However, as we worked on Suspense, we realized that we need the capability to read context without an explicit `Consumer` render prop anyway — otherwise accessing a Suspense cache is too cumbersome. Since this makes the original performance optimization justification weaker, and we might as well add a convenience API if we can't take advantage of the low-level API constraints.

This API proposal is intentionally limited and **doesn't** support multiple contexts directly. It is only a shortcut for the common case. If you need to read several contexts (or read it from function components), you can use the existing API.

Choose a reason for hiding this comment

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

So excited for this proposal! Can you explain why this limit is here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Static typing and performance reasons.


# Drawbacks

* Adds another way to do the same thing. (But without the nesting.)
* Might be confused with the legacy API due to the similarity.
* Doesn't let you use multiple contexts without a render prop.
* Code using this API is more hassle to move into a function component.

# Alternatives

* Don't do anything (status quo). This significantly slows down the adoption of new context.
Copy link
Member

@aweary aweary Oct 19, 2018

Choose a reason for hiding this comment

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

What are the specific problems with the new context that have slowed adoption, and that this solves? Accessing context in lifecycles?

Copy link
Member Author

Choose a reason for hiding this comment

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

Accessing context in lifecycles and the need to forward refs to do that. It's awkward.

* Call the field something different, like `contextConsumerType`.
Copy link
Member

Choose a reason for hiding this comment

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

I wonder how many people will try and use PropTypes to define contextType thanks to contextTypes. Maybe it should use a name that is even further from contextTypes so that there's no risk of confusing them?

Copy link
Member Author

Choose a reason for hiding this comment

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

¯\(ツ)

Copy link
Member Author

Choose a reason for hiding this comment

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

We warn immediately if you pass something else.

* Support using multiple contexts from one class component.

# Adoption strategy

You don't need to use it, but it's a nicer migration path from the legacy context.

# How we teach this

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