Modifying state in render - reactjs

I've got a SPA that has two parts, one of which allows you to choose a geographic area, and another which allows you to pick and choose countries. The country picker subscribes to the geographic area updates so that it can only put up countries that overlap your chosen geographic area. The problem happens when the render method on the CountryPanel attempts to remove countries from the selection if they are no longer in the selected geographic area. So if you select some countries in the eastern hemisphere in the CountryPanel, then go to the GeographicPanel and select an area in the western hemisphere, and the CountryPanel attempts to remove the selected countries from the store, I get the following error in the console:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
The problem occurs in a call to this.props.dispatch(removeCountry(c)) in the following code:
connect((store) => {
return {
min_lat_val: store.session.min_latitude_val,
min_lat_ns: store.session.min_latitude_ns,
max_lat_val: store.session.max_latitude_val,
max_lat_ns: store.session.max_latitude_ns,
min_long_val: store.session.min_longitude_val,
min_long_ew: store.session.min_longitude_ew,
max_long_val: store.session.max_longitude_val,
max_long_ew: store.session.max_longitude_ew,
sel_countries: store.session.selected_countries,
countries: store.extents.countries,
}
})
export class CountryPanel extends React.Component {
constructor(props) {
super(props)
}
render() {
let country_keys = Object.keys(this.props.countries).sort()
if (country_keys.length < 1) {
return <div class="panel-body">
<h3>Countries</h3>
<p className="strong">Country data has not loaded yet!</p>
</div>
}
let countries = []
if (this.props.min_lat_val && this.props.max_lat_val &&
this.props.min_long_val && this.props.max_long_val) {
let tuple = []
country_keys.forEach((c, i) => {
const country = this.props.countries[c]
if (extentsOverlap(
this.props.min_lat_val,
this.props.min_lat_ns,
this.props.max_lat_val,
this.props.max_lat_ns,
this.props.min_long_val,
this.props.min_long_ew,
this.props.max_long_val,
this.props.max_long_ew,
country)) {
tuple.push(<CountryCheck key={c} country={country} />)
if (tuple.length > 2) {
countries.push(tuple)
tuple = []
}
} else {
if (this.props.sel_countries.has(c)) {
this.props.dispatch(removeCountry(c))
}
}
})
if (tuple.length > 0) {
countries.push(tuple)
}
} else {
let tuple = []
country_keys.forEach((c, i) => {
tuple.push(<CountryCheck key={c} country={this.props.countries[c]} />)
if (tuple.length > 2) {
countries.push(tuple)
tuple = []
}
})
if (tuple.length > 0) {
countries.push(tuple)
}
}
const trs = countries.map((c, i) => <tr key={i}>{c}</tr>)
return <div class="panel-body">
<h3>Countries</h3>
<p>If you leave all the checkboxes unchecked, it will return waypoints in all countries that meet all the other criteria. If you select US or Canada, you will be able to select states or provinces from a further list below, or leave those checkboxes empty to select the whole country.</p>
<p>The two letter country codes you see below are from FIPS 10.4, not the more common ISO 3166, which is why you might not recognize some of these codes.</p>
<p><strong>Note: Non-USA data is not as current as USA data, so carefully check this data.</strong> Actually, you should always check all this data against current official data sources.</p>
<table class="table table-striped table-bordered">
<tbody>
{trs}
</tbody>
</table>
</div>
}
}
If I try what's referred to as a "workaround and proof of concept" in https://stackoverflow.com/a/43438560/3333 and change the offending line to setTimeout(() => this.props.dispatch(removeCountry(c)), 0) the warning goes away. Is that the best solution?

Related

How can I create a parent html element by appending sub element from an object?

In my react app I need to return a line which will be created based on a list.
Here is the object,
searchCriteria: {
op_company: "039",
doc_type: "ALL"
}
and in my UI, i need to show it as a paragraph with bold values. So the hard coded code would be like below
<p>Download request for op_company: <b>{searchCriteria.op_company}</b>, doc_type: <b>{searchCriteria.doc_type}</b></p>
But the object(searchCriteria) will be changed based on the user request. So I tried like below.
const getSearchCriteria = (criteria) => {
let searchCriteria = []
searchCriteria.push('Download request for')
Object.keys(criteria).forEach((key) => {
if(criteria[key] !== '') {
searchCriteria.push(` ${key}: ${criteria[key]},`)
}
});
return searchCriteria;
}
return (
<p>
{getSearchCriteria(searchCriteria).map((item) => <span key = {item}>{item}</span>)}
</p>
);
here i'm getting the expected output. But I can't get the value as bold (highlighted). Is there another way to directly deal with html elements?

compare an object inside a map function React

I try to render a mapped list of activity based on props. Layout of those props are:
completedActivity:
message:
status:
time:
type:
progressId:
time:
userId:
I need to compare the completedActivity.progressId to another set of props.
logData:
timestamp:
taskRunId:
userName:
I need to see where completedActivity.progressId = logData.taskRunId. If they are the same I need to grab the logData.userName.
Here is the code to render out the activities. It is working just, but I need to add additional info to them. As of right now it will render activity.userId, which is a bind user and not the actual user. If they are not the same, it needs to render the bind user.
const ActivityList = props => {
const activityList = props.data.completedActivity.map(activity => (
<li
class="collection-item"
onClick={() =>
activity.messageDetails
? props.toggleMessageDetails(true, activity.messageDetails)
: false
}
>
<p>{activity.type}</p>
<p className="message">{activity.message}</p>
{/*
displays who last ran that activity and when they did. Need to come up with a better naming solution.
*/}
<div class="whodiditandwhen">
<span>{activity.userId}
</span>
{/*<span>{activity.userId}</span>*/}
<span>{new Date(activity.time).toLocaleString()}</span>
</div>
{/*
this will allow the user to click on the icon and see more detailed info on the activity. NOT ALL ACTIVITES HAVE THIS
*/}
{activity.messageDetails}
</li>
));
return (
<ul className="activity-list">{activityList}
</ul>
);};
Here is a screenshot of how it is rendered now.
Thank you
This is what I tried:
const userNameLog = props.data.completedActivity.map(activity => {
let result = props.logData.find(log => log.taskRunID === activity.progressId)
let userName = ""
if(result === undefined){
userName = activity.userId
} else {
userName = result
}
console.log(userName)
}
)
This works to some degree except it gets rendered multiple times.
I was able to solve this issue inside of the axios call that is getting the data before the page renders it.
export function getActivityUpdates(options, updateProgress) {
axios
.post(URL, qs.stringify(options))
.then(response => {
// Null out saved currentActivityID if no live activity.
handleCurrentActivityStorage(response.data.liveActivity);
let changedIDs = [];
let dataResponse = response.data;
dataResponse.userId = ". . ."
/*
iterate over the completed activity and the log data.
compare progress id to taskRunId.
logData is from highest to lowest id.
completedActivity is also from high to low but puts parents above children.
check and make sure that it has a bind user written to it.
Then if taskrunid is = to or less than the progressId. change it and add it to the changedIDs array.
the ChangedIds array makes sure that ids are not iterated over multiple times.
set the userID to the actual username found in the logs.
*/
dataResponse.completedActivity.forEach(data => {
options.logData.forEach(log => {
//
if (
data.userId === options.bindUsername &&
!changedIDs.includes(data.progressId) &&
(log.taskRunID === data.progressId ||
log.taskRunID < data.progressId)) {
changedIDs.push(data.progressId)
data.userId = log.magUserName;
}
})
});
updateProgress(dataResponse);
// Exit function if a task is not running
if (!response.data.liveActivity.length) {
return;
}
// Get updated data every second until task is complete
setTimeout(
getActivityUpdates.bind(null, options, updateProgress),
1000
);
})
.catch(() => showErrorMessage(options.command));
}

Mapping over function that sets state in react

I use the function changeCheck to check and uncheck specific components.
When I use the function, it works correctly.
this.props.team is a list of all of the teams.
The goal of changeAllTeams is to be able to check and uncheck all of the teams that have a specific league.
In this example I want to change all of the teams that have a league acronym of NFL:
this.state = {
checked: [],
checkedTeams: [],
teamObject: [],
queryString: [],
accordionStatus: [true, true, true]
}
changeAllTeams = (leagueType) => {
this.props.team.map(
(v, i) => {
if(v.league.acronym === 'NFL'){
this.changeCheck(i, v.team_name, v)
}
}
)
}
componentDidUpdate(){
console.log('checked', this.state.checked)
console.log('team object', this.state.teamObject)
console.log('props team object', this.props.teamObject)
this.props.changeLeagues(this.props.league, this.props.checkedLeagues, this.state.checkedTeams, this.state.queryString, this.state.teamObject, this.state.checked)
}
changeCheck = (index, name, teamObject) => {
//updates checked team state
if(!this.state.checkedTeams.includes(name)){
this.state.checkedTeams[this.state.checkedTeams.length] = name
this.setState({ checkedTeams: [...this.state.checkedTeams] })
//sets team object with new team object
this.state.teamObject[this.state.teamObject.length] = teamObject
this.setState({ teamObject: this.state.teamObject })
} else {
console.log(name)
newChecked = this.state.checkedTeams.filter(v => { return v !== name})
this.setState({ checkedTeams: newChecked })
//removes team object and sets new state
newObjectChecked = this.state.teamObject.filter(v => { return v.team_name !== teamObject.team_name})
this.setState({ teamObject: newObjectChecked })
}
//updates checkbox for specific space
this.state.checked[index] = !this.state.checked[index]
this.setState({ checked: this.state.checked })
this.forceUpdate()
}
When I map over the array in changeAllTeams, only the last object in the array takes effect.
The state for checked updates for everything, but the state for checkedTeams and teamObject does not.
This video may help to understand further:
https://streamable.com/q4mqc
Edit:
This is the structure of the objects in this.props.team:
I don't have your code but I'm pretty sure that the problem is that you didn't provide a unique id for each item (remember that it's most of the time a bad idea to use map index for your items). The thing that you should do is to give each item a unique key and call the function based on that id.
There are a few places where you mutate the contents of this.state. That could cause React to be unable to detect changes in the state because the new and old state are referencing the same object. I would recommend that you don't mutate any state and instead create clones of the data object before passing the new data to setState()

map function with table is not working in React

Here's the code that I want to render table data with map function in the child component:
const serviceFeeTableData = this.props.serviceFeeTableData;
const serviceFeeDataTable = serviceFeeTableData.map(serviceFeeDataRow => {
return (
<tr>
<td>{`${serviceFeeDataRow.periodFrom} - ${serviceFeeDataRow.periodTo}`}</td>
<td>{serviceFeeDataRow.serviceFeeType}</td>
<td>{serviceFeeDataRow.serviceFee}</td>
<td>{serviceFeeDataRow.refundableStatus}</td>
<td>
<Button variant="primary" size="sm">Edit</Button>
<Button variant="danger" size="sm">Remove</Button>
</td>
</tr>
)
})
As you shown here https://jsfiddle.net/EshanRajapakshe/Lvh6q84t/ the problem is that the state of the parent component only contains one element (and also the response seems to contain only one element)
componentWillReceiveProps(nextProps) {
if (nextProps.getServiceFeeData && nextProps.getServiceFeeData.length > 0) {
this.setState({
periodFrom: nextProps.getServiceFeeData.periodFrom,
periodTo: nextProps.getServiceFeeData.periodTo,
serviceFeeType: nextProps.getServiceFeeData.serviceFeeType,
serviceFee: nextProps.getServiceFeeData.serviceFee,
refundableStatus: nextProps.getServiceFeeData.refundableStatus
})
}
}
You should check the URL you are calling to get a list of data (if you are trying to get the list).
Note also that componentWillReceiveProps is deprecated as described here https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops
const object = {0: {name: 'Stepan'}, 1: {name: 'Oleg'}} // You can not map Object keys
const array = Object.keys(object); // create array from object
array.map(key => object[key]);

React - Not able to update list of product component

I made a list of product which contains name, price etc.. properties. Then I created a simple search box and I am searching my products based on product name . the search result returns the correct object but the UI is not getting updated with that result.Initially, I am able to see the list but after searching it is not getting updated. I am new to react SO need some help. here is my code
OnInputChange(term)
{
let result= this.products.filter(product=>{
return product.name==term;
});
console.log(result);
let list=result.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
console.log(list);
this.setState({listOfProducts:list});
}
render()
{
this.state.listOfProducts=this.products.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
return <div>
<input onChange={event=>{this.OnInputChange(event.target.value)}}/>
<ul>{this.state.listOfProducts}</ul>
</div>
}
}`
this.products.filter should probably be this.state.products.filter
When referring to a Components method you say this.onInputChange but when referencing state you must say this.state.products because this.products doesn't exist.
I would also move this:
let list=result.map((product)=>
{
return <li key={product.price}>{product.name}</li>
});
to your render statement. so your onchange handler could be:
OnInputChange(term)
{
let result= this.products.filter(product=>{
return product.name==term;
});
this.setState({listOfProducts:result});
}
and then you
render(){
return(
{this.state.listOfProducts.map(product => {
return <li key={product.price}>{product.name}</li>
})}
)
}
hope that helps! If your problem persists please share your entire code so I can better understand the issue.

Resources