Brian Vaughn
7 years ago
committed by
GitHub
39 changed files with 1341 additions and 258 deletions
@ -0,0 +1,98 @@ |
|||
--- |
|||
title: "React v16.3.0: New lifecycles and context API" |
|||
author: [bvaughn] |
|||
--- |
|||
|
|||
This release includes an official context API, new class component lifecycles, a new `StrictMode` component, a new ergonomic ref API, and a ref-forwarding API! |
|||
|
|||
For the past few months, the React team has been working on support for [asynchronous rendering](/blog/2018/03/01/sneak-peek-beyond-react-16.html). We are excited about the new features it will enable. |
|||
|
|||
We've also learned that some long-term changes will be required to the way we write React components. (If you haven't already, you may want to check out our [recent blog post](/blog/2018/03/27/update-on-async-rendering.html) about this.) Rest assured that we respect [semver](https://semver.org/) and **will not ship breaking changes in a minor version**! |
|||
|
|||
Read on to learn more about the release. |
|||
|
|||
## Official Context API |
|||
|
|||
For many years, React has offered an experimental API for context. Although it was a powerful tool, its use was discouraged because of inherent problems in the API, and because we always intended to replace the experimental API with a better one. |
|||
|
|||
Version 16.3 introduces a new context API that is more efficient and supports both static type checking and deep updates. |
|||
|
|||
> **Note** |
|||
> |
|||
> The old context API will keep working for all React 16.x releases, so you will have time to migrate. |
|||
|
|||
Here is an example illustrating how you might inject a "theme" using the new context API: |
|||
`embed:16-3-release-blog-post/context-example.js` |
|||
|
|||
[Learn more about the new context API here.](/docs/context.html) |
|||
|
|||
## `createRef` API |
|||
|
|||
Previously, React provided two ways of managing refs: the legacy string ref API and the callback API. Although the string ref API was the more convenient of the two, it had [several downsides](https://github.com/facebook/react/issues/1373) and so our official recommendation was to use the callback form instead. |
|||
|
|||
Version 16.3 adds a new option for managing refs that offers the convenience of a string ref without any of the downsides: |
|||
`embed:16-3-release-blog-post/create-ref-example.js` |
|||
|
|||
> **Note:** |
|||
> |
|||
> Callback refs will continue to be supported in addition to the new `createRef` API. |
|||
> |
|||
> You don't need to replace callback refs in your components. They are slightly more flexible, so they will remain as an advanced feature. |
|||
|
|||
[Learn more about the new `createRef` API here.](/docs/refs-and-the-dom.html) |
|||
|
|||
## `forwardRef` API |
|||
|
|||
[Higher-order components](/docs/higher-order-components.html) (or HOCs) are a common way to reuse code between components. Building on the theme context example from above, we might create an HOC that injects the current "theme" as a prop: |
|||
|
|||
`embed:16-3-release-blog-post/hoc-theme-example.js` |
|||
|
|||
We can use the above HOC to wire components up to the theme context without having to use `ThemeContext` directly. For example: |
|||
|
|||
`embed:16-3-release-blog-post/fancy-button-example.js` |
|||
|
|||
HOCs typically [pass props through](/docs/higher-order-components.html#convention-pass-unrelated-props-through-to-the-wrapped-component) to the components they wrap. Unfortunately, [refs are not passed through](/docs/higher-order-components.html#refs-arent-passed-through). This means that we can't attach a ref to `FancyButton` if we use `FancyThemedButton`- so there's no way for us to call `focus()`. |
|||
|
|||
The new `forwardRef` API solves this problem by providing a way for us to intercept a `ref` and forward it as a normal prop: |
|||
`embed:16-3-release-blog-post/forward-ref-example.js` |
|||
|
|||
## Component Lifecycle Changes |
|||
|
|||
React's class component API has been around for years with little change. However, as we add support for more advanced features (such as [error boundaries](/docs/react-component.html#componentdidcatch) and the upcoming [async rendering mode](/blog/2018/03/01/sneak-peek-beyond-react-16.html)) we stretch this model in ways that it was not originally intended. |
|||
|
|||
For example, with the current API, it is too easy to block the initial render with non-essential logic. In part this is because there are too many ways to accomplish a given task, and it can be unclear which is best. We've observed that the interrupting behavior of error handling is often not taken into consideration and can result in memory leaks (something that will also impact the upcoming async rendering mode). The current class component API also complicates other efforts, like our work on [prototyping a React compiler](https://twitter.com/trueadm/status/944908776896978946). |
|||
|
|||
Many of these issues are exacerbated by a subset of the component lifecycles (`componentWillMount`, `componentWillReceiveProps`, and `componentWillUpdate`). These also happen to be the lifecycles that cause the most confusion within the React community. For these reasons, we are going to deprecate those methods in favor of better alternatives. |
|||
|
|||
We recognize that this change will impact many existing components. Because of this, the migration path will be as gradual as possible, and will provide escape hatches. (At Facebook, we maintain more than 50,000 React components. We depend on a gradual release cycle too!) |
|||
|
|||
> **Note:** |
|||
> |
|||
> Deprecation warnings will be enabled with a future 16.x release, **but the legacy lifecycles will continue to work until version 17**. |
|||
> |
|||
> Even in version 17, it will still be possible to use them, but they will be aliased with an "UNSAFE_" prefix to indicate that they might cause issues. We have also prepared an [automated script to rename them](https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles) in existing code. |
|||
|
|||
In addition to deprecating unsafe lifecycles, we are also adding a couple of new lifecyles: |
|||
* [`getDerivedStateFromProps`](/docs/react-component.html#static-getderivedstatefromprops) is being added as a safer alternative to the legacy `componentWillReceiveProps`. |
|||
* [`getSnapshotBeforeUpdate`](/docs/react-component.html#getsnapshotbeforeupdate) is being added to support safely reading properties from e.g. the DOM before updates are made. |
|||
|
|||
[Learn more about these lifecycle changes here.](/blog/2018/03/27/update-on-async-rendering.html) |
|||
|
|||
## `StrictMode` Component |
|||
|
|||
`StrictMode` is a tool for highlighting potential problems in an application. Like `Fragment`, `StrictMode` does not render any visible UI. It activates additional checks and warnings for its descendants. |
|||
|
|||
> **Note:** |
|||
> |
|||
> `StrictMode` checks are run in development mode only; _they do not impact the production build_. |
|||
|
|||
Although it is not possible for strict mode to catch all problems (e.g. certain types of mutation), it can help with many. If you see warnings in strict mode, those things will likely cause bugs for async rendering. |
|||
|
|||
In version 16.3, `StrictMode` helps with: |
|||
* Identifying components with unsafe lifecycles |
|||
* Warning about legacy string ref API usage |
|||
* Detecting unexpected side effects |
|||
|
|||
Additional functionality will be added with future releases of React. |
|||
|
|||
[Learn more about the `StrictMode` component here.](/docs/strict-mode.html) |
@ -0,0 +1,22 @@ |
|||
--- |
|||
id: forwarding-refs |
|||
title: Forwarding Refs |
|||
permalink: docs/forwarding-refs.html |
|||
--- |
|||
|
|||
Ref forwarding is a technique for passing a [ref](/docs/refs-and-the-dom.html) through a component to one of its descendants. This technique can be particularly useful with [higher-order components](/docs/higher-order-components.html) (also known as HOCs). |
|||
|
|||
Let's start with an example HOC that logs component props to the console: |
|||
`embed:forwarding-refs/log-props-before.js` |
|||
|
|||
The "logProps" HOC passes all `props` through to the component it wraps, so the rendered output will be the same. For example, we can use this HOC to log all props that get passed to our "fancy button" component: |
|||
`embed:forwarding-refs/fancy-button.js` |
|||
|
|||
There is one caveat to the above example: refs will not get passed through. That's because `ref` is not a prop. Like `key`, it's handled differently by React. If you add a ref to a HOC, the ref will refer to the outermost container component, not the wrapped component. |
|||
|
|||
This means that refs intended for our `FancyButton` component will actually be attached to the `LogProps` component: |
|||
`embed:forwarding-refs/fancy-button-ref.js` |
|||
|
|||
Fortunately, we can explicitly forward refs to the inner `FancyButton` component using the `React.forwardRef` API. `React.forwardRef` accepts a render function that receives `props` and `ref` parameters and returns a React node. For example: |
|||
`embed:forwarding-refs/log-props-after.js` |
|||
|
@ -0,0 +1,217 @@ |
|||
--- |
|||
id: legacy-context |
|||
title: Legacy Context |
|||
permalink: docs/legacy-context.html |
|||
--- |
|||
|
|||
> Note: |
|||
> |
|||
> The legacy context API will be removed in a future major version. |
|||
> Use the [new context API](/docs/context.html) introduced with version 16.3. |
|||
> The legacy API will continue working for all 16.x releases. |
|||
|
|||
## How To Use Context |
|||
|
|||
> This section documents a legacy API. See the [new API](/docs/context.html). |
|||
|
|||
Suppose you have a structure like: |
|||
|
|||
```javascript |
|||
class Button extends React.Component { |
|||
render() { |
|||
return ( |
|||
<button style={{background: this.props.color}}> |
|||
{this.props.children} |
|||
</button> |
|||
); |
|||
} |
|||
} |
|||
|
|||
class Message extends React.Component { |
|||
render() { |
|||
return ( |
|||
<div> |
|||
{this.props.text} <Button color={this.props.color}>Delete</Button> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
class MessageList extends React.Component { |
|||
render() { |
|||
const color = "purple"; |
|||
const children = this.props.messages.map((message) => |
|||
<Message text={message.text} color={color} /> |
|||
); |
|||
return <div>{children}</div>; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
In this example, we manually thread through a `color` prop in order to style the `Button` and `Message` components appropriately. Using context, we can pass this through the tree automatically: |
|||
|
|||
```javascript{6,13-15,21,28-30,40-42} |
|||
import PropTypes from 'prop-types'; |
|||
|
|||
class Button extends React.Component { |
|||
render() { |
|||
return ( |
|||
<button style={{background: this.context.color}}> |
|||
{this.props.children} |
|||
</button> |
|||
); |
|||
} |
|||
} |
|||
|
|||
Button.contextTypes = { |
|||
color: PropTypes.string |
|||
}; |
|||
|
|||
class Message extends React.Component { |
|||
render() { |
|||
return ( |
|||
<div> |
|||
{this.props.text} <Button>Delete</Button> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
class MessageList extends React.Component { |
|||
getChildContext() { |
|||
return {color: "purple"}; |
|||
} |
|||
|
|||
render() { |
|||
const children = this.props.messages.map((message) => |
|||
<Message text={message.text} /> |
|||
); |
|||
return <div>{children}</div>; |
|||
} |
|||
} |
|||
|
|||
MessageList.childContextTypes = { |
|||
color: PropTypes.string |
|||
}; |
|||
``` |
|||
|
|||
By adding `childContextTypes` and `getChildContext` to `MessageList` (the context provider), React passes the information down automatically and any component in the subtree (in this case, `Button`) can access it by defining `contextTypes`. |
|||
|
|||
If `contextTypes` is not defined, then `context` will be an empty object. |
|||
|
|||
> Note: |
|||
> |
|||
> `React.PropTypes` has moved into a different package since React v15.5. Please use [the `prop-types` library instead](https://www.npmjs.com/package/prop-types) to define `contextTypes`. |
|||
> |
|||
> We provide [a codemod script](/blog/2017/04/07/react-v15.5.0.html#migrating-from-react.proptypes) to automate the conversion. |
|||
|
|||
### Parent-Child Coupling |
|||
|
|||
> This section documents a legacy API. See the [new API](/docs/context.html). |
|||
|
|||
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): |
|||
|
|||
```javascript |
|||
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'; |
|||
|
|||
const BasicExample = () => ( |
|||
<Router> |
|||
<div> |
|||
<ul> |
|||
<li><Link to="/">Home</Link></li> |
|||
<li><Link to="/about">About</Link></li> |
|||
<li><Link to="/topics">Topics</Link></li> |
|||
</ul> |
|||
|
|||
<hr /> |
|||
|
|||
<Route exact path="/" component={Home} /> |
|||
<Route path="/about" component={About} /> |
|||
<Route path="/topics" component={Topics} /> |
|||
</div> |
|||
</Router> |
|||
); |
|||
``` |
|||
|
|||
By passing down some information from the `Router` component, each `Link` and `Route` can communicate back to the containing `Router`. |
|||
|
|||
Before you build components with an API similar to this, consider if there are cleaner alternatives. For example, you can pass entire React components as props if you'd like to. |
|||
|
|||
### Referencing Context in Lifecycle Methods |
|||
|
|||
> This section documents a legacy API. See the [new API](/docs/context.html). |
|||
|
|||
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: |
|||
|
|||
- [`constructor(props, context)`](/docs/react-component.html#constructor) |
|||
- [`componentWillReceiveProps(nextProps, nextContext)`](/docs/react-component.html#componentwillreceiveprops) |
|||
- [`shouldComponentUpdate(nextProps, nextState, nextContext)`](/docs/react-component.html#shouldcomponentupdate) |
|||
- [`componentWillUpdate(nextProps, nextState, nextContext)`](/docs/react-component.html#componentwillupdate) |
|||
|
|||
> Note: |
|||
> |
|||
> As of React 16, `componentDidUpdate` no longer receives `prevContext`. |
|||
|
|||
### Referencing Context in Stateless Functional Components |
|||
|
|||
> This section documents a legacy API. See the [new API](/docs/context.html). |
|||
|
|||
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. |
|||
|
|||
```javascript |
|||
import PropTypes from 'prop-types'; |
|||
|
|||
const Button = ({children}, context) => |
|||
<button style={{background: context.color}}> |
|||
{children} |
|||
</button>; |
|||
|
|||
Button.contextTypes = {color: PropTypes.string}; |
|||
``` |
|||
|
|||
### Updating Context |
|||
|
|||
> This section documents a legacy API. See the [new API](/docs/context.html). |
|||
|
|||
Don't do it. |
|||
|
|||
React has an API to update context, but it is fundamentally broken and you should not use it. |
|||
|
|||
The `getChildContext` function will be called when the state or props changes. In order to update data in the context, trigger a local state update with `this.setState`. This will trigger a new context and changes will be received by the children. |
|||
|
|||
```javascript |
|||
import PropTypes from 'prop-types'; |
|||
|
|||
class MediaQuery extends React.Component { |
|||
constructor(props) { |
|||
super(props); |
|||
this.state = {type:'desktop'}; |
|||
} |
|||
|
|||
getChildContext() { |
|||
return {type: this.state.type}; |
|||
} |
|||
|
|||
componentDidMount() { |
|||
const checkMediaQuery = () => { |
|||
const type = window.matchMedia("(min-width: 1025px)").matches ? 'desktop' : 'mobile'; |
|||
if (type !== this.state.type) { |
|||
this.setState({type}); |
|||
} |
|||
}; |
|||
|
|||
window.addEventListener('resize', checkMediaQuery); |
|||
checkMediaQuery(); |
|||
} |
|||
|
|||
render() { |
|||
return this.props.children; |
|||
} |
|||
} |
|||
|
|||
MediaQuery.childContextTypes = { |
|||
type: PropTypes.string |
|||
}; |
|||
``` |
|||
|
|||
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. |
@ -0,0 +1,88 @@ |
|||
--- |
|||
id: strict-mode |
|||
title: Strict Mode |
|||
permalink: docs/strict-mode.html |
|||
--- |
|||
|
|||
`StrictMode` is a tool for highlighting potential problems in an application. Like `Fragment`, `StrictMode` does not render any visible UI. It activates additional checks and warnings for its descendants. |
|||
|
|||
> Note: |
|||
> |
|||
> Strict mode checks are run in development mode only; _they do not impact the production build_. |
|||
|
|||
You can enable strict mode for any part of your application. For example: |
|||
`embed:strict-mode/enabling-strict-mode.js` |
|||
|
|||
In the above example, strict mode checks will *not* be run against the `Header` and `Footer` components. However, `ComponentOne` and `ComponentTwo`, as well as all of their descendants, will have the checks. |
|||
|
|||
`StrictMode` currently helps with: |
|||
* [Identifying components with unsafe lifecycles](#identifying-unsafe-lifecycles) |
|||
* [Warning about legacy string ref API usage](#warning-about-legacy-string-ref-api-usage) |
|||
* [Detecting unexpected side effects](#detecting-unexpected-side-effects) |
|||
|
|||
Additional functionality will be added with future releases of React. |
|||
|
|||
### Identifying unsafe lifecycles |
|||
|
|||
As explained [in this blog post](/blog/2018/03/27/update-on-async-rendering.html), certain legacy lifecycle methods are unsafe for use in async React applications. However, if your application uses third party libraries, it can be difficult to ensure that these lifecycles aren't being used. Fortunately, strict mode can help with this! |
|||
|
|||
When strict mode is enabled, React compiles a list of all class components using the unsafe lifecycles, and logs a warning message with information about these components, like so: |
|||
|
|||
![](../images/blog/strict-mode-unsafe-lifecycles-warning.png) |
|||
|
|||
Addressing the issues identified by strict mode _now_ will make it easier for you to take advantage of async rendering in future releases of React. |
|||
|
|||
### Warning about legacy string ref API usage |
|||
|
|||
Previously, React provided two ways for managing refs: the legacy string ref API and the callback API. Although the string ref API was the more convenient of the two, it had [several downsides](https://github.com/facebook/react/issues/1373) and so our official recommendation was to [use the callback form instead](/docs/refs-and-the-dom.html#legacy-api-string-refs). |
|||
|
|||
React 16.3 added a third option that offers the convenience of a string ref without any of the downsides: |
|||
`embed:16-3-release-blog-post/create-ref-example.js` |
|||
|
|||
Since object refs were largely added as a replacement for string refs, strict mode now warns about usage of string refs. |
|||
|
|||
> **Note:** |
|||
> |
|||
> Callback refs will continue to be supported in addition to the new `createRef` API. |
|||
> |
|||
> You don't need to replace callback refs in your components. They are slightly more flexible, so they will remain as an advanced feature. |
|||
|
|||
[Learn more about the new `createRef` API here.](/docs/refs-and-the-dom.html) |
|||
|
|||
### Detecting unexpected side effects |
|||
|
|||
Conceptually, React does work in two phases: |
|||
* The **render** phase determines what changes need to be made to e.g. the DOM. During this phase, React calls `render` and then compares the result to the previous render. |
|||
* The **commit** phase is when React applies any changes. (In the case of React DOM, this is when React inserts, updates, and removes DOM nodes.) React also calls lifecycles like `componentDidMount` and `componentDidUpdate` during this phase. |
|||
|
|||
The commit phase is usually very fast, but rendering can be slow. For this reason, the upcoming async mode (which is not enabled by default yet) breaks the rendering work into pieces, pausing and resuming the work to avoid blocking the browser. This means that React may invoke render phase lifecycles more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption). |
|||
|
|||
Render phase lifecycles include the following class component methods: |
|||
* `constructor` |
|||
* `componentWillMount` |
|||
* `componentWillReceiveProps` |
|||
* `componentWillUpdate` |
|||
* `getDerivedStateFromProps` |
|||
* `shouldComponentUpdate` |
|||
* `render` |
|||
* `setState` updater functions (the first argument) |
|||
|
|||
Because the above methods might be called more than once, it's important that they do not contain side-effects. Ignoring this rule can lead to a variety of problems, including memory leaks and invalid application state. Unfortunately, it can be difficult to detect these problems as they can often be [non-deterministic](https://en.wikipedia.org/wiki/Deterministic_algorithm). |
|||
|
|||
Strict mode can't automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following methods: |
|||
|
|||
* Class component `constructor` method |
|||
* The `render` method |
|||
* `setState` updater functions (the first argument) |
|||
* The static `getDerivedStateFromProps` lifecycle |
|||
|
|||
> Note: |
|||
> |
|||
> This only applies to development mode. _Lifecycles will not be double-invoked in production mode._ |
|||
|
|||
For example, consider the following code: |
|||
`embed:strict-mode/side-effects-in-constructor.js` |
|||
|
|||
At first glance, this code might not seem problematic. But if `SharedApplicationState.recordEvent` is not [idempotent](https://en.wikipedia.org/wiki/Idempotence#Computer_science_meaning), then instantiating this component multiple times could lead to invalid application state. This sort of subtle bug might not manifest during development, or it might do so inconsistently and so be overlooked. |
|||
|
|||
By intentionally double-invoking methods like the component constructor, strict mode makes patterns like this easier to spot. |
After Width: | Height: | Size: 52 KiB |
@ -0,0 +1,26 @@ |
|||
// highlight-next-line
|
|||
const ThemeContext = React.createContext('light'); |
|||
|
|||
class ThemeProvider extends React.Component { |
|||
state = {theme: 'light'}; |
|||
|
|||
render() { |
|||
// highlight-range{2-4}
|
|||
return ( |
|||
<ThemeContext.Provider value={this.state.theme}> |
|||
{this.props.children} |
|||
</ThemeContext.Provider> |
|||
); |
|||
} |
|||
} |
|||
|
|||
class ThemedButton extends React.Component { |
|||
render() { |
|||
// highlight-range{2-4}
|
|||
return ( |
|||
<ThemeContext.Consumer> |
|||
{theme => <Button theme={theme} />} |
|||
</ThemeContext.Consumer> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
class MyComponent extends React.Component { |
|||
// highlight-next-line
|
|||
inputRef = React.createRef(); |
|||
|
|||
render() { |
|||
// highlight-next-line
|
|||
return <input type="text" ref={this.inputRef} />; |
|||
} |
|||
|
|||
componentDidMount() { |
|||
// highlight-next-line
|
|||
this.inputRef.current.focus(); |
|||
} |
|||
} |
@ -0,0 +1,32 @@ |
|||
class FancyButton extends React.Component { |
|||
buttonRef = React.createRef(); |
|||
|
|||
focus() { |
|||
this.buttonRef.current.focus(); |
|||
} |
|||
|
|||
render() { |
|||
// highlight-next-line
|
|||
const {label, theme, ...rest} = this.props; |
|||
// highlight-range{5}
|
|||
return ( |
|||
<button |
|||
{...rest} |
|||
className={`${theme}-button`} |
|||
ref={this.buttonRef}> |
|||
{label} |
|||
</button> |
|||
); |
|||
} |
|||
} |
|||
|
|||
// highlight-next-line
|
|||
const FancyThemedButton = withTheme(FancyButton); |
|||
|
|||
// We can render FancyThemedButton as if it were a FancyButton
|
|||
// It will automatically receive the current "theme",
|
|||
// And the HOC will pass through our other props.
|
|||
<FancyThemedButton |
|||
label="Click me!" |
|||
onClick={handleClick} |
|||
/>; |
@ -0,0 +1,36 @@ |
|||
function withTheme(Component) { |
|||
// Note the second param "ref" provided by React.forwardRef.
|
|||
// We can attach this to Component directly.
|
|||
// highlight-range{1,5}
|
|||
function ThemedComponent(props, ref) { |
|||
return ( |
|||
<ThemeContext.Consumer> |
|||
{theme => ( |
|||
<Component {...props} ref={ref} theme={theme} /> |
|||
)} |
|||
</ThemeContext.Consumer> |
|||
); |
|||
} |
|||
|
|||
// These next lines are not necessary,
|
|||
// But they do give the component a better display name in DevTools,
|
|||
// e.g. "ForwardRef(withTheme(MyComponent))"
|
|||
// highlight-range{1-2}
|
|||
const name = Component.displayName || Component.name; |
|||
ThemedComponent.displayName = `withTheme(${name})`; |
|||
|
|||
// Tell React to pass the "ref" to ThemedComponent.
|
|||
// highlight-next-line
|
|||
return React.forwardRef(ThemedComponent); |
|||
} |
|||
|
|||
// highlight-next-line
|
|||
const fancyButtonRef = React.createRef(); |
|||
|
|||
// fancyButtonRef will now point to FancyButton
|
|||
// highlight-range{4}
|
|||
<FancyThemedButton |
|||
label="Click me!" |
|||
onClick={handleClick} |
|||
ref={fancyButtonRef} |
|||
/>; |
@ -0,0 +1,10 @@ |
|||
function withTheme(Component) { |
|||
return function ThemedComponent(props) { |
|||
// highlight-range{2-4}
|
|||
return ( |
|||
<ThemeContext.Consumer> |
|||
{theme => <Component {...props} theme={theme} />} |
|||
</ThemeContext.Consumer> |
|||
); |
|||
}; |
|||
} |
@ -0,0 +1,11 @@ |
|||
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>; |
@ -0,0 +1,16 @@ |
|||
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,3}
|
|||
export default React.forwardRef((props, ref) => ( |
|||
<ThemeContext.Consumer> |
|||
{theme => <Button {...props} theme={theme} ref={ref} />} |
|||
</ThemeContext.Consumer> |
|||
)); |
@ -0,0 +1,10 @@ |
|||
const ThemeContext = React.createContext('light'); |
|||
|
|||
function ThemedButton(props) { |
|||
// highlight-range{2-4}
|
|||
return ( |
|||
<ThemeContext.Consumer> |
|||
{theme => <button className={theme} {...props} />} |
|||
</ThemeContext.Consumer> |
|||
); |
|||
} |
@ -0,0 +1,7 @@ |
|||
function Button({theme, ...rest}) { |
|||
// highlight-next-line
|
|||
return <button className={theme} {...rest} />; |
|||
} |
|||
|
|||
// highlight-next-line
|
|||
const ThemedButton = withTheme(Button); |
@ -0,0 +1,18 @@ |
|||
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> |
|||
); |
|||
}; |
|||
} |
@ -0,0 +1,28 @@ |
|||
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 ? 'dark' : 'light'}> |
|||
{children} |
|||
</button> |
|||
); |
|||
} |
|||
} |
|||
|
|||
// highlight-range{3}
|
|||
export default props => ( |
|||
<ThemeContext.Consumer> |
|||
{theme => <Button {...props} theme={theme} />} |
|||
</ThemeContext.Consumer> |
|||
); |
@ -0,0 +1,23 @@ |
|||
function ThemedButton(props) { |
|||
//highlight-range{1}
|
|||
return <Button theme={props.theme} />; |
|||
} |
|||
|
|||
// An intermediate component
|
|||
function Toolbar(props) { |
|||
// highlight-range{1-2,5}
|
|||
// The Toolbar component must take an extra theme prop
|
|||
// and pass it to the ThemedButton
|
|||
return ( |
|||
<div> |
|||
<ThemedButton theme={props.theme} /> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
class App extends React.Component { |
|||
render() { |
|||
// highlight-range{1}
|
|||
return <Toolbar theme="dark" />; |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
// Create a theme context, defaulting to light theme
|
|||
// highlight-next-line
|
|||
const ThemeContext = React.createContext('light'); |
|||
|
|||
function ThemedButton(props) { |
|||
// highlight-range{1,3-5}
|
|||
// The ThemedButton receives the theme from context
|
|||
return ( |
|||
<ThemeContext.Consumer> |
|||
{theme => <Button {...props} theme={theme} />} |
|||
</ThemeContext.Consumer> |
|||
); |
|||
} |
|||
|
|||
// An intermediate component
|
|||
function Toolbar(props) { |
|||
return ( |
|||
<div> |
|||
<ThemedButton /> |
|||
</div> |
|||
); |
|||
} |
|||
|
|||
class App extends React.Component { |
|||
render() { |
|||
// highlight-range{2,4}
|
|||
return ( |
|||
<ThemeContext.Provider value="dark"> |
|||
<Toolbar /> |
|||
</ThemeContext.Provider> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
// Theme context, default to light theme
|
|||
// highlight-next-line
|
|||
const ThemeContext = React.createContext('light'); |
|||
|
|||
// Signed-in user context
|
|||
// highlight-next-line
|
|||
const UserContext = React.createContext(); |
|||
|
|||
// An intermediate component that depends on both contexts
|
|||
function Toolbar(props) { |
|||
// highlight-range{2-10}
|
|||
return ( |
|||
<ThemeContext.Consumer> |
|||
{theme => ( |
|||
<UserContext.Consumer> |
|||
{user => ( |
|||
<ProfilePage user={user} theme={theme} /> |
|||
)} |
|||
</UserContext.Consumer> |
|||
)} |
|||
</ThemeContext.Consumer> |
|||
); |
|||
} |
|||
|
|||
class App extends React.Component { |
|||
render() { |
|||
const {signedInUser, theme} = this.props; |
|||
|
|||
// App component that provides initial context values
|
|||
// highlight-range{2-3,5-6}
|
|||
return ( |
|||
<ThemeContext.Provider value={theme}> |
|||
<UserContext.Provider value={signedInUser}> |
|||
<Toolbar /> |
|||
</UserContext.Provider> |
|||
</ThemeContext.Provider> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
class App extends React.Component { |
|||
render() { |
|||
// highlight-range{2}
|
|||
return ( |
|||
<Provider value={{something: 'something'}}> |
|||
<Toolbar /> |
|||
</Provider> |
|||
); |
|||
} |
|||
} |
@ -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> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
import {ThemeContext, themes} from './theme-context'; |
|||
import ThemedButton from './button'; |
|||
|
|||
// An intermediate component that uses the ThemedButton
|
|||
function Toolbar(props) { |
|||
return ( |
|||
<ThemedButton onClick={props.changeTheme}> |
|||
Change Theme |
|||
</ThemedButton> |
|||
); |
|||
} |
|||
|
|||
class App extends React.Component { |
|||
constructor(props) { |
|||
this.state = { |
|||
theme: themes.light, |
|||
}; |
|||
|
|||
this.toggleTheme = () => { |
|||
this.setState(state => ({ |
|||
theme: |
|||
state.theme === themes.dark |
|||
? themes.light |
|||
: themes.dark, |
|||
})); |
|||
}; |
|||
} |
|||
|
|||
render() { |
|||
//highlight-range{1-3}
|
|||
// The ThemedButton button inside the ThemeProvider
|
|||
// uses the theme from state while the one outside uses
|
|||
// the default dark theme
|
|||
//highlight-range{3-5,7}
|
|||
return ( |
|||
<Page> |
|||
<ThemeContext.Provider value={this.state.theme}> |
|||
<Toolbar changeTheme={this.toggleTheme} /> |
|||
</ThemeContext.Provider> |
|||
<Section> |
|||
<ThemedButton /> |
|||
</Section> |
|||
</Page> |
|||
); |
|||
} |
|||
} |
|||
|
|||
ReactDOM.render(<App />, document.root); |
@ -0,0 +1,15 @@ |
|||
export const themes = { |
|||
light: { |
|||
foreground: '#ffffff', |
|||
background: '#222222', |
|||
}, |
|||
dark: { |
|||
foreground: '#000000', |
|||
background: '#eeeeee', |
|||
}, |
|||
}; |
|||
|
|||
// highlight-range{1-3}
|
|||
export const ThemeContext = React.createContext( |
|||
themes.dark // default value
|
|||
); |
@ -0,0 +1,17 @@ |
|||
import {ThemeContext} from './theme-context'; |
|||
|
|||
function ThemedButton(props) { |
|||
// highlight-range{2-9}
|
|||
return ( |
|||
<ThemeContext.Consumer> |
|||
{theme => ( |
|||
<button |
|||
{...props} |
|||
style={{backgroundColor: theme.background}} |
|||
/> |
|||
)} |
|||
</ThemeContext.Consumer> |
|||
); |
|||
} |
|||
|
|||
export default ThemedButton; |
@ -0,0 +1,15 @@ |
|||
import FancyButton from './FancyButton'; |
|||
|
|||
// highlight-next-line
|
|||
const ref = React.createRef(); |
|||
|
|||
// The FancyButton component we imported is the LogProps HOC.
|
|||
// Even though the rendered output will be the same,
|
|||
// Our ref will point to LogProps instead of the inner FancyButton component!
|
|||
// This means we can't call e.g. ref.current.focus()
|
|||
// highlight-range{4}
|
|||
<FancyButton |
|||
label="Click Me" |
|||
handleClick={handleClick} |
|||
ref={ref} |
|||
/>; |
@ -0,0 +1,12 @@ |
|||
class FancyButton extends React.Component { |
|||
focus() { |
|||
// ...
|
|||
} |
|||
|
|||
// ...
|
|||
} |
|||
|
|||
// Rather than exporting FancyButton, we export LogProps.
|
|||
// It will render a FancyButton though.
|
|||
// highlight-next-line
|
|||
export default logProps(FancyButton); |
@ -0,0 +1,34 @@ |
|||
function logProps(Component) { |
|||
class LogProps extends React.Component { |
|||
componentDidUpdate(prevProps) { |
|||
console.log('old props:', prevProps); |
|||
console.log('new props:', this.props); |
|||
} |
|||
|
|||
render() { |
|||
// highlight-next-line
|
|||
const {forwardedRef, ...rest} = this.props; |
|||
|
|||
// Assign the custom prop "forwardedRef" as a ref
|
|||
// highlight-next-line
|
|||
return <Component ref={forwardedRef} {...rest} />; |
|||
} |
|||
} |
|||
|
|||
// Note the second param "ref" provided by React.forwardRef.
|
|||
// We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
|
|||
// And it can then be attached to the Component.
|
|||
// highlight-range{1-3}
|
|||
function forwardRef(props, ref) { |
|||
return <LogProps {...props} forwardedRef={ref} />; |
|||
} |
|||
|
|||
// These next lines are not necessary,
|
|||
// But they do give the component a better display name in DevTools,
|
|||
// e.g. "ForwardRef(logProps(MyComponent))"
|
|||
// highlight-range{1-2}
|
|||
const name = Component.displayName || Component.name; |
|||
forwardRef.displayName = `logProps(${name})`; |
|||
|
|||
return React.forwardRef(forwardRef); |
|||
} |
@ -0,0 +1,16 @@ |
|||
// highlight-next-line
|
|||
function logProps(WrappedComponent) { |
|||
class LogProps extends React.Component { |
|||
componentDidUpdate(prevProps) { |
|||
console.log('old props:', prevProps); |
|||
console.log('new props:', this.props); |
|||
} |
|||
|
|||
render() { |
|||
// highlight-next-line
|
|||
return <WrappedComponent {...this.props} />; |
|||
} |
|||
} |
|||
|
|||
return LogProps; |
|||
} |
@ -0,0 +1,27 @@ |
|||
class ScrollingList extends React.Component { |
|||
listRef = React.createRef(); |
|||
|
|||
getSnapshotBeforeUpdate(prevProps, prevState) { |
|||
// Are we adding new items to the list?
|
|||
// Capture the current height of the list so we can adjust scroll later.
|
|||
if (prevProps.list.length < this.props.list.length) { |
|||
return this.listRef.current.scrollHeight; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
componentDidUpdate(prevProps, prevState, snapshot) { |
|||
// If we have a snapshot value, we've just added new items.
|
|||
// Adjust scroll so these new items don't push the old ones out of view.
|
|||
if (snapshot !== null) { |
|||
this.listRef.current.scrollTop += |
|||
this.listRef.current.scrollHeight - snapshot; |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
return ( |
|||
<div ref={this.listRef}>{/* ...contents... */}</div> |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
function enhance(Component) { |
|||
class Enhanced extends React.Component { |
|||
// ...
|
|||
|
|||
render() { |
|||
const {forwardedRef, ...rest} = this.props; |
|||
|
|||
// Assign the custom prop "forwardedRef" as a ref
|
|||
// highlight-next-line
|
|||
return <Component ref={forwardedRef} {...rest} />; |
|||
} |
|||
} |
|||
|
|||
// Intercept the "ref" and pass it as a custom prop, e.g. "forwardedRef"
|
|||
// highlight-range{1-3}
|
|||
function enhanceForwardRef(props, ref) { |
|||
return <Enhanced {...props} forwardedRef={ref} />; |
|||
} |
|||
|
|||
// These next lines are not necessary,
|
|||
// But they do give the component a better display name in DevTools,
|
|||
// e.g. "ForwardRef(withTheme(MyComponent))"
|
|||
const name = Component.displayName || Component.name; |
|||
enhanceForwardRef.displayName = `enhance(${name})`; |
|||
|
|||
// highlight-next-line
|
|||
return React.forwardRef(enhanceForwardRef); |
|||
} |
@ -0,0 +1,18 @@ |
|||
import React from 'react'; |
|||
|
|||
function ExampleApplication() { |
|||
return ( |
|||
<div> |
|||
<Header /> |
|||
{/* highlight-next-line */} |
|||
<React.StrictMode> |
|||
<div> |
|||
<ComponentOne /> |
|||
<ComponentTwo /> |
|||
</div> |
|||
{/* highlight-next-line */} |
|||
</React.StrictMode> |
|||
<Footer /> |
|||
</div> |
|||
); |
|||
} |
@ -0,0 +1,7 @@ |
|||
class TopLevelRoute extends React.Component { |
|||
constructor(props) { |
|||
super(props); |
|||
|
|||
SharedApplicationState.recordEvent('ExampleComponent'); |
|||
} |
|||
} |
Loading…
Reference in new issue