setState is not merging the values - javascript

I use the following code in react in order to update state. state should finally looks like this:
this.state.output = {
'abc':{
value: 10
},
'cde':{
value: 20
}
// new values are added and old kept (if not overwritten)
}
My handler:
handleChange = (data) => {
this.setState(prevState => {
return {
output: {
[data.id]: { ...data },
},
}
})
}
When the data is passed in to handleChage with a new data.id, output does not add the new key, instead completely replace all its content
this.state.output = {
'new':{
value: 2
},
}
I need instead keep the previous key also. What is wrong in my code?

Because you forgot to add the other property and their values, update the object like this:
handleChange = (data) => {
this.setState(prevState => {
return {
output: {
...prevState.output, // notice this
[data.id]: { ...data },
},
}
})
}
Or simply:
handleChange = (data) => {
this.setState(prevState => ({
output: {
...prevState.output,
[data.id]: { ...data },
},
})
)}

object spread syntax is recent spec. Here is the documentation for it: using-object-spread-operator
Below code uses Object.assign method instead :
handleChange = (data) => {
this.setState(prevState => ({
output: Object.assign({}, prevState, {
[data.id]: data
})
})
)}

Related

Can't add object to existing array (useState react native)

I have addText() that runs on click event
const [list, setList] = useState([])
const [value, setValue] = useState("")
useEffect(() => {
getObjectItem("tasks")
.then(t => setList(t.item))
.catch(e => { console.log(e) })
}), []
// A function that add data to the list array
function addText(text) {
console.log(list);
if (value !== "") {
setList(prev =>
[...prev,
{ text: text, isSelected: false }] // Adding a JS Object
)
setObjectItem("tasks", list);
setValue("")
} else {
alert("Please type in something!")
}
}
Output from console.log(list):
Array [
Object {
"isSelected": true,
"text": "Test",
}
]
getObjectItem("tasks") function:
const getObjectItem = async (name) => {
try {
const jsonItem = await AsyncStorage.getItem(name)
const item = JSON.parse(jsonItem)
return {
status: 'success',
name: name,
item: item
}
} catch (err) {
return {
status: 'error',
name: name,
error: err
}
}
}
Why can't I add values to the existing list array with setList() in addText() function?
Setting state is asynchronous.
In addText you write:
setObjectItem("task", list)
which will set the value in AsyncStorage to whatever list was, not what it will be after the state has been updated. The easiest solution is to create the new array then set it to state and AsyncStorage.
Try to put
.then(t => setList([t.item]))
instead of what you wrote

React - State is not updating when it is supposed to, why is react doing this? (not retaining)

Hi so I'm trying to grab some json from an api and then populate a table, pretty simple stuff.
What's happening is that I can see the "tableData" state being updated as each new row comes in, I'm also logging every time "tableData" is updated, yet maybe .5 seconds after its all done my "tableData" is empty again (check console screenshots)
const [bigChartData, setbigChartData] = React.useState("data1");
const [tableData, setTableData] = React.useState([]);
const setBgChartData = (name) => {
setbigChartData(name);
};
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.forEach(item => {
setTableData(oldData => [...oldData, {
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
}]);
});
})
.catch(err => console.log(err));
setTimeout(function () {
console.log(tableData);
}, 3000);
}
useEffect(() => {
getData();
}, []);
useEffect(() => {
console.log("Table data updated:");
console.log(tableData);
}, [tableData]);
I think you should not iterate through each row inside getData() method instead try following code
const getData = () => {
axios.get("URL")
.then(res => {
const data = res.data.items.map(item => {
return{
data: [
{ text: item.title },
{ text: "asd" + item.url },
{ text: "some links..." }
]
};
});
setTableData(data)
}).catch(err => console.log(err));
}
or if you have already some data in tableData then
setTableData([...tableData, data])

Problem get unstructured object - Angular

i am student
Why does it return undefined?
It is an object I need to perform destructuring to obtain only two values.
I can get complete array, but trying to unstructured it returns undefined.
service
getLastCashMov(): Observable<CashMov> {
const url = `${this.API_URL}/last_cashmov`
return this.http.get<CashMov>(url)
}
component
getLastCashMov() {
this.cashMovsService.getLastCashMov()
.subscribe(({balanceARS, balanceUSD}) =>
{ this.lastCashMovs = {balanceARS, balanceUSD};
console.log(balanceARS, balanceUSD)
}, error => {
console.log(error)
})
}
return
when using
getLastCashMov() {
this.cashMovsService.getLastCashMov()
.subscribe((res) =>
{ this.lastCashMovs = res;
console.log(this.lastCashMovs)
}, error => {
console.log(error)
})
}
return
SOLUTION
getLastCashMov() {
this.cashMovsService.getLastCashMov()
.subscribe(([{balanceARS, balanceUSD}]) =>
{ this.lastCashMovs = {balanceARS, balanceUSD};
console.log(this.lastCashMovs)
}, error => {
console.log(error)
})
}
SERVICE
getLastCashMov(): Observable<CashMov[]> {
const url = `${this.API_URL}/last_cashmov`
return this.http.get<CashMov[]>(url)
}
We can't Unstructure array of object for some keys. we need to update and filter keys through map or filter
Here is the code :
getLastCashMov() {
this.cashMovsService.getLastCashMov()
.subscribe((responseArray) => {
let filteredArray = responseArray.map(item => return { balanceARS : item.balanceARS, balanceUSD: item.balanceUSD})
// filteredArray = [{ balanceARS : "...", balanceUSD: "..."}]
this.lastCashMovs = filteredArray[0];
console.log(balanceARS, balanceUSD)
}, error => {
console.log(error)
})
}

Double spread problem - getting rid of Object.assign()

Here is my reducer body code fragment:
const newState = {
...state,
programs: {
...state.programs,
...Object.assign(
{},
...action.payload.map(
(channelName) =>
({
[channelName]: {
...state.programs[channelName],
selected: true
}
})
)
)
}
}
return newState
Is there any chance to get rid of Object.assign in this case?
The classical advice to change Object.assign({}, a) to { ...a } does not work in this case, because here we already have ...action.payload.map, so it would result in ... { ...a } which makes spread to produce array-like keys of 0,1,2...
Is there any elegant way to transform my code correctly?
Ever heard of reduce?
const action = {
payload: ['discoveryChannel']
}
const state = {
programs: {
cartoonNetwork: {
description: '',
when: new Date()
},
discoveryChannel: {
description: '',
when: new Date()
}
}
}
const newState = {
...state,
programs: {
...state.programs,
...action.payload.reduce(
(acc, channelName) => {
acc[channelName] = {
...state.programs[channelName],
selected: true
}
return acc;
}, {})
}
}
console.log(newState);
Another option to use Object.fromEntries:
const action = {
payload: ['discoveryChannel']
}
const state = {
programs: {
cartoonNetwork: {
description: '',
when: new Date()
},
discoveryChannel: {
description: '',
when: new Date()
}
}
}
const newState = {
...state,
programs: {
...state.programs,
...Object.fromEntries(
action.payload.map(
channelName => ([
channelName, {...state.programs[channelName], selected: true}
])
)
)
}
}
console.log(newState);

React Apollo: Uncaught (in promise) TypeError: Cannot read property 'refetch' of undefined

I have a function that has a bit of a promise chain going on, but that's besides the point.
When I run a certain mutation's refetch, it gives me Uncaught (in promise) TypeError: Cannot read property 'refetch' of undefined.
The strange part is that if I remove a mutation before it, it will work. So here's the code:
Promise.all(this.props.questionnaireData.map(({ kind, id }): Promise<any> => {
const responses = this.props.formData[kind];
return this.props.updateQuestionnaire(id, responses);
})).then(() => {
this.props.finishAssessment(this.props.assessmentId)
.then(() => {
track('Assessment -- Finished', {
'Assessment Kind' : this.props.assessmentKind,
'Assessment Id' : this.props.assessmentId,
});
if (this.props.assessmentKind === 'INITIAL_ASSESSMENT') {
this.props.getCompletedInitialAssessment.refetch().then(() => {
Router.replace(routes.LoadingAssessmentResults.to, routes.LoadingAssessmentResults.as);
});
this.submitEmailNotifications();
} else if(this.props.assessmentKind === 'GOAL_CHECK_IN') {
Router.replace(routes.MemberProgressDashboard.to, routes.MemberProgressDashboard.as);
} else {
Router.replace(routes.MemberDashboard.to, routes.MemberDashboard.as);
}
});
});
The error happens at this.props.getCompletedInitialAssessment.refetch(), to which I don't know why. However, when I remove this.props.finishAssessment(this.props.assessmentId), only then the refetch will work.
Basically:
Promise.all(this.props.questionnaireData.map(({ kind, id }): Promise<any> => {
const responses = this.props.formData[kind];
return this.props.updateQuestionnaire(id, responses);
})).then(() => {
track('Assessment -- Finished', {
'Assessment Kind' : this.props.assessmentKind,
'Assessment Id' : this.props.assessmentId,
});
if (this.props.assessmentKind === 'INITIAL_ASSESSMENT') {
this.props.getCompletedInitialAssessment.refetch().then(() => {
Router.replace(routes.LoadingAssessmentResults.to, routes.LoadingAssessmentResults.as);
});
this.submitEmailNotifications();
} else if(this.props.assessmentKind === 'GOAL_CHECK_IN') {
Router.replace(routes.MemberProgressDashboard.to, routes.MemberProgressDashboard.as);
} else {
Router.replace(routes.MemberDashboard.to, routes.MemberDashboard.as);
}
});
will make refetch work. Otherwise it complains that it doesn't know what refetch is.
For Apollo, I'm using the graphql HOC, and it looks like this:
graphql(getCompletedInitialAssessment, {
name : 'getCompletedInitialAssessment',
options : { variables: { status: ['Finished'], limit: 1 } },
}),
graphql(updateQuestionnaire, {
props: ({ mutate }) => ({
updateQuestionnaire: (id, responses) => {
let normalized = {};
for (let res in responses) {
let num = +responses[res];
// If the value is a stringified numuber, turn it into a num
// otherwise, keep it a string.
normalized[res] = Number.isNaN(num) ? responses[res] : num;
}
const input = {
id,
patch: { responses: JSON.stringify(normalized) },
};
return mutate({
variables: { input },
});
},
}),
}),
graphql(finishAssessment, {
props: ({ mutate }) => ({
finishAssessment: (id) => {
const input = { id };
return mutate({
variables : { input },
refetchQueries : ['getMemberInfo'],
});
},
}),
}),
What I've tried is even rewriting this to use async/await, but the problem still happens:
try {
await Promise.all(this.props.questionnaireData.map(({ kind, id }): Promise<any> => {
const responses = this.props.formData[kind];
return this.props.updateQuestionnaire(id, responses);
}));
const finishAssessmentRes = await this.props.finishAssessment(this.props.assessmentId);
console.log(finishAssessmentRes)
if (this.props.assessmentKind === 'INITIAL_ASSESSMENT') {
const res = await this.props.getCompletedInitialAssessment.refetch();
console.log(res);
this.submitEmailNotifications();
Router.replace(routes.LoadingAssessmentResults.to, routes.LoadingAssessmentResults.as);
} else if(this.props.assessmentKind === 'GOAL_CHECK_IN') {
Router.replace(routes.MemberProgressDashboard.to, routes.MemberProgressDashboard.as);
} else {
Router.replace(routes.MemberDashboard.to, routes.MemberDashboard.as);
}
} catch (error) {
console.error(error);
}
I honestly don't know what's happening or why refetch wouldn't work. Would refactoring into hooks help? Does anyone have any idea?
From the docs
The config.props property allows you to define a map function that takes the props... added by the graphql() function (props.data for queries and props.mutate for mutations) and allows you to compute a new props... object that will be provided to the component that graphql() is wrapping.
To access props that are not added by the graphql() function, use the ownProps keyword.
By using the props function, you're telling the HOC which props to pass down either to the next HOC or to the component itself. If you don't include the props that were already passed down to it in what you return inside props, it won't be passed to the component. You need do something like this for every props function:
props: ({ mutate, ownProps }) => ({
finishAssessment: (id) => {
//
},
...ownProps,
}),
Composing HOCs is a pain and the graphql HOC is being deprecated anyway in favor of hooks. I would strongly advise migrating to the hooks API.

Categories