ComponentWillReceiveProps are changing my props which I was getting from API - reactjs

In my ComponentWillReceiveProps() methods my prop are getting updated which was fetched from API only. Please help how to recover the props.
My ComponentWillReceiveProps():
constructor(props) {
super(props)
this.state = {
filtered_chart_data: {}
}
props.getEngagementInfo()
props.getChartData()
}
componentWillReceiveProps(nextProps) {
const lastSavedCharts = this.state.filtered_chart_data
if (!_.isEmpty(nextProps.diagnose.chart_data)) {
if (_.isEmpty(this.state.filtered_chart_data)) {
return this.setState({
filtered_chart_data: nextProps.diagnose.chart_data
})
}
return this.setState(
{
filtered_chart_data: lastSavedCharts
},
() => this.updateFilters(nextProps.diagnose.chart_data)
)
}
}
updateFilters = chartDataToApplyFilters => {
if (!_.isEmpty(this.state.filtered_chart_data)) {
const { filters } = this.props.diagnose
this.handleFilterPlantRegion(
filters.plant_region,
chartDataToApplyFilters
)
this.handleFilterPlant(filters.plant, chartDataToApplyFilters)
}
}
In my nextProps the variable nextProps.diagnose.chart_data is updating every time, but it is being fetch from API.
Can you help How to not update this props?

you can try shouldComponentUpdate(), from docs

Related

setState not returned from render when using Axios

I'm using axios to get data from an endpoint. I'm trying to store this data inside the state of my React component, but I keep getting this error:
Error: Results(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
I've struggled with many approaches: arrow functions etc., but without luck.
export default class Map extends Component {
constructor() {
super();
this.state = {
fillColor: {},
selectedCounty: "",
dbResponse: null,
};
}
getCounty(e) {
axios.get("/getWeatherData?county=" + e.target.id)
.then((response) => {
this.setState(prevState => {
let fillColor = {...prevState.fillColor};
fillColor[prevState.selectedCounty] = '#81AC8B';
fillColor[e.target.id] = '#425957';
const selectedCounty = e.target.id;
const dbResponse = response.data;
return { dbResponse, selectedCounty, fillColor };
})
}).catch((error) => {
console.log('Could not connect to the backend');
console.log(error)
});
}
render() {
return (
<div id="map">
<svg>big svg file</svg>
{this.state.selectedCounty ? <Results/> : null}
</div>
)
}
I need to set the state using prevState in order to update the fillColor dictionary.
Should this be expected? Is there a workaround?

React - Render happening before data is returned and not updating component

I can't get this to work correctly after several hours.
When creating a component that needs data from Firebase to display, the data is returning after all actions have taken place so my component isn't showing until pressing the button again which renders again and shows correctly.
Currently my function is finishing before setState, and setState is happening before the data returns.
I can get setState to happen when the data is returned by using the callback on setState but the component would have already rendered.
How do i get the component to render after the data has returned?
Or what would the correct approach be?
class CoffeeList extends Component {
constructor(props) {
super(props);
this.state = {
coffeeList: [],
}
}
componentDidMount() {
this.GetCoffeeList()
}
GetCoffeeList() {
var cups = []
coffeeCollection.get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
})
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
}
render() {
const coffeeCups = this.state.coffeeList;
console.log("Rendering component")
return (
<div className="coffee">
<p> This is the Coffee Component</p>
{coffeeCups.map((c) => {
return (
<CoffeeBox name={c.name} />
)
})}
</div >
)
}
}
Thanks
The problem is that you set the state before the promise is resolved. Change the code in the following way:
GetCoffeeList() {
coffeeCollection.get().then((querySnapshot) => {
const cups = []
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
})
}

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

Why is the state change of my component not being detected by componentDidUpdate()?

I have a table of ships, and am trying to implement sorting (using table header clicks) and filtering (using a text field that the user types in).
I am puzzled by how React handles the state of my component.
My understanding is that componentDidUpdate() works like this:
I make a change to the component state somewhere
The state change is detected by the component and componentDidUpdate() runs
Based on this understanding, I expected componentDidUpdate() to
Re-sort when I change the state of ships
Re-filter when I change the state of ships
However, when a sorting is triggered, filtering is not done.
I thought that this would happen:
State is changed, triggering componentDidUpdate()
Ships are sorted
The state is saved
The saving of the state triggers a re-run of componentDidUpdate()
this.state.ships is now different from prevState.ships, triggering a re-filtering
But this seems to happen:
State is changed, triggering componentDidUpdate()
Ships are sorted
The state is saved
The saving of the state triggers a re-run of componentDidUpdate()
this.state.ships is the same as prevState.ships, not triggering a re-filtering
So either my understanding of componentDidUpdate() is spotty, or my understanding of state synchronicity is. I have read that state can be asynchronous in event handlers. Perhaps the sorted ships are not yet saved into the state when I try to detect if I should be filtering?
import React, { Component } from 'react';
import { SearchBar } from '../SearchBar';
import { Table } from '../Table/Table';
import { MoreButton } from '../MoreButton/MoreButton';
export class SearchableSortableTable extends Component {
constructor(props) {
super(props);
this.fetchShips = this.fetchShips.bind(this);
this.filterShips = this.filterShips.bind(this);
this.setSearchExpression = this.setSearchExpression.bind(this);
this.setSort = this.setSort.bind(this);
this.state = {
ships: [],
filteredShips: [],
searchExpression: '',
reverseSort: false
};
}
render() {
return (
this.state.error ?
<div>
<div>There was a problem fetching the ships, sorry.</div>
<div>{this.state.error}</div>
</div>
:
this.state.ships.length === 0 ? <h4>Loading...</h4> :
<div>
<div>
<SearchBar setSearchExpression={this.setSearchExpression} />
<MoreButton className="di" url={this.state.nextUrl} fetchShips={this.fetchShips} />
</div>
<div>
<Table ships={this.state.filteredShips} setSort={this.setSort} sortBy={this.state.columnName} reverse={this.state.reverseSort} />
</div>
</div>
);
}
componentDidMount() {
this.fetchShips(this.props.url);
}
componentDidUpdate(prevProps, prevState) {
if (this.state.columnName !== prevState.columnName || this.state.reverseSort !== prevState.reverseSort) {
this.sortShips();
}
// This conditional block is not entered when I sort.
if (this.state.ships !== prevState.ships || this.state.searchExpression !== prevState.searchExpression) {
this.filterShips();
}
}
async fetchShips(url) {
try {
const response = await fetch(url);
if (response['status'] && response['status'] === 200) {
const json = await response.json();
const ships = json['results'].map(this.mapShip);
this.setState({
ships: this.state.ships.concat(ships),
nextUrl: json['next']
});
} else {
this.setState({ error: `${response['status']} ${response['statusText']}` });
}
} catch (error) {
if (error instanceof TypeError && error.message.includes('NetworkError')) {
this.setState({ error: `${error.name} ${error.message}` });
} else {
throw error;
}
}
}
filterShips() {
const filteredShips = this.state.ships.filter(ship => {
return Object.values(ship).some(shipProp => shipProp.includes(this.state['searchExpression']))
});
this.setState({
filteredShips: filteredShips
});
}
setSearchExpression(event) {
this.setState({ searchExpression: event.target.value });
}
setSort(event) {
if (event && event['currentTarget'] && event['currentTarget']['attributes'] &&
event['currentTarget']['attributes']['name'] && event['currentTarget']['attributes']['name']['nodeValue']) {
const columnName = event['currentTarget']['attributes']['name']['nodeValue'];
this.setState({
columnName,
reverseSort: columnName === this.state.columnName ? !this.state.reverseSort : false
});
}
}
sortShips() {
if (this.state.columnName) {
const sortedShips = this.state.ships.sort((a, b) => {
const propA = a[this.state.columnName];
const propB = b[this.state.columnName];
if (!isNaN(+propA)) {
return this.state.reverseSort ? Number(propB) - Number(propA) : Number(propA) - Number(propB);
}
return this.state.reverseSort ? propB.localeCompare(propA) : propA.localeCompare(propB);
});
this.setState({ ships: sortedShips });
}
}
/**
* Maps a ship to its name, manufacturer, cost and starship class.
* #param ship The ship to be mapped.
*/
mapShip(ship) {
const { name, manufacturer, cost_in_credits, starship_class } = ship;
return Object.assign(
{
name,
manufacturer,
cost_in_credits,
starship_class
},
{}
);
}
}
The shouldComponentUpdate() method works for both props and state. In your example, after the sort/filter events, the following method is fired by React. Try using,
shouldComponentUpdate(nextProps, nextState) {
return this.state.value != nextState.value;
}

React component infinite update

I have a React component and there will be an infinite update on the component whenever I connect it to redux and reference to properties from the store. If I just reference/connect one property from the store, the infinite update will not occur. It only happens when I connect two or more and I really cannot determine why this is happening.
When I add shouldComponentUpdate, it will actually not stop the infinite updating, it will just slow it down a lot. I really have no idea what is going on.
UPDATE:
Interestingly, if I just take out the componentDidUpdate function, it will not do an infinite loop and crash the browser.
import React, { Component } from 'react';
import { connect } from 'react-redux';
class UnreadMessages extends Component {
constructor(props) {
super(props);
this.state = {
messageReceived: false,
unreadMessages: 0
}
this.unreadMessages = 0;
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.lastViewedMessageTime === this.props.lastViewedMessageTime) {
this.setState({ messageReceived: true },
() => {
setTimeout(
() => {
this.setState({ messageReceived: false });
},
300
);
}
);
}
const conOne = prevProps.messages.length !== this.props.messages.length;
const conTwo = this.props.visible === false && window.innerWidth < 768;
if (conTwo) {
let index = this.props.messages.length - 1;
const conOne = this.props.messages[index].type === 'chat.msg';
const conTwo = this.props.messages[index].member_type === 'agent';
if (conOne && conTwo) {
this.setState({
unreadMessages: this.state.unreadMessages + 1
});
}
}
if (this.props.visible === true) {
this.setState({ unreadMessages: 0 });
}
}
render () {
let displayBadge = this.state.unreadMessages > 0 ? true : false;
console.log('DISPLAY BAD', displayBadge)
let pulse = this.state.messageReceived === true ? 'pulse' : '';
console.log('PULSE', pulse)
if (!displayBadge) {
return null;
}
return (
<span className={`msgBadge ${pulse}`}>{this.state.unreadMessages}</span>
)
}
}
function mapStateToProps(state) {
return {
lastViewedMessageTime: state.lastViewedMessageTime,
messages: state.chats.toArray(),
visible: state.visible
};
}
export default connect(mapStateToProps, null)(UnreadMessages);
As #Hamms pointed out in the comments, using this.setState inside componentDidUpdate is most likely going to cause issues.
Take the first few lines of componentDidUpdate.
componentDidUpdate(prevProps, prevState) {
if (prevProps.lastViewedMessageTime === this.props.lastViewedMessageTime) {
this.setState({ messageReceived: true },
...
If the state or any other prop apart from lastViewedMessageTime is changed and causes an update then the lastViewedMessageTime prop will be the same in the current props as in the prevProps.
This will cause a state change - therefore an update - and the same will be true about lastViewedMessageTime.
You are already stuck in an endless update cycle.
You should look into static getDerivedStateFromProps or memoization. These should help avoid these issues.

Resources