Skip to content

Commit 3af89c9

Browse files
authored
Merge pull request #4 from alexkrolick/context-tweaks
Context tweaks
2 parents 2f9b77a + ccc1f26 commit 3af89c9

10 files changed

+170
-146
lines changed

content/docs/context.md

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,32 @@ Context provides a way to pass data through the component tree without having to
88

99
In a typical React application, data is passed top-down (parent to child) via props, but this can be cumbersome for certain types of props (e.g. locale preference, UI theme) that are required by many components within an application. Context provides a way to share values like this between components without having to explicitly pass a prop through every level of the tree.
1010

11-
- [Motivation](#motivation)
11+
- [When to Use Context](#when-to-use-context)
1212
- [API](#api)
1313
- [React.createContext](#reactcreatecontext)
1414
- [Provider](#provider)
1515
- [Consumer](#consumer)
1616
- [Examples](#examples)
1717
- [Static Context](#static-context)
1818
- [Dynamic Context](#dynamic-context)
19+
- [Consuming Multiple Contexts](#consuming-multiple-contexts)
20+
- [Accessing Context in Lifecycle Methods](#accessing-context-in-lifecycle-methods)
21+
- [Forwarding Refs to Context Consumers](#forwarding-refs-to-context-consumers)
1922
- [Legacy API](#legacy-api)
2023

2124

22-
## Motivation
25+
## When to Use Context
2326

24-
Context is designed to relieve the pain of passing props down through a deeply nested component tree. For example, in the code below we manually thread through a color prop in order to style the Button and Message components:
27+
Context is designed to share data that can be considered "global" for a tree of React components, such as the current authenticated user, theme, or preferred language. For example, in the code below we manually thread through a "theme" prop in order to style the Button component:
2528

2629
`embed:context/motivation-problem.js`
2730

2831
Using context, we can avoid passing props through intermediate elements:
2932

3033
`embed:context/motivation-solution.js`
3134

35+
Note: Don't use context just to avoid passing props a few levels down. Stick to cases where the same data needs to accessed in many components at multiple levels.
36+
3237
## API
3338

3439
### `React.createContext`
@@ -55,26 +60,20 @@ Accepts a `value` prop to be passed to Consumers that are descendants of this Pr
5560

5661
```js
5762
<Consumer>
58-
{ value => { /* render something based on the context value */ } }
63+
{value => /* render something based on the context value */}
5964
</Consumer>
6065
```
6166

6267
A React component that subscribes to context changes.
6368

64-
Requires a [function as a child](/docs/render-props.html#using-props-other-than-render). This function receives the current context value and returns a React node. It will be called whenever the Provider's value is updated.
69+
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. All consumers are re-rendered whenever the Provider value changes. Changes are determined by comparing the new and old values using [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).
6570

6671
> Note:
6772
>
6873
> For more information about this pattern, see [render props](/docs/render-props.html).
6974
7075
## Examples
7176

72-
### Static Context
73-
74-
Here is an example illustrating how you might inject a "theme" using context:
75-
76-
`embed:context/theme-example.js`
77-
7877
### Dynamic Context
7978

8079
A more complex example with dynamic values for the theme:
@@ -88,8 +87,21 @@ A more complex example with dynamic values for the theme:
8887
**app.js**
8988
`embed:context/theme-detailed-app.js`
9089

90+
## Consuming Multiple Contexts
91+
92+
`embed:context/multiple-contexts.js`
93+
94+
## Accessing Context in Lifecycle Methods
95+
96+
`embed:context/lifecycles.js`
97+
98+
## Forwarding Refs to Context Consumers
99+
100+
`embed:context/forwarding-refs.js`
101+
91102
## Legacy API
92103

93-
> The legacy context API was deprecated in React 16.3 and will be removed in version 17.
104+
> Note:
94105
>
95-
> React previously shipped with an experimental context API. The old API will be supported in all 16.x releases, but applications using it should migrate to the new version. Read the [legacy context docs here](/docs/legacy-context.html).
106+
> React previously shipped with an experimental context API. The old API will be supported in all 16.x releases, but applications using it should migrate to the new version. The legacy API will be removed in React 17. Read the [legacy context docs here](/docs/legacy-context.html).
107+

content/docs/legacy-context.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ title: Legacy Context
44
permalink: docs/legacy-context.html
55
---
66

7-
> Deprecation Warning
7+
> Note:
88
>
9-
> The legacy API has been deprecated and will be removed in version 17.
9+
> The legacy context API will be removed in version 17.
1010
> Use the [new context API](/docs/context.html) introduced with version 16.3.
1111
> The legacy API will continue working for all 16.x releases.
1212
1313
## How To Use Context
1414

15-
> This section documents a deprecated API. See the [new API](/docs/context.html).
15+
> This section documents a legacy API. See the [new API](/docs/context.html).
1616
1717
Suppose you have a structure like:
1818

@@ -107,7 +107,7 @@ If `contextTypes` is not defined, then `context` will be an empty object.
107107
108108
### Parent-Child Coupling
109109

110-
> This section documents a deprecated API. See the [new API](/docs/context.html).
110+
> This section documents a legacy API. See the [new API](/docs/context.html).
111111
112112
Context can also let you build an API where parents and children communicate. For example, one library that works this way is [React Router V4](https://reacttraining.com/react-router):
113113

@@ -139,7 +139,7 @@ Before you build components with an API similar to this, consider if there are c
139139

140140
### Referencing Context in Lifecycle Methods
141141

142-
> This section documents a deprecated API. See the [new API](/docs/context.html).
142+
> This section documents a legacy API. See the [new API](/docs/context.html).
143143
144144
If `contextTypes` is defined within a component, the following [lifecycle methods](/docs/react-component.html#the-component-lifecycle) will receive an additional parameter, the `context` object:
145145

@@ -154,7 +154,7 @@ If `contextTypes` is defined within a component, the following [lifecycle method
154154
155155
### Referencing Context in Stateless Functional Components
156156

157-
> This section documents a deprecated API. See the [new API](/docs/context.html).
157+
> This section documents a legacy API. See the [new API](/docs/context.html).
158158
159159
Stateless functional components are also able to reference `context` if `contextTypes` is defined as a property of the function. The following code shows a `Button` component written as a stateless functional component.
160160

@@ -171,7 +171,7 @@ Button.contextTypes = {color: PropTypes.string};
171171

172172
### Updating Context
173173

174-
> This section documents a deprecated API. See the [new API](/docs/context.html).
174+
> This section documents a legacy API. See the [new API](/docs/context.html).
175175
176176
Don't do it.
177177

examples/context/forwarding-refs.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const Button = ({theme, children}) => (
2+
<button className={theme ? 'dark' : 'light'}>
3+
{children}
4+
</button>
5+
);
6+
7+
// highlight-range{1,3}
8+
export default React.forwardRef((props, ref) => (
9+
<ThemeContext.Consumer>
10+
{theme => <Button {...props} theme={theme} ref={ref} />}
11+
</ThemeContext.Consumer>
12+
));

examples/context/lifecycles.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class Button extends React.Component {
2+
componentDidMount() {
3+
// highlight-next-line
4+
alert(this.props.theme);
5+
}
6+
7+
render() {
8+
const {theme, children} = this.props;
9+
return (
10+
<button className={theme ? 'dark' : 'light'}>
11+
{children}
12+
</button>
13+
);
14+
}
15+
}
16+
17+
// highlight-range{3}
18+
export default props => (
19+
<ThemeContext.Consumer>
20+
{theme => <Button {...props} theme={theme} />}
21+
</ThemeContext.Consumer>
22+
);
Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,23 @@
1-
class Button extends React.Component {
2-
render() {
3-
return (
4-
<button style={{background: this.props.color}}>
5-
{this.props.children}
6-
</button>
7-
);
8-
}
9-
}
1+
const ThemedButton = props => {
2+
//highlight-range{1}
3+
return <Button theme={props.theme} />;
4+
};
105

11-
class Message extends React.Component {
12-
render() {
13-
// highlight-range{1-3}
14-
// The Message component must take `color` as as prop to pass it
15-
// to the Button. Using context, the Button could connect to the
16-
// color context on its own.
17-
return (
18-
<div>
19-
<p>{this.props.text}</p>
20-
<Button color={this.props.color}>Delete</Button>
21-
</div>
22-
);
23-
}
24-
}
6+
// An intermediate component
7+
const Toolbar = props => {
8+
// highlight-range{1-2,5}
9+
// The Toolbar component must take an extra theme prop
10+
// and pass it to the ThemedButton
11+
return (
12+
<div>
13+
<ThemedButton theme={props.theme} />
14+
</div>
15+
);
16+
};
2517

26-
class MessageList extends React.Component {
18+
class App extends React.Component {
2719
render() {
28-
const color = 'purple';
29-
const children = this.props.messages.map(message => (
30-
<Message text={message.text} color={color} />
31-
));
32-
return <div>{children}</div>;
20+
// highlight-range{1}
21+
return <Toolbar theme="dark" />;
3322
}
3423
}
Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,33 @@
1-
// highlight-range{1}
2-
const ColorContext = React.createContext();
1+
// Create a theme context, defaulting to light theme
2+
// highlight-next-line
3+
const ThemeContext = React.createContext('light');
34

4-
class Button extends React.Component {
5-
render() {
6-
// highlight-range{2-8}
7-
return (
8-
<ColorContext.Consumer>
9-
{color => (
10-
<button style={{background: color}}>
11-
{this.props.children}
12-
</button>
13-
)}
14-
</ColorContext.Consumer>
15-
);
16-
}
17-
}
5+
const ThemedButton = props => {
6+
// highlight-range{1,3-5}
7+
// The ThemedButton receives the theme from context
8+
return (
9+
<ThemeContext.Consumer>
10+
{theme => <Button theme={theme} />}
11+
</ThemeContext.Consumer>
12+
);
13+
};
1814

19-
class Message extends React.Component {
20-
render() {
21-
return (
22-
<div>
23-
<p>{this.props.text}</p>
24-
<Button>Delete</Button>
25-
</div>
26-
);
27-
}
28-
}
15+
// An intermediate component
16+
const Toolbar = props => {
17+
return (
18+
<div>
19+
<ThemedButton />
20+
</div>
21+
);
22+
};
2923

30-
class MessageList extends React.Component {
24+
class App extends React.Component {
3125
render() {
32-
const color = 'purple';
33-
const children = this.props.messages.map(message => (
34-
<Message text={message.text} />
35-
));
36-
// highlight-range{2-4}
26+
// highlight-range{2,4}
3727
return (
38-
<ColorContext.Provider value={color}>
39-
{children}
40-
</ColorContext.Provider>
28+
<ThemeContext.Provider value="dark">
29+
<Toolbar />
30+
</ThemeContext.Provider>
4131
);
4232
}
4333
}

examples/context/multiple-contexts.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Theme context, default to light theme
2+
// highlight-next-line
3+
const ThemeContext = React.createContext('light');
4+
5+
// Signed-in user context
6+
// highlight-next-line
7+
const UserContext = React.createContext();
8+
9+
class App extends React.Component {
10+
static propTypes = {
11+
theme: PropTypes.string,
12+
signedInUser: PropTypes.shape({
13+
id: number,
14+
name: string,
15+
avatar: string,
16+
}),
17+
};
18+
19+
render() {
20+
// highlight-range{9}
21+
return (
22+
<ThemeContext.Provider value={this.props.theme}>
23+
<UserContext.Provider
24+
value={this.props.signedInUser}>
25+
<ThemeContext.Consumer>
26+
{theme => (
27+
<UserContext.Consumer>
28+
{user => (
29+
<ProfilePage user={user} theme={theme} />
30+
)}
31+
</UserContext.Consumer>
32+
)}
33+
</ThemeContext.Consumer>
34+
</UserContext.Provider>
35+
</ThemeContext.Provider>
36+
);
37+
}
38+
}

examples/context/theme-detailed-app.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import {ThemeContext, themes} from './theme-context';
22
import ThemedButton from './button';
33

4+
// An intermediate component that uses the ThemedButton
5+
const Toolbar = props => {
6+
return (
7+
<ThemedButton onClick={props.changeTheme}>
8+
Change Theme
9+
</ThemedButton>
10+
);
11+
};
12+
413
class App extends React.Component {
514
state = {
615
theme: themes.light,
@@ -16,13 +25,20 @@ class App extends React.Component {
1625
};
1726

1827
render() {
19-
//highlight-range{2,6}
28+
//highlight-range{1-3}
29+
// The ThemedButton button inside the ThemeProvider
30+
// uses the theme from state while the one outside uses
31+
// the default dark theme
32+
//highlight-range{3-5,7}
2033
return (
21-
<ThemeContext.Provider value={this.state.theme}>
22-
<ThemedButton onClick={this.toggleTheme}>
23-
Change Theme
24-
</ThemedButton>
25-
</ThemeContext.Provider>
34+
<div>
35+
<ThemeContext.Provider value={this.state.theme}>
36+
<Toolbar changeTheme={this.toggleTheme} />
37+
</ThemeContext.Provider>
38+
<div>
39+
<ThemedButton />
40+
</div>
41+
</div>
2642
);
2743
}
2844
}

examples/context/theme-detailed-theme-context.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ export const themes = {
1111

1212
// highlight-range{1-3}
1313
export const ThemeContext = React.createContext(
14-
themes.dark
14+
themes.dark // default value
1515
);

0 commit comments

Comments
 (0)