Skip to content

Commit

Permalink
Document class contextType as the primary consuming mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
sebmarkbage committed Oct 16, 2018
1 parent 2ff1f9f commit d7b724e
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 169 deletions.
123 changes: 65 additions & 58 deletions content/docs/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ In a typical React application, data is passed top-down (parent to child) via pr
- [Before You Use Context](#before-you-use-context)
- [API](#api)
- [React.createContext](#reactcreatecontext)
- [Provider](#provider)
- [Consumer](#consumer)
- [Context.Provider](#contextprovider)
- [Class.contextType](#classcontexttype)
- [Context.Consumer](#contextconsumer)
- [Examples](#examples)
- [Dynamic Context](#dynamic-context)
- [Updating Context from a Nested Component](#updating-context-from-a-nested-component)
- [Consuming Multiple Contexts](#consuming-multiple-contexts)
- [Accessing Context in Lifecycle Methods](#accessing-context-in-lifecycle-methods)
- [Consuming Context with a HOC](#consuming-context-with-a-hoc)
- [Forwarding Refs to Context Consumers](#forwarding-refs-to-context-consumers)
- [Caveats](#caveats)
- [Legacy API](#legacy-api)

Expand Down Expand Up @@ -114,46 +112,89 @@ However, sometimes the same data needs to be accessible by many components in th
### `React.createContext`

```js
const {Provider, Consumer} = React.createContext(defaultValue);
const MyContext = React.createContext(defaultValue);
```

Creates a `{ Provider, Consumer }` pair. When React renders a context `Consumer`, it will read the current context value from the closest matching `Provider` above it in the tree.
Creates an Context object. When React renders a component that subscribes to this Context object it will read the current context value from the closest matching `Provider` above it in the tree.

The `defaultValue` argument is **only** used by a Consumer when it does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing `undefined` as a Provider value does not cause Consumers to use `defaultValue`.
The `defaultValue` argument is **only** used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing `undefined` as a Provider value does not cause consuming components to use `defaultValue`.

### `Provider`
### `Context.Provider`

```js
<Provider value={/* some value */}>
<MyContext.Provider value={/* some value */}>
```

A React component that allows Consumers to subscribe to context changes.
Every Context object comes with a Provider React component that allows consuming components to subscribe to context changes.

Accepts a `value` prop to be passed to Consumers that are descendants of this Provider. One Provider can be connected to many Consumers. Providers can be nested to override values deeper within the tree.
Accepts a `value` prop to be passed to consuming components that are descendants of this Provider. One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree.

### `Consumer`
All consumers that are descendants of a Provider will re-render whenever the Provider's `value` prop changes. The propagation from Provider to its descendant consumers is not subject to the `shouldComponentUpdate` method, so the consumer is updated even when an ancestor component bails out of the update.

Changes are determined by comparing the new and old values using the same algorithm as [`Object.is`](//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description).

> Note
>
> The way changes are determined can cause some issues when passing objects as `value`: see [Caveats](#caveats).
### `Class.contextType`

```js
<Consumer>
{value => /* render something based on the context value */}
</Consumer>
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render soemthing based on the value of MyContext */
}
}
MyClass.contextType = MyContext;
```

A React component that subscribes to context changes.
The `contextType` property on a class can be assigned a Context object created by [`React.createContext()`](#reactcreatecontext). This lets you consume the nearest current value of that Context type using `this.context`. You can reference this in any of the life-cycles including the render function.

Requires a [function as a child](/docs/render-props.html#using-props-other-than-render). The function receives the current context value and returns a React node. The `value` argument passed to the function will be equal to the `value` prop of the closest Provider for this context above in the tree. If there is no Provider for this context above, the `value` argument will be equal to the `defaultValue` that was passed to `createContext()`.
> Note:
>
> You can only subscribe to a single context using this API. If you need to read more than one see [Consuming Multiple Contexts](#consuming-multiple-contexts).
>
> If you are using the experimental [public class fields syntax](https://babeljs.io/docs/plugins/transform-class-properties/), you can use a **static** class field to initialize your `contextType`.
> Note
>
> For more information about the 'function as a child' pattern, see [render props](/docs/render-props.html).

All Consumers that are descendants of a Provider will re-render whenever the Provider's `value` prop changes. The propagation from Provider to its descendant Consumers is not subject to the `shouldComponentUpdate` method, so the Consumer is updated even when an ancestor component bails out of the update.
```js
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* render soemthing based on the value */
}
}
```

Changes are determined by comparing the new and old values using the same algorithm as [`Object.is`](//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description).
### `Context.Consumer`

```js
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
```

A React component that subscribes to context changes. This lets you subscribe to a context within a [function component](/docs/components-and-props.html#function-and-class-components).

Requires a [function as a child](/docs/render-props.html#using-props-other-than-render). The function receives the current context value and returns a React node. The `value` argument passed to the function will be equal to the `value` prop of the closest Provider for this context above in the tree. If there is no Provider for this context above, the `value` argument will be equal to the `defaultValue` that was passed to `createContext()`.

> Note
>
> The way changes are determined can cause some issues when passing objects as `value`: see [Caveats](#caveats).
> For more information about the 'function as a child' pattern, see [render props](/docs/render-props.html).
## Examples

Expand Down Expand Up @@ -191,40 +232,6 @@ To keep context re-rendering fast, React needs to make each context consumer a s

If two or more context values are often used together, you might want to consider creating your own render prop component that provides both.

### Accessing Context in Lifecycle Methods

Accessing values from context in lifecycle methods is a relatively common use case. Instead of adding context to every lifecycle method, you just need to pass it as a prop, and then work with it just like you'd normally work with a prop.

`embed:context/lifecycles.js`

### Consuming Context with a HOC

Some types of contexts are consumed by many components (e.g. theme or localization). It can be tedious to explicitly wrap each dependency with a `<Context.Consumer>` element. A [higher-order component](/docs/higher-order-components.html) can help with this.

For example, a button component might consume a theme context like so:

`embed:context/higher-order-component-before.js`

That's alright for a few components, but what if we wanted to use the theme context in a lot of places?

We could create a higher-order component called `withTheme`:

`embed:context/higher-order-component.js`

Now any component that depends on the theme context can easily subscribe to it using the `withTheme` function we've created:

`embed:context/higher-order-component-usage.js`

### Forwarding Refs to Context Consumers

One issue with the render prop API is that refs don't automatically get passed to wrapped elements. To get around this, use `React.forwardRef`:

**fancy-button.js**
`embed:context/forwarding-refs-fancy-button.js`

**app.js**
`embed:context/forwarding-refs-app.js`

## Caveats

Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider's parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for `value`:
Expand Down
11 changes: 0 additions & 11 deletions examples/context/forwarding-refs-app.js

This file was deleted.

18 changes: 0 additions & 18 deletions examples/context/forwarding-refs-fancy-button.js

This file was deleted.

10 changes: 0 additions & 10 deletions examples/context/higher-order-component-before.js

This file was deleted.

5 changes: 0 additions & 5 deletions examples/context/higher-order-component-usage.js

This file was deleted.

18 changes: 0 additions & 18 deletions examples/context/higher-order-component.js

This file was deleted.

28 changes: 0 additions & 28 deletions examples/context/lifecycles.js

This file was deleted.

6 changes: 4 additions & 2 deletions examples/context/motivation-problem.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ function Toolbar(props) {
);
}

function ThemedButton(props) {
return <Button theme={props.theme} />;
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
13 changes: 6 additions & 7 deletions examples/context/motivation-solution.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,13 @@ function Toolbar(props) {
);
}

function ThemedButton(props) {
class ThemedButton extends React.Component {
// highlight-range{1-3,6}
// Use a Consumer to read the current theme context.
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
25 changes: 13 additions & 12 deletions examples/context/theme-detailed-themed-button.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import {ThemeContext} from './theme-context';

function ThemedButton(props) {
// highlight-range{2-9}
return (
<ThemeContext.Consumer>
{theme => (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
)}
</ThemeContext.Consumer>
);
class ThemedButton extends React.Component {
// highlight-range{3,12}
render() {
let props = this.props;
let theme = this.context;
return (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
);
}
}
ThemedButton.contextType = ThemeContext;

export default ThemedButton;

0 comments on commit d7b724e

Please sign in to comment.