How to sum the value only once in react redux - javascript

I would like to know in my scenario: After receiving props, the function gets called in the compoentdidupdate method, in which am summing up the amount if id is same, But it keeps on adding the values multiple times on load. how to resolve this.
class Data extends React.PureComponent{
constructor(props){
super(props);
this.state={
total: "";
}
}
componentDidMount() {
this.callFetch();
}
callFetch(){
this.props.dispatch(getData("all"));
this.props.dispatch(newData("new"));
}
componentDidUpdate(){
const { alldata, newdata } = this.props.query;
const obj =[...alldata, ...newdata];
const result=[];
if(obj.length > 0) {
obj.forEach(function (o) {
var existing = result.filter(function (i) { return i.id=== o.id})[0];
if (!existing)
result.push(o);
else
existing.amount+= o.amount;
});
this.setState({total: result})
}
}
render(){
return(
this.state.total.length > 0 ?
this.state.total.map(e=>{
<div>price:{e.amount}</div>
}) : ""
)
}
props am receiving is below, but in output am receiving 1200, and keeps on increasing,
alldata= [{
id: 1,
amount: 200
}, {
id: 2,
amount: 400
}]
newdata= [{
id: "1",
amount: 400
}]
expected output:
price: 600
price: 400

Hey #miyavv Have a look at the below piece of code. Comparing prevProps and current props should help. It is general technique used in reactjs to solve non-needed runs in componentDidUpdate.
class Data extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
total: ""
};
}
componentDidMount() {
this.callFetch();
}
callFetch() {
this.props.dispatch(getData("all"));
this.props.dispatch(newData("new"));
}
componentDidUpdate(prevProps) {
const { alldata, newdata } = this.props.query;
const obj = [...alldata, ...newdata];
const result = [];
// new Line added
const { alldata: prevAllData, newdata: prevNewData } = prevProps.query;
if (prevAllData !== alldata || prevNewData !== newdata) {
if (obj.length > 0) {
obj.forEach(function(o) {
var existing = result.filter(function(i) {
return i.id === o.id;
})[0];
if (!existing) result.push(o);
else existing.amount += o.amount;
});
this.setState({ total: result });
}
}
}
render() {
return this.state.total.length > 0
? this.state.total.map(e => {
<div>price:{e.amount}</div>;
})
: "";
}
}
Let me know if it helps !!. Thanks

That’s because you are dispatching two actions which will result to call componentDidUpdate multiple times. And in your componentDidUpdate method there’s no conditioning telling that your data is already fetched.
You could call only one action that will further dispatch two actions you are dispatching in callFetch method. You could also keep a flag which will tell whether the data is yet fetched or not.
If you are using redux-thunk, than it can be easily achieved. You can define one action which will be dispatched in callFetch method. And in your newly defined action you can dispatch your mentioned two actions.

Related

add the data every time the funtion is called into a array object of State

i am having this handleCheckClick funtion witch gets Data i want to store the data into a state every time the handleCheckClick funtion is called so after many times handleCheckClick is called the state should look like the object array below
handleCheckClick = (e, stateVal, index) => {
let prevState = [...this.state[stateVal]];
prevState[index].positive = e.target.checked;
console.log(index);
this.setState({ [stateVal]: prevState });
var date = moment(this.state.dateState).format("YYYY-MM-DD");
const { id, checked } = e.target.dataset;
console.log(stateVal);
if (e.target.checked) {
var checkbox = "True";
} else {
var checkbox = "False";
}
const Data = {
boolvalue: checkbox,
date: date,
userid: id,
};
this.setState({ datastate : Data });// something like this
};
after many times the handleCheckClick funtion is called the state must look like this
[
{
"date" : "2022-02-15",
"userid" : 6,
"boolvalue" : true
},
{
"date" : "2022-02-15",
"userid" : 5,
"boolvalue" : false
},
{
"date" : "2022-02-15",
"userid" :7,
"boolvalue" : true
},
{
"date" : "2022-02-15",
"userid" : 11,
"boolvalue" : true
},
{
"date" : "2022-02-15",
"id" : 4,
"boolvalue" : false
}
]
pls create a codesandbox example
https://codesandbox.io/s/recursing-wind-mjfjh4?file=/src/App.js
You have to take your data and call setState using the existing data merged with the new Data object. The merging can be done using ... (spread) operator. Here's the code with the relevant parts:
class Component extends React.Component {
handleClick = (e) => {
// build up a new data object:
if (e.target.checked) {
var checkbox = "True";
} else {
var checkbox = "False";
}
const { id } = e.target.dataset
var date = moment(this.state.dateState).format("YYYY-MM-DD");
const Data = {
boolvalue: checkbox,
date: date,
userid: id,
};
// set the new state, merging the Data with previous state (accesible via this.state)
// this creates a new array with all the objects from this.state.datastate and the new Dataobject
this.setState({
datastate: [...this.state.datastate, Data]
})
}
// log the state on each update for seeing changes.
componentDidUpdate() {
console.log('Component did update. State:', this.state)
}
// Rendering only a button for showcasing the logic.
render() {
return <button onClick={this.handleClick}></button>
}
constructor(props) {
super(props)
// initialise an empty state
this.state = {
datastate: [],
dateState: new Date()
}
}
}
Edit for removing an element when unchecked:
You can remove a certain element by its id in the onClick handler when the box is unchecked:
class Component extends React.Component {
handleClick = (e) => {
// get id first.
const { id } = e.target.dataset
// if element is not checked anymore remove its corresponding data:
if(e.target.checked === false) {
// remove the element by filtering. Accept everything with a different id!
const update = this.state.datastate.filter(d => d.userid !== id)
this.setState({
datastate: update
})
// end handler here..
return
}
// if we get here, it means checkbox is checked now, so add data!
var date = moment(this.state.dateState).format("YYYY-MM-DD");
const Data = {
// it is better to keep the boolean value for later use..
boolvalue: e.target.checked,
date: date,
userid: id,
};
// set the new state, merging the Data with previous state (accesible via this.state)
// this creates a new array with all the objects from this.state.datastate and the new Dataobject
this.setState({
datastate: [...this.state.datastate, Data]
})
}
// log the state on each update for seeing changes.
componentDidUpdate() {
console.log('Component did update. State:', this.state)
}
// Rendering only a button for showcasing the logic.
render() {
return <button onClick={this.handleClick}></button>
}
constructor(props) {
super(props)
// initialise an empty state
this.state = {
datastate: [],
dateState: new Date()
}
}
}
Feel free to leave a comment

setState() in Reactjs updating multiple properties instead of one

When I update 'classProficienciesChoices' in my state using setState() it is updating not only that property, but also where I derived the 'classProficienciesChoices' info from in the 'classSelected' property, AND ALSO from where I derived the classSelected info from in the 'classesInfo' property.
The same function I update 'classProficienciesChoices' I also update 'classProficiencies', and it updates properly in the one property I tell it to, and not the elements where the information was derived from.
Any insight would be helpful. The Create component has other components nested and none of them have state and only use props passed. There are navigation, selection, and information display components nested.
import React, { Component } from 'react'
import Create from './Create'
class App extends Component {
constructor(props) {
super(props);
const url = 'http://www.dnd5eapi.co/api/';
fetch(url + 'classes')
.then(result => result.json())
.then(result => { this.setState({ classes: result, }, this.getInfo(result)) });
}
state = {
classes: {}, //assigned value from API call in constructor
classesInfo: [], //assigned value from API call in getInfo()
classSelected: {}, //assigned a value derived from classInfo in displayClassInfo()
classProficiencies: [], //assigned a value derived from classSelected in setStartingProficiencies()
classProficienciesChoices: [], //assigned a value derived from classSelected in setStartingProficiencies()
}
getInfo(data) {
let info = []
const url = 'http://www.dnd5eapi.co'
for (var i = 0; i < data.results.length; i++) {
fetch(url + data.results[i].url)
.then(result => result.json())
.then(result => info.push(result))
}
this.setState({ classesInfo: info, })
}
}
setStartingProficiencies(chosenClass) {
const profs = chosenClass.proficiencies.map((prof) => {
return prof;
});
const proChoice = chosenClass.proficiency_choices.map((choices) => {
return choices;
});
this.setState({ classProficiencies: profs, classProficienciesChoices: proChoice, });
}
addProficiency = (proficiencyName) => {
const { classProficienciesChoices } = this.state
// classProficienciesChoices: [
// { choose: 2, type: 'proficiencies', from: [{ name: 'someName', url: 'someUrl' }, { name: 'someName', url: 'someUrl' }] },
// ]
// different classes have more objects in the parent array
let newChoiceArray = classProficienciesChoices.map((choices) => {
return choices
})
for (var i = 0; i < newChoiceArray.length; i++) {
for (var j = 0; j < newChoiceArray[i].from.length; j++) {
if (newChoiceArray[i].from[j].name === proficiencyName) {
let newChoices = newChoiceArray[i].from.filter(function (proficiency) { return proficiency.name !== pIndex })
let newProficiency = newChoiceArray[i].from.filter(function (proficiency) { return proficiency.name === pIndex })
newChoiceArray[i].from = newChoices //I think this is the problem
this.setState(state => ({
classProficiencies: [...state.classProficiencies, newProficiency[0]],
proficienciesChoices: newChoiceArray,
}))
}
}
}
}
displayClassInfo = index => {
const { classesInfo } = this.state
for (let i = 0; i < classesInfo.length; i++) {
if (classesInfo[i].index === index) {
const classSelected = classesInfo.filter(function (cClass) { return cClass.name === classesInfo[i].name })
this.setState({ classSelected: classSelected[0], isClassSelected: true }, this.setStartingProficiencies(classSelected[0]),)
break;
}
}
}
render() {
const { classes, classesInfo, classSelected, isClassSelected, classProficiencies, classProficienciesChoices } = this.state
return (<Create classes={classes} classesInfo={classesInfo} displayClassInfo={this.displayClassInfo} classSelected={classSelected} isClassSelected={isClassSelected} category='classes' classProficiencies={classProficiencies} classProficienciesChoices={classProficienciesChoices} addProficiency={this.addProficiency} />);
}
}
export default App
You call setState four times, of course it will update the state multiple times.
SOLVED!!! Stumbled across turning the array into a string and then back. It breaks the reference and creates an entirely new array. Reference type got me.
setStartingProficiencies(chosenClass) {
const proficiencies = JSON.parse(JSON.stringify(chosenClass.proficiencies))
const proficienciesChoices = JSON.parse(JSON.stringify(chosenClass.proficiency_choices))
this.setState({ classProficiencies: proficiencies, classProficienciesChoices: proficienciesChoices, });
}

setState is not working properly in react native

i want to create a object with multiple object. the data is something like this
dataList = [{inputFieldId: 1, dataField:{...}, data: '120'}, {inputFieldId: 2, dataField:{...}, data: '120'} ]
what is want like this.
res = [{1: '120'}, {2: '120'}]
i write a code for this but its giving me the last object data only.
constructor(){
super()
this.state = {
inputValue:{},
datalist = [],
}
}
async componentWillMount(){
for(var key in dataList){
this.setState({
inputValue: {
...this.state.inputValue,
[dataList[key].inputFieldId]: dataList[key].data
}
})
}
}
code output = { 2: '120'}
You can try a code as follows:
constructor(){
super()
this.state = {
inputValue: {},
datalist = [],
}
}
async componentWillMount() {
const inputValues = [];
for(var key in dataList) {
inputValues.push({[dataList[key].inputFieldId]: dataList[key].data});
}
this.setState({ inputValue: inputValues });
}

reactJS map function not working as expected

I have a reactJS application where I am trying to dynamically render some data that I read in with a fetch() promise. This is the code of my application:
import React from 'react';
import '../styles/app.css';
//think of react components as functions
class Testpage2 extends React.Component {
constructor(props) {
super(props);
this.state = {
numberOfRecords: 0,
productArray: [{
barcode: '',
name: ''
}]
};
}
componentDidMount() {
let currentComponent = this;
var recordCount = 0;
var tempData = [];
//Make use of the API not the web service.
let url = "http://wmjwwebapi-dev.us-west-2.elasticbeanstalk.com/api/getdata";
const options = { method: 'GET' };
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed");
}
else
{
//inspect the data that the WebAPI returned
var return_code = myJson[0].return_code;
if (return_code == "Default Return code"){
recordCount = -2;
} else {
tempData = JSON.parse(myJson[0].return_string);
recordCount = tempData.barcode.length;
}
currentComponent.setState(
{
numberOfRecords: recordCount,
productArray: currentComponent.state.productArray.push(
{
name: tempData.name,
barcode: tempData.barcode
})
}
);
}
});
}
render() {
console.log(this.state.productArray);
return (
<div>
{ this.state.productArray.map((prod, index) => <li key={index}>{prod.barcode}</li>)}
</div>
)
}
}
export default Testpage2
and this is the error message that I am getting:
Uncaught (in promise) TypeError: this.state.productArray.map is not a function
at Testpage2.render (testpage2.js:67)
This is the result of the console.log() that I added in the render() function:
I'm not really sure what this error is telling me or how to go about debugging the issue.
Any help is greatly appreciated.
Thank you.
The return type of array.push is the new length of the array aka a number
So you set the state property productArray to a number and then try to call number.map which is not defined
How to fix?
push first and then use that array to set the state
const updatedArray = [...currentComponent.state.productArray]
updatedArray.push({ name: tempData.name, barcode: tempData.barcode })
currentComponent.setState({
numberOfRecords: recordCount,
productArray: updatedArray
}
Resources:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
According to MDN:
The push() method adds one or more elements to the end of an array and returns the new length of the array.
It appears that your code expects that Array.push() will return the modified array itself:
productArray: currentComponent.state.productArray.push(...
To prevent the state corruption you should do construct the new array separately, before invoking setState().
Array's push() function returns integer, so you cannot call map() function on it. Try to change your function to:
currentComponent.setState({
numberOfRecords: recordCount,
productArray: [...currentComponent.state.productArray, {
name: tempData.name,
barcode: tempData.barcode
}]
})
The JavaScript Array.push method does not return the modified array, it returns the new length of the array, which is a number. Numbers in JavaScript do not have the map method.
You need to do first create a clone of the productArray, then push the new data, and finally set state:
const newProductArray = [...currentComponent.state.productArray]
newProductArray.push({
name: tempData.name,
barcode: tempData.barcode
})
currentComponent.setState(
{
numberOfRecords: recordCount,
productArray: newProductArray
}
)
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push

Using spread operator to setState in multiple succession

I have some values stored in local storage. When my component mounts, I want to load these values into the state. However, only the last property being added is added to the state. I've checked the values on my localStorage, and they are all there. Furthermore, when I log the variables (desc, pic or foo) in the condition block, they are there.
I thought at first each subsequent if block is re-writing the state, but this is not the case as I am using the spread operator correctly (I think!), adding the new property after all pre-existing properties.
I think the problem is that the code in the last if block is running before the state is set in the first if block. How do I write the code so I get all three properties from my local storage into the state?
//what I expect state to be
{
textArea: {
desc: 'some desc',
pic: 'some pic',
foo: 'some foo'
}
}
//what the state is
{
textArea: {
foo: 'some foo'
}
}
componentDidMount () {
const desc = window.localStorage.getItem('desc');
const pic = window.localStorage.getItem('pic');
const foo = window.localStorage.getItem('foo');
if (desc) {
console.log(desc) //'some desc'
this.setState({
...this.state,
textArea: {
...this.state.textArea,
desc: desc,
},
}, ()=>console.log(this.state.textArea.desc)); //undefined
}
if (pic) {
console.log(pic) //'some pic'
this.setState({
...this.state,
textArea: {
...this.state.textArea,
pic: pic,
},
}, ()=>console.log(this.state.textArea.pic)); //undefined
}
if (foo) {
console.log(foo) //'some foo'
this.setState({
...this.state,
textArea: {
...this.state.textArea,
foo: foo,
},
}, ()=>console.log(this.state.textArea.foo)); //'some foo'
}
}
You are likely being caught by React batching setState calls by shallow-merging the arguments you pass. This would result in only the last update being applied. You can fix this by only calling setState once, for example:
componentDidMount () {
const desc = window.localStorage.getItem('desc');
const pic = window.localStorage.getItem('pic');
const foo = window.localStorage.getItem('foo');
this.setState({
textArea: Object.assign({},
desc ? { desc } : {},
pic ? { pic } : {},
foo ? { foo } : {}
)
});
}
The other version is to pass an update function to setState rather than an update object, which is safe to use over multiple calls. The function is passed two arguments: the previous state, and the current props - whatever you return from the function will be set as the new state.
componentDidMount () {
const desc = window.localStorage.getItem('desc');
const pic = window.localStorage.getItem('pic');
const foo = window.localStorage.getItem('foo');
this.setState(prevState => {
if (desc) {
return {
textArea: {
...prevState.textArea,
desc
}
}
} else {
return prevState;
}
});
// Repeat for other properties
}
It's a little more verbose using this approach, but does offer the opportunity to extract state updating functions outside of your component for testability:
// Outside component
const updateSubProperty = (propertyName, spec) => prevState => {
return {
[propertyName]: {
...prevState[propertyName],
...spec
}
}
}
const filterNullProperties = obj => {
return Object.keys(obj).reduce((out, curr) => {
return obj[curr] ? { ...out, [curr]: obj[curr] } : out;
}, {});
}
componentDidMount () {
this.setState(updateSubProperty("textArea",
filterNullProperties(
desc: window.localStorage.getItem('desc'),
pic: window.localStorage.getItem('pic'),
foo: window.localStorage.getItem('foo')
)
));
}
This way adds some complexity, but (in my opinion) gives a really readable component where it is clear to our future selves what we were trying to achieve.

Categories

Resources