Reputation: 130
I'm using React & _underscorejs to filter a single data element from an array of JSON objects and render it on to the screen in a table. My code will render the filtered data but then the whole table gets rendered as well after a few seconds.
this.state = { stats:[],
value:'';
componentDidMount() {
fetch('http://localhost:5000/cityStats')
.then((data) => data.json())
.then((data) => this.setState( { stats: data } ))
;
}
// Using a select menu to select value
handleChange = (e) => {
this.setState({ value: e.target.value });
// Filtering a element that matches value choosen
this.setState( { "stats": _.where(this.state.stats,
{NAME:e.target.value})});
this.getDataAgain();
}
//
getDataAgain (){
fetch('http://localhost:5000/citystats')
.then((data) => data.json())
.then((data) => this.setState( { stats: data } ));
}
If I don't call getDataAgain(), then the filter will only work once and show a blank list. How can I fix it to show only the filtered data when the user selects an option?
Upvotes: 0
Views: 43
Reputation: 1897
The main problem here is that you are overwriting the data with the filtered object. This is definitively a wrong approach to the issue. After you set the data in the state, you shouldn't mutate it to avoid additional unnecessary api requests.
NOTE: Also, you should remove the getDataAgain or name it getData and just call it in the CDM method to avoid code duplication.
componentDidMount() {
this.getData();
}
getData() {
fetch('http://localhost:5000/cityStats')
.then((data) => data.json())
.then((stats) => this.setState( { stats } ))
}
Approach 1: Using a private property to set/unset data
stats = undefined; // Will permanently hold the whole dataset
componentDidMount() {
fetch('http://localhost:5000/cityStats')
.then(data => data.json())
.then(stats => {
this.stats = stats; // Saves the whole unfiltered dataset to private prop
this.setState({ stats });
});
}
handleChange = e => {
const { value } = e.target.value;
// In case value is unset it will revert stats to the whole unfiltered set
// from the data prop
this.setState({
value,
stats: value ? _.where(this.state.stats, { NAME: value }) : this.stats
});
};
Approach 2: Filtering in render (memoization optional)
Simply filter it in the render. If you don't have a huge dataset, this will not cause any performance issues.
render() {
const { stats, value } = this.state;
const filteredData = value
? _.where(stats, { NAME: value })
: stats;
.. do something with filteredData
}
Even in the case with a big dataset, you should do the filtering in the render function, so you always have correctly filtered data shown. The only difference is, that in this scenario you would use some form of a memoization function so that each filtered set gets cached for next renders.
A basic example: https://medium.com/@planttheidea/memoize-react-components-33377d7ebb6c
Upvotes: 0
Reputation: 16132
Use two arrays and call the endpoint once, save original array in both arrays this.setState({ stats: data, filtered: data})
. Use filtered
array as your data source.
state = { stats: [], filtered: [], value: '' };
componentDidMount = () => {
this.getDataAgain();
}
// Using a select menu to select value
handleChange = e => {
this.setState({ value: e.target.value });
// Filtering a element that matches value choosen
const filtered = _.where(this.state.stats, { NAME: e.target.value });
this.setState({
filtered: filtered
});
};
//
getDataAgain = () => {
fetch('http://localhost:5000/citystats')
.then(data => data.json())
.then(data => this.setState({ stats: data, filtered: data }));
}
where you are currently using stats
, change it to this.state.filtered
Upvotes: 1