Browse Source

Updated Code Sandbox links. Mentioned Redux.

main
Brian Vaughn 7 years ago
parent
commit
f5b7b76d30
  1. 6
      content/blog/2018-06-07-when-to-use-derived-state.md
  2. 91
      examples/when-to-use-derived-state/derived-state-anti-pattern.js

6
content/blog/2018-06-07-when-to-use-derived-state.md

@ -67,7 +67,7 @@ class EmailInput extends Component {
}
```
At first, this component might look okay. State is initialized to the value specified by props and updated when we type into the `<input>`. But once our component re-renders– either because it called `setState` or because its parent re-rendered– anything we've typed into the `<input>` will be lost! (See [this demo for an example](codesandbox://when-to-use-derived-state/derived-state-anti-pattern).)
At first, this component might look okay. State is initialized to the value specified by props and updated when we type into the `<input>`. But once our component re-renders– either because it called `setState` or because its parent re-rendered– anything we've typed into the `<input>` will be lost! ([See this demo for an example.](https://codesandbox.io/s/m3w9zn1z8x))
At this point, you might be wondering if this component would have worked with version 16.3. Unfortunately, the answer is "no". Before moving on, let's take a look at why this is.
@ -79,7 +79,7 @@ For the simple example above, we could "fix" the problem of unexpected re-render
/>
```
The above example binds the validation callback inline and so it will pass a new function prop every time it renders– effectively bypassing `shouldComponentUpdate` entirely. [Here is a demo](codesandbox://when-to-use-derived-state/derived-state-anti-pattern) that uses a timer to illustrate this problem. Because it might break at any time your component needs to accept new props, this design pattern is inherently fragile.
The above example binds the validation callback inline and so it will pass a new function prop every time it renders– effectively bypassing `shouldComponentUpdate` entirely. Even before `getDerivedStateFromProps` was introduced, this exact pattern led to bugs in `componentWillReceiveProps`. [Here is another demo that shows it.](https://codesandbox.io/s/jl0w6r9w59)
Hopefully it's clear by now why unconditionally overriding state with props is a bad idea. But what if we were to only update the state when the email prop changes? We'll take a look at that pattern next.
@ -132,7 +132,7 @@ This approach simplifies the implementation of our component but it also has a p
#### Alternative 2: Fully uncontrolled component
Another alternative would be for our component to fully own the local email state. It could still accept a prop for the initial value, but it would ignore any changes to that prop afterward. For example:
Another alternative would be for our component to fully own the "draft" email state. This might be helpful if the "committed" state lives in a state container like Redux. In that case, our component could still accept a prop for the _initial_ value, but it would ignore any changes to that prop afterward. For example:
```js
class EmailInput extends Component {

91
examples/when-to-use-derived-state/derived-state-anti-pattern.js

@ -1,91 +0,0 @@
import React, {Fragment, PureComponent} from 'react';
import {render} from 'react-dom';
// This component illustrates a getDerivedStateFromProps anti-pattern.
// Don't copy this approach!
class ExampleInput extends PureComponent {
state = {
prevProps: this.props,
text: this.props.text,
};
// This lifecycle will be re-run any time the component is rendered,
// Even if props.text has not changed.
// For this reason, it should not update state in the way shown below!
static getDerivedStateFromProps(props, state) {
if (props !== state.prevProps) {
// This return would override state,
// Erasing anything the user typed since the last render.
return {
prevProps: props,
text: props.text,
};
}
return null;
}
render() {
return (
<input
onChange={this.handleChange}
value={this.state.text}
/>
);
}
handleChange = event =>
this.setState({text: event.target.value});
}
// This component uses a timer to simulate arbitrary re-renders.
// In a real application, this could happen for a variety of reasons:
// Event handlers that call setState, Flux updates, network responses, etc.
class Timer extends PureComponent {
state = {
count: 0,
};
componentDidMount() {
this.interval = setInterval(
() =>
this.setState(prevState => ({
count: prevState.count + 1,
})),
1000
);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
// Binding the validat function inline, as is done below,
// Causes a new function value to be passed each time we render.
// Even though ExampleInput is a PureComponent,
// Its shouldComponentUpdate() will always return true because of this.
// The same would be true of inline objects (e.g. styles) or arrays.
return (
<Fragment>
<p>Type in the box below:</p>
<ExampleInput
exampleFunctionProp={this.exampleInstanceMethod.bind(
this
)}
text="example@google.com"
/>
<p>
Each time the render count ({this.state.count}) is
updated, the text you type will be reset. This
illustrates a derived state anti-pattern.
</p>
</Fragment>
);
}
exampleInstanceMethod(text) {
// ...
}
}
render(<Timer />, document.getElementById('root'));
Loading…
Cancel
Save