Reputation: 91
Let's take some trivial react component that have to render:
1) text field (managed one, connected to state)
2) list of tasks (map through array connected to state too)
3) button, that adds new item to array from text field, when clicked
https://stackblitz.com/edit/react-glpzqn?embed=1&file=index.js
when text field is updated(textChange event) => then state is updated => that force render(on each key press in text field).
it's unwanted render, I only want to render when button is clicked, item is added to array and finally new list is rendered on a screen.
so I want to force render only on item added to the list, and not when text is changed.
some solutions that I can reveal are:
1) take input to another component so text change will not affect list rendering.
2) change input field to unmanaged and retrieve text manually when button clicked.
I'm guessing if there is some more elegant solution without changing a component? may be by using some HOC or same?
Upvotes: 0
Views: 13661
Reputation: 2718
Just replace onChange with onBlur like below, the below change would only render as soon you leave the input.
<input onBlur={this.setValue} />
Upvotes: 1
Reputation: 20614
The simplest solution is using shouldComponentUpdate
. Not on the entire component, but on the components you want not to render if props not relevant to them are changing:
class List extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.items !== this.props.items
}
render() {
// will only fire when the tasks array has changed
console.log('list rendered')
return this.props.items.map(item => (
<div key={item}>{item}</div>
))
}
}
This is just a shallow check too, which you get for free with PureComponent
class List extends PureComponent {
render() {
return this.props.items.map(item => (
<div key={item}>{item}</div>
))
}
}
All together:
import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';
class List extends PureComponent {
render() {
console.log('list rendered')
return this.props.items.map(item => (
<div key={item}>{item}</div>
))
}
}
class App extends Component {
// do not need constructor if already using class arrows
state = {
input: "",
tasks: []
};
addTask = () => {
this.setState({
tasks: [...this.state.tasks, this.state.input],
input: ""
});
}
setValue = event => {
this.setState({
input: event.target.value
});
}
render() {
return (
<div>
<List items={this.state.tasks} />
<input onChange={this.setValue} value={this.state.input} />
<button onClick={this.addTask}>Add</button>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Upvotes: 1