Digestion from Writing Resilient Components
from Writing Resilient Components — Overreacted
There are 4 helpful principles.
Don't Stop the Data Flow
When somebody uses your component, they expect that they can pass different props to it over time, and that the component will reflect those changes.
Keep the rendering result consistent with prop changes.
const Button = ({ color, children }) => {
return (
// ✅ `color` is always fresh!
<button className={'Button-' + color}>
{children}
</button>
);
}
// usage
<Button color={isOk ? 'blue' : 'red'} />確保傳進 Component 的 props 總是會直接影響最後 render 的結果
Common Mistake
copy props into state:
class Button extends React.Component {
state = {
color: this.props.color
};
render() {
const { color } = this.state; // ❌ `color` is stale!
return (
<button className={'Button-' + color}>
{this.props.children}
</button>
);
}
}Computed Value
Computed values are another reason people sometimes attempt to copy props into state.
Use memoization.
function Button({ color, children }) {
const textColor = useMemo(
() => slowlyCalculateTextColor(color),
[color] // ✅ Don’t recalculate until `color` changes
);
return (
<button className={'Button-' + color + ' Button-text-' + textColor}>
{children}
</button>
);
}even optimizing expensive computations isn’t a good reason to copy props into state. Our rendering result should respect changes to props.
Keep the Local State Isolated
如何決定什麼該存在 Component 的 local state, 什麼該存在 Component 外部,
If you’re not sure whether some state is local, ask yourself: “If this component was rendered twice, should this interaction reflect in the other copy?” Whenever the answer is “no”, you found some local state.
For example, imagine we rendered the same Post twice.
-
Post content. We’d want editing the post in one tree to update it in another tree. Therefore, it probably should not be the local state of a Post component. (Instead, the post content could live in some cache like Apollo, Relay, or Redux.)
-
List of comments. This is similar to post content. We’d want adding a new comment in one tree to be reflected in the other tree too. So ideally we would use some kind of a cache for it, and it should not be a local state of our Post.
-
Which comments are expanded. It would be weird if expanding a comment in one tree would also expand it in another tree. In this case we’re interacting with a particular Comment UI representation rather than an abstract “comment entity”. Therefore, an “expanded” flag should be a local state of the Comment.
-
The value of new comment input. It would be odd if typing a comment in one input would also update an input in another tree. Unless inputs are clearly grouped together, usually people expect them to be independent. So the input value should be a local state of the NewComment component.