component is getting loaded on 2nd iteration in react - reactjs

I am using react plus d3 for my graph. now I am merging two different arrays to populate y graph. but one of the array is showing undefined to me, I checked in reducer both array gets the data properly. but if I go and come back again to the page it is working fine. so means only whenever my DOM is clear it is giving me problem. below is my reducer
const VolumeQuantity = MixMergeGroup.map(data => {
return data.map
.map(mixdata => mixdata.world_volume * sum)
.reduce((prev, curr) => prev + curr);
});
var VolumeQuantityGraph = [];
var tempObj_world = null;
MixMergeGroup.map((data, index) => {
tempObj_world = {};
tempObj_world['world_name'] = data.key;
tempObj_world['world_volume'] = VolumeQuantity[index];
VolumeQuantityGraph[index] = tempObj_world;
});
var overGraph = [];
currentYearData.map((data, index) => {
let temp = {};
temp.key = data.name;
let values = {};
values.value = data.forcasted_volume;
values.label = data.year + ' Volume';
temp.values = [values];
overGraph[index] = temp;
});
return {...state,overallGraph: overGraph,MixVolumeGraph:VolumeQuantityGraph}
here is my component where I use this value
componentWillReceiveProps(nextProps) {
if (
this.state.graphData.length == 0 &&
nextProps.MixVolumeGraph !== undefined
) {
nextProps.overallMixData.forEach(function(element) {
let filtereddata = nextProps.MixVolumeGraph.filter(
data => data.name === element.name
);
element.world_volume = filtereddata[0].volume;
});
console.log('nextprops', nextProps);
this.setState({ graphData: nextProps.overallMixData });
}
}
can please anyone let me know why on first instance its not giving me any value?

from docs React doesn't call componentWillReceiveProps with initial props during mounting. It only calls this method if some of component's props may update. Calling this.setState generally doesn't trigger componentWillReceiveProps.
so you need to call this method in componentdidmount to start it on first mount
edit: sample:
processData(nextProps) {
if (
this.state.graphData.length == 0 &&
nextProps.MixVolumeGraph !== undefined
) {
nextProps.overallMixData.forEach(function(element) {
let filtereddata = nextProps.MixVolumeGraph.filter(
data => data.name === element.name
);
element.world_volume = filtereddata[0].volume;
});
console.log('nextprops', nextProps);
this.setState({ graphData: nextProps.overallMixData });
}
}
componentWillReceiveProps(nextProps) {
processData(nextProps);
}
componentDidMount() {
processData(this.props);
}

I had used componentDidMount but it was giving me error Cannot read property 'props' of undefined this is because I was using foreach loop. I changed into map it is working now below is my update loop
this.props.overallWorldMixData.map(data => {
let filtereddata = this.props.MixVolumeGraph.filter(
dataName=> dataName.name === data.name
);
data.volume = filtereddata[0].volume;
});
I don't know the reason and I know it is not immutable solution, if anyone can clarify this I would be glad

Related

I cant update my component state.. Do somebody understand how it fix?

I cant understand why my renderMovies() function dont wanna update my component state.data and i cant render component on my screen ?!
Everithing goes ok until renderMovies function.. I think this.setState(newState) in my fetchPostData function is working incorrect... Do somebody know how to fix it? I tried different ways but i cant solve this issue.
class Movies extends React.Component {
constructor(props) {
super(props)
this.state = { data: {}}
this.fetchPostData = this.fetchPostData.bind(this)
this.renderMovies = this.renderMovies.bind(this)
this.populatePageAfterFetch = this.populatePageAfterFetch.bind(this)
}
componentDidMount() {
this.fetchPostData()
}
fetchPostData() {
fetch(`http://localhost/reacttest/wp-json/wp/v2/movies?per_page=100`)
.then(response => response.json())
.then(myJSON => {
let objLength = Object.keys(myJSON).length
let newState = this.state;
for (let i = 0; i < objLength; i++) {
let objKey = Object.values(myJSON)[i].title.rendered;
// console.log(objKey)
let currentMovie = newState.data[objKey];
currentMovie = {};
currentMovie.name = Object.values(myJSON)[i].title.rendered;
currentMovie.description = Object.values(myJSON)[i].content.rendered;
currentMovie.featured_image = Object.values(myJSON)[i]['featured_image_url'];
currentMovie.genre = Object.values(myJSON)[i]['genre'];
}
this.setState(newState)
})
}
renderMovies() {
if(this.state.data) {
const moviesArray = Object.values(this.state.data)
console.log(moviesArray)
return Object.values(moviesArray).map((movie, index) => this.populatePageAfterFetch(movie, index))
}
}
populatePageAfterFetch(movie, index) {
if (this.state.data) {
return (
<div key={index} index={index}>
<h2>{movie.title}</h2>
<h3>{movie.genre}</h3>
<p>{movie.description}</p>
</div>
)
}
}
render() {
return (
<div>
<h1>Movies</h1>
<div>{this.renderMovies()}</div>
</div>
)
}
}
When i try to console.log(moviesArray) it show me:
Issue
You save current state into a variable named newState, never update it, and then save the same object reference back into state. React state never really updates.
let newState = this.state;
for (let i = 0; i < objLength; i++) {
...
}
this.setState(newState);
Additionally you mutate state
let currentMovie = newState.data[objKey];
currentMovie = {};
But this doesn't work either since initial state is an empty object so newState.data[objKey] is aways undefined. (so nothing is ever actually mutated)
Solution
It appears as though you intended to map the myJSON data/values into movie objects to update this.state.data. May I suggest this solution. The key is to always create new object references for any object you update.
fetchPostData() {
fetch(`http://localhost/reacttest/wp-json/wp/v2/movies?per_page=100`)
.then(response => response.json())
.then(myJSON => {
this.setState(prevState => ({
// array::reduce over the JSON values
data: Object.values(myJSON).reduce((movies, movie) => {
// compute movie key
const name = movie.title.rendered;
return {
...movies,
[name]: {
...movies[name], // copy any existing movie properties
// merge in new/updated properties
name,
description: movie.content.rendered,
featured_image: movie.featured_image_url,
genre: movie.genre,
},
}
}, { ...prevState.data }) // use previous state as initial value for reduce
}))
})
}

React componentDidMount and setState not seeming to cause a rerender to show my component [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
/*After fetching data and setting to state, I am attempting to generate an array of jsx items to display. But the array is showing as empty and nothing is rendering.
Tried, hardcoding and this works. Tried loggin the data and it does show up that state is receving the data.
Removed my authorization token from the code below.
*/
import React, { Component } from 'react';
import AddCard from '../AddCard/AddCard.js';
import KanCard from '../KanCard/KanCard.js';
import './CardHolder.scss';
export default class CardHolder extends Component {
constructor(props){
super(props);
this.state = {
stories: [],
inProgressTasks: [],
completeTasks: [],
};
}
componentDidMount(){
// let id = this.props.id;
let id = 168881069;
let completetask = [];
let progresstask =[];
var data = null;
var xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
let parsedResponse = JSON.parse(this.responseText);
for( let taskResponse of parsedResponse ){
let task = {
key:taskResponse.id,
id:taskResponse.id,
story_id:taskResponse.story_id,
complete:taskResponse.complete,
description: taskResponse.description,
}
if(!taskResponse.complete){
progresstask.push(task)
} else {
completetask.push(task);
}
}
}
});
this.setState({inProgressTasks:progresstask, completeTasks:completetask})
xhr.open("GET", `https://www.pivotaltracker.com/services/v5/projects/2401708/stories/${id}/tasks`);
xhr.setRequestHeader("X-TrackerToken", "296912a3ff4ddcda26b4a419934b3051");
xhr.setRequestHeader("Accept", "*/*");
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("cache-control", "no-cache");
xhr.send(data);
}
render(){
let completeTasks = this.state.completeTasks.map((task)=>{
return (
<KanCard
key = {task.id}
id = {task.id}
story_id = {task.story_id}
complete = {task.complete}
description = {task.description}
/>
)
})
let inProgressTasks = this.state.inProgressTasks.map((task)=>{
return (
<KanCard
key = {task.id}
id = {task.id}
story_id = {task.story_id}
complete = {task.complete}
description = {task.description}
/>
)
})
console.log(inProgressTasks)
return (
<div className='holder'>
<h2> {this.props.title} </h2>
<div>
<h3>In Progress</h3>
{inProgressTasks}
</div>
<div>
<h3>Complete</h3>
{completeTasks}
</div>
<AddCard />
</div>
)
}
}
There are a few issues with the way you're setting your call up and updating your state.
First, make sure you update your state when you get your response back, after all, it's an asynchronous request and you need to wait to get something, then update your state.
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
let parsedResponse = JSON.parse(this.responseText);
for( let taskResponse of parsedResponse ){
let task = {
key:taskResponse.id,
id:taskResponse.id,
story_id:taskResponse.story_id,
complete:taskResponse.complete,
description: taskResponse.description,
}
if(!taskResponse.complete){
progresstask.push(task)
} else {
completetask.push(task);
}
}
this.setState({inProgressTasks:progresstask, completeTasks:completetask})
}
});
Second, remember you're inside a class, so this.readyState and this.responseText are referencing the class when you use the keyword this, not your XHR object as you're expecting it to. In order to make this work, you should change the readystatechange's callback function to a lambda function, then replace the this for xhr, yet you should keep the this that actually makes a reference to your class in the this.setState:
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4) {
let parsedResponse = JSON.parse(xhr.responseText);
for( let taskResponse of parsedResponse ){
let task = {
key:taskResponse.id,
id:taskResponse.id,
story_id:taskResponse.story_id,
complete:taskResponse.complete,
description: taskResponse.description,
}
if(!taskResponse.complete){
progresstask.push(task)
} else {
completetask.push(task);
}
}
this.setState({inProgressTasks:progresstask, completeTasks:completetask})
}
});
I tried to replicate your issue here:
I'm hitting a dumb api and updating my state with the response data. Play around with it. Change the readystatechange's callback function from being a lambda function to being an anonymous function as you initially set up and see what happens.
To read more on the this problem, take a look at this question.

How to use a method in render reactjs?

i have a method set_data which is used to set data based on id. I know it could be easy to call this set_data in componentdidupdate when id changes. However in doing so it doesnt set some state variables in the parent component.
To get rid of that want to call set_data method in render . However since this set_data method sets state of data it enters into an infinite loop in render . Also cannot provide a condition (like prevprops.id!== this.props.id) to execute set_data method.
To prevent it thought of using this set_data method not to set state at all. and can call this set_data method in render.
Below is the code,
export default class child extends React.Component {
state = {
query: '',
data: null,
};
empty_id = 0xffffffff;
componentDidMount() {
this.set_open_data();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.set_data();
}
}
set_data = () => {
if (!this.props.info) {
return;
}
if (this.props.id === this.empty_id) {
this.setState({data: null});
return;
}
let data = {
info: [],
values: [],
};
const info = this.props.info;
for (let i=0, ii=info.length; i < ii; i++) {
if (info[i].meshes.includes(this.props.id)) {
const info = info[i].info;
const values = info[i].values;
data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
this.setState({data: this.filter_data(data, this.state.query)});
};
render = () => {
const shown_data= this.state.data;
/* i want to call set_data method here*/};}
Could someone help me solve this. Thanks.
You can't call setData there, because that would be anti-pattern. It will trigger a loop that will continuously render as well as keeps setting state.
You can probably rewrite the component this way:
export default class child extends React.Component {
state = {
query: ''
};
empty_id = 0xffffffff;
componentDidMount() {
this.set_open_data();
}
set_data = () => {
let data = {};
if (!this.props.info) {
return data;
}
if (this.props.id === this.empty_id) {
return data;
}
let data = {
info: [],
values: [],
};
const info = this.props.info;
for (let i=0, ii=info.length; i < ii; i++) {
if (info[i].meshes.includes(this.props.id)) {
const info = info[i].info;
const values = info[i].values;
data = {
info: typeof info === 'string' ? info.split('\r\n') : [],
values: values ? values : [],
};
break;
}
}
data = this.filter_data(data, this.state.query);
return data;
};
render = () => {
const shown_data= this.state.data;
const data = this.set_data();
/* i want to call set_data method here*/};}
In this, we are not setting data in the state. For every new ID, it will get new data and will compute it from render thereby avoiding antipattern. I have also removed componentDidMount, since we are doing computation in render. Note: This solution means taking away data from the state, if you are not using data anywhere before render, this will work.
Let me know if this helps.

How to solve Error Use object destructuring prefer-destructuring - React

I am stuck with an ugly issue which I am unable to resolve. I am beginner in React.
This is my Code
handleCheckChildElement(event) {
let items = this.state.items;
items.forEach(items = () => {
if(items.value === event.target.value) {
items.isChecked = event.target.checked;
}
});
this.setState({ items });
}
This is the image of the error -
Use below code for line #55 :
let {items}= {...this.state};
Read more here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring
Your code can be improved to something like below. Please find relevant comments in the code below for your better understanding
handleCheckChildElement(event) {
const { items } = this.state; //extract state values like this to a const variable
const newItems = items.map(item => { //do map on items because map returns a new array. It’s good practice to use .map than forEach in your case
if(item.value === event.target.value) {
item.isChecked = event.target.checked;
return item; //return updated item object so that it will be pushed to the newItems array
}
return item; // return item because you need this item object as well
});
this.setState({ items: newItems}); //finally set newItems array into items
}
handleCheckChildElement(event) {
const items = this.state.items;
const filtered = items.filter(item => item.value === event.target.value)
.map(item => item.isChecked = event.target.checked) ;
this.setState({items : [...filtered] );
}

How to update state in react.js?

I'd like to ask if there is any better way to update state in react.js.
I wrote this code below but just updating state takes many steps and I wonder if I'm doing in a right way.
Any suggestion?
How about immutable.js? I know the name of it but I've never used it and don't know much about it.
code
toggleTodoStatus(todoId) {
const todosListForUpdate = [...this.state.todos];
const indexForUpdate = this.state.todos.findIndex((todo) => {
return todo.id === todoId;
});
const todoForUpdate = todosListForUpdate[indexForUpdate];
todoForUpdate.isDone = !todoForUpdate.isDone;
this.setState({
todos: [...todosListForUpdate.slice(0, indexForUpdate), todoForUpdate, ...todosListForUpdate.slice(indexForUpdate + 1)]
})
}
You are using an extra step that you don't need. Directly setting value to the cloned object and restting back to state will work
toggleTodoStatus(todoId) {
const todosListForUpdate = [...this.state.todos];
const indexForUpdate = this.state.todos.findIndex((todo) => {
return todo.id === todoId;
});
todosListForUpdate[indexForUpdate].isDone = !todosListForUpdate[indexForUpdate].isDone;
this.setState({
todos: todosListForUpdate
})
}

Resources