Skip to content

Context updates #5

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

Merged
30 changes: 25 additions & 5 deletions content/docs/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ In a typical React application, data is passed top-down (parent to child) via pr
- [Consuming Multiple Contexts](#consuming-multiple-contexts)
- [Accessing Context in Lifecycle Methods](#accessing-context-in-lifecycle-methods)
- [Forwarding Refs to Context Consumers](#forwarding-refs-to-context-consumers)
- [Caveats](#caveats)
- [Legacy API](#legacy-api)


Expand Down Expand Up @@ -68,7 +69,7 @@ Accepts a `value` prop to be passed to Consumers that are descendants of this Pr

A React component that subscribes to context changes.

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).
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 the same algorithm as [`Object.is`](developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description). (This can cause some issues when passing objects as `value`: see [Caveats](#caveats).)

> Note
>
Expand All @@ -89,25 +90,44 @@ A more complex example with dynamic values for the theme:
**app.js**
`embed:context/theme-detailed-app.js`

## Consuming Multiple Contexts
### Consuming Multiple Contexts

To keep context re-rendering fast, React needs to make each context consumer a separate node in the tree.

`embed:context/multiple-contexts.js`

## Accessing Context in Lifecycle Methods
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`

## Forwarding Refs to Context Consumers
### 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`:

`embed:context/reference-caveats-problem.js`


To get around this, lift the value into the parent's state:

`embed:context/reference-caveats-solution.js`

## Legacy API

> Note
>
> 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).
> 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 a future major React version. Read the [legacy context docs here](/docs/legacy-context.html).

4 changes: 2 additions & 2 deletions content/docs/legacy-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ permalink: docs/legacy-context.html

> Note:
>
> The legacy context API will be removed in version 17.
> The legacy context API will be removed in a future major version.
Copy link
Collaborator

@acdlite acdlite Mar 23, 2018

Choose a reason for hiding this comment

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

Could we move the entire "legacy context" section to its own document and link to it here? I think that would reduce the potential for confusion.

Copy link
Author

@alexkrolick alexkrolick Mar 23, 2018

Choose a reason for hiding this comment

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

Isn't this the legacy context document? (legacy-context.md)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh lol, my bad :D

> Use the [new context API](/docs/context.html) introduced with version 16.3.
> The legacy API will continue working for all 16.x releases.

Expand Down Expand Up @@ -214,4 +214,4 @@ MediaQuery.childContextTypes = {
};
```

The problem is, if a context value provided by component changes, descendants that use that value won't update if an intermediate parent returns `false` from `shouldComponentUpdate`. This is totally out of control of the components using context, so there's basically no way to reliably update the context. [This blog post](https://medium.com/@mweststrate/how-to-safely-use-react-context-b7e343eff076) has a good explanation of why this is a problem and how you might get around it.
The problem is, if a context value provided by component changes, descendants that use that value won't update if an intermediate parent returns `false` from `shouldComponentUpdate`. This is totally out of control of the components using context, so there's basically no way to reliably update the context. [This blog post](https://medium.com/@mweststrate/how-to-safely-use-react-context-b7e343eff076) has a good explanation of why this is a problem and how you might get around it.
8 changes: 4 additions & 4 deletions examples/context/motivation-problem.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const ThemedButton = props => {
function ThemedButton(props) {
//highlight-range{1}
return <Button theme={props.theme} />;
};
}

// An intermediate component
const Toolbar = props => {
function Toolbar(props) {
// highlight-range{1-2,5}
// The Toolbar component must take an extra theme prop
// and pass it to the ThemedButton
Expand All @@ -13,7 +13,7 @@ const Toolbar = props => {
<ThemedButton theme={props.theme} />
</div>
);
};
}

class App extends React.Component {
render() {
Expand Down
10 changes: 5 additions & 5 deletions examples/context/motivation-solution.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
// highlight-next-line
const ThemeContext = React.createContext('light');

const ThemedButton = props => {
function ThemedButton(props) {
// highlight-range{1,3-5}
// The ThemedButton receives the theme from context
return (
<ThemeContext.Consumer>
{theme => <Button theme={theme} />}
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
};
}

// An intermediate component
const Toolbar = props => {
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
};
}

class App extends React.Component {
render() {
Expand Down
12 changes: 2 additions & 10 deletions examples/context/multiple-contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const ThemeContext = React.createContext('light');
const UserContext = React.createContext();

// An intermediate component that depends on both contexts
const Toolbar = props => {
function Toolbar(props) {
// highlight-range{2-10}
return (
<ThemeContext.Consumer>
Expand All @@ -20,17 +20,9 @@ const Toolbar = props => {
)}
</ThemeContext.Consumer>
);
};
}

class App extends React.Component {
static propTypes = {
theme: PropTypes.string,
signedInUser: PropTypes.shape({
id: number,
name: string,
}),
};

render() {
const {signedInUser, theme} = this.props;

Expand Down
10 changes: 10 additions & 0 deletions examples/context/reference-caveats-problem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class App extends React.Component {
render() {
// highlight-range{2}
return (
<Provider value={{something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
17 changes: 17 additions & 0 deletions examples/context/reference-caveats-solution.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class App extends React.Component {
constructor(props) {
// highlight-range{2}
this.state = {
value: {something: 'something'},
};
}

render() {
// highlight-range{2}
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
36 changes: 19 additions & 17 deletions examples/context/theme-detailed-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@ import {ThemeContext, themes} from './theme-context';
import ThemedButton from './button';

// An intermediate component that uses the ThemedButton
const Toolbar = props => {
function Toolbar(props) {
return (
<ThemedButton onClick={props.changeTheme}>
Change Theme
</ThemedButton>
);
};
}

class App extends React.Component {
state = {
theme: themes.light,
};
constructor(props) {
this.state = {
theme: themes.light,
};

toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
}

render() {
//highlight-range{1-3}
Expand All @@ -31,14 +33,14 @@ class App extends React.Component {
// the default dark theme
//highlight-range{3-5,7}
return (
<div>
<Page>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
<div>
<Section>
<ThemedButton />
</div>
</div>
</Section>
</Page>
);
}
}
Expand Down
26 changes: 12 additions & 14 deletions examples/context/theme-detailed-themed-button.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import {ThemeContext} from './theme-context';

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

export default ThemedButton;