Browse Source

Document class contextType as the primary consuming mechanism (#1283)

* Document class contextType as the primary consuming mechanism

* Update context.md
main
Sebastian Markbåge 6 years ago
committed by Dan Abramov
parent
commit
896214b7d2
  1. 123
      content/docs/context.md
  2. 11
      examples/context/forwarding-refs-app.js
  3. 18
      examples/context/forwarding-refs-fancy-button.js
  4. 10
      examples/context/higher-order-component-before.js
  5. 5
      examples/context/higher-order-component-usage.js
  6. 18
      examples/context/higher-order-component.js
  7. 28
      examples/context/lifecycles.js
  8. 6
      examples/context/motivation-problem.js
  9. 13
      examples/context/motivation-solution.js
  10. 25
      examples/context/theme-detailed-themed-button.js

123
content/docs/context.md

@ -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)
@ -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 something 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 lifecycle methods 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 something 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
@ -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`:

11
examples/context/forwarding-refs-app.js

@ -1,11 +0,0 @@
import FancyButton from './fancy-button';
const ref = React.createRef();
// Our ref will point to the FancyButton component,
// And not the ThemeContext.Consumer that wraps it.
// This means we can call FancyButton methods like ref.current.focus()
// highlight-next-line
<FancyButton ref={ref} onClick={handleClick}>
Click me!
</FancyButton>;

18
examples/context/forwarding-refs-fancy-button.js

@ -1,18 +0,0 @@
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Use context to pass the current "theme" to FancyButton.
// Use forwardRef to pass refs to FancyButton as well.
// highlight-range{1,4}
export default React.forwardRef((props, ref) => (
<ThemeContext.Consumer>
{theme => (
<FancyButton {...props} theme={theme} ref={ref} />
)}
</ThemeContext.Consumer>
));

10
examples/context/higher-order-component-before.js

@ -1,10 +0,0 @@
const ThemeContext = React.createContext('light');
function ThemedButton(props) {
// highlight-range{2-4}
return (
<ThemeContext.Consumer>
{theme => <button className={theme} {...props} />}
</ThemeContext.Consumer>
);
}

5
examples/context/higher-order-component-usage.js

@ -1,5 +0,0 @@
function Button({theme, ...rest}) {
return <button className={theme} {...rest} />;
}
const ThemedButton = withTheme(Button);

18
examples/context/higher-order-component.js

@ -1,18 +0,0 @@
const ThemeContext = React.createContext('light');
// This function takes a component...
// highlight-next-line
export function withTheme(Component) {
// ...and returns another component...
// highlight-next-line
return function ThemedComponent(props) {
// ... and renders the wrapped component with the context theme!
// Notice that we pass through any additional props as well
// highlight-range{2-4}
return (
<ThemeContext.Consumer>
{theme => <Component {...props} theme={theme} />}
</ThemeContext.Consumer>
);
};
}

28
examples/context/lifecycles.js

@ -1,28 +0,0 @@
class Button extends React.Component {
componentDidMount() {
// highlight-next-line
// ThemeContext value is this.props.theme
}
componentDidUpdate(prevProps, prevState) {
// highlight-range{1-2}
// Previous ThemeContext value is prevProps.theme
// New ThemeContext value is this.props.theme
}
render() {
const {theme, children} = this.props;
return (
<button className={theme || 'light'}>
{children}
</button>
);
}
}
// highlight-range{3}
export default props => (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);

6
examples/context/motivation-problem.js

@ -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
examples/context/motivation-solution.js

@ -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
examples/context/theme-detailed-themed-button.js

@ -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;

Loading…
Cancel
Save