I created a React App with AXIOS. I need to get some JSON data from back end and change the State with that data. When I get the object and mapping to my state, the state is only setting for the last element of the object. So I can only see the last element in the state. How I can get all the elements to the state?
My API call is as follows
API.post('viewallusers', [], config)
.then(({ data }) => {
const allUsers = data.response.AllUsers;
allUsers
.map(user => {
return (
this.setState({
data: [
createData(
user.name,
user.id,
user.email
)
]
})
)
})
})
.catch((err) => {
console.log("AXIOS ERROR: ", err);
})
JSON data:
{response :
{AllUsers :
0 : {name: "Amy", id: 1, email: "myEmail1"},
1 : {name: "Doris", id: 2, email: "myEmail2"},
2 : {name: "Jase", id: 3, email: "myEmail3"}
}
}
I expect the the state "data" is to be set as follows:
data : [
createData("Amy",1,"myEmail1"),
createData("Doris",2,"myEmail2"),
createData("Jase",3,"myEmail3")
]
But the actual state after getting the JSON data is
data : [
createData("Jase",3,"myEmail3")
]
How can I solve this?
You need to first map the data then set entire state.
API.post('viewallusers', [], config)
.then(({ data }) => {
this.setState({
data: data.response.AllUsers.map(user => (createData(user.name, user.id, user.email)))
})
})
Or use callback version of setState and manually merge state.data (NOT recommended in this particular case)
API.post('viewallusers', [], config)
.then(({ data }) => {
data.response.AllUsers.forEach(user => {
this.setState(prev =>
({...prev, data: [prev.data, createData(user.name, user.id, user.email)]})
)
})
})
It probably happens because setState doesn't do a deep merge. So if you have in state
state = {
key1: 123,
key2: {
test1: 1,
test2: 2
}
}
And you do
this.setState({
key2: {
test1: 4
}
})
You will end up with
state = {
key1: 123,
key2: {
test1: 4
}
}
You have to do instead:
this.setState((ps) => ({
key2: {
...ps.key2,
test1: 4
}
}));
Similar approach works if value for key2 is array. Or alternatively you can first map all the data and then do a setState as suggested in other answer.
Related
this is my code
const [state, setState] = useState(
[{id: 1, key:""}, {id: 2, key:""}, {id: 3, key:""}]
)
i want to to change "key" state
im confuse
now im using
setState(
[...state].map((data, index) => {
if (data.id === state[index].id) {
return {
...data,
key: result,
};
} else return data;
}),
);
}
result variable came from result when i fetching data.
result is a random string
If your data structure is always going to be in that order data.id === state[index].id doesn't really achieve much.
For example:
when data.id is 1 the index will be 0. And state[0].id is 1.
when data.id is 2 the index will be 2. And state[1].id is 2.
etc.
It just sounds like you want to iterate over all the objects in state and update each key value with that random string you mentioned in the comment section. There's no need to make a copy of state since map already returns a new array ready for setState to use.
function setState(mapped) {
console.log(mapped);
}
const state = [{ id: 1, key: '' }, { id: 2, key: '' }, { id: 3, key: '' }];
const result = 'random';
const mapped = state.map(data => {
return { ...data, key: result };
});
setState(mapped);
New react developer here, here i have two useEffects, one of them(first one) is an object which contains a 'name' which i want to use in my second useEffect. Second useEffect contains an array of objects, these array of objects has 'billingName' and 'driverName', some of them has same value in them for example driverName: "james". What i want to do is in my second useEffect check if 'name' from first useEffect is same as 'driverName', only then do this 'setOrders(res);
setRenderedData(res);'
my error message: Property 'driverName' does not exist on type...
my object: {
id: "98s7faf",
isAdmin: true,
name: "james"}
my array: [{billingName: "trump",driverName: "james"}, {billingName: "putin",driverName: "alex"}, {…}, {…}, {…}]
my code:
const [driver, setDriver] = useState<User>();
useEffect(() => {
api.userApi
.apiUserGet()
.then((res: React.SetStateAction<User | undefined>) => {
console.log(res);
setDriver(res);
});
}, [api.userApi]);
useEffect(() => {
api.caApi
.apiCaGet(request)
.then((res: React.SetStateAction<CaDto[] | undefined>) => {
if (driver?.name == res?.driverName) {
setOrders(res);
setRenderedData(res);
console.log(res);
}
});
}, [api.caApi]);
You can call api.caApi in then of api.userApi,
useEffect(() => {
api.userApi.apiUserGet().then((res1?: User ) => {
setDriver(res1);
return api.caApi.apiCaGet(request).then((res?: CaDto[])
=> {
if (res.some(({ driverName }) => driverName === res1?.name)) {
setOrders(res);
setRenderedData(res);
console.log(res);
}
});
});
}, [api.caApi, api.userApi]);
I have a scenario
{
data:'',
skus: [
{ id: 1, ......}
{ id: 2, ......}
{ id: 3, ......}
]
api_first:'',
}
I have that schema and want to setState in somewhere skus on selected sku item and return changed item to original array
this.setState(produce(prevstate =>
prevstate.data.sku.obj="change"
))
this works for me
I'd recommend to use functional setState and map:
const updateSku = (skuId, data) => {
this.setState(prevState => ({
...prevState,
skus: prevState.skus.map(sku => {
if (sku.id === skuId) {
return {...sku, ...data}
} // else
return sku
})
}))
}
State immutability is important sometimes devs mutate states those are complex with multiple nested levels. You can always update state with simple javascript object update stratigy but I would suggest you to use immerjs. It reduces the code and makes it much more cleaner and easy to understand what is going to change. It helps a lot in redux reducers where a complex state needs to be updated with mutation
Here is example
https://immerjs.github.io/immer/docs/example-setstate
/**
* Classic React.setState with a deep merge
*/
onBirthDayClick1 = () => {
this.setState(prevState => ({
user: {
...prevState.user,
age: prevState.user.age + 1
}
}))
}
/**
* ...But, since setState accepts functions,
* we can just create a curried producer and further simplify!
*/
onBirthDayClick2 = () => {
this.setState(
produce(draft => {
draft.user.age += 1
})
)
}
Using immerjs, it will be
const updateSku = (skuId, data) => {
this.setState(produce(draft => {
const sku = draft.skus.find(s => s.id === skusId);
Object.assign(sku, data);
}));
}
What I have understood from your explanation is that when the SKU item gets changed you want to update the state Skus.
Here I've provided a solution for the same please try to relate with your example.
let's assume you have the following react component.
import React, { Component } from "react";
export class Sku extends Component {
constructor(props) {
super(props);
this.state = {
data: "",
skus: [
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
{ key: "key3", value: "value3" },
],
APIFirst: "",
};
}
handleSkuChange = (data) => {
this.setState(({ skus }) => {
const newSkus = skus.map(sku => (sku.key === data.key ? { ...sku, ...data } : sku));
return { skus: newSkus };
});
};
render() {
const { data, skus, APIFirst } = this.state;
const newSku = { key: 'key2', value: 'newSku' };
console.log("states =>", data, skus, APIFirst);
return (
<button type="button" onClick={() => this.handleSkuChange(newSku)}>'Change sku'</button>
);
}
}
The handleSkuChange function will work like it,
const skus = [
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
{ key: "key3", value: "value3" },
];
const handleSkuChange = (data) => (
skus.map(sku => (sku.key === data.key) ? { ...sku, value: "newValue" } : sku));
const newSku = { key: 'key2', value: 'newSku' };
console.log('old skus', skus);
console.log('new skus', handleSkuChange(newSku));
I have an function to get the data from firebase,
and I want to set data into a state after getting,
so I just declare an array and push all data to them after that I setState this array with my state
But when I log these state or render it, I got some issues With RN 0.61.2, It's Work perfect In RN 0.58.0 I don't Know Why/How!!!
when I log the state I just see overrated data "I just have 4 items in DB" but the logger print to me more than tens,
and when I just render these data "state" they tell me
JSON value "5" of type NSNumber cannot be converted to Nsstring
So how can I avoid that?
here is my code Snippet
this.state = {
RecommendedProviders: [],
}
componentDidMount() {
this._recommendedProvider();
}
_recommendedProvider = () => {
let Recommended = [];
firebase
.database()
.ref('recommendationProviders')
.once('value')
.then(snapshot => {
snapshot.forEach(childSnapshot => {
Recommended.push({
gKey: childSnapshot.key,
id: childSnapshot.val().id,
username: childSnapshot.val().username,
service: childSnapshot.val().service,
aboutMe: childSnapshot.val().aboutMe,
coordinates: {
longitude: childSnapshot.val().coordinates.longitude,
latitude: childSnapshot.val().coordinates.latitude,
},
city: childSnapshot.val().city,
mobileNumber: childSnapshot.val().mobileNumber,
token: childSnapshot.val().token._55,
});
});
console.log('Recommended', Recommended); //when i log this i can just see array of 4 item
this.setState({RecommendedProviders: Recommended});
});
// .then(() => this.setState({RecommendedProviders: Recommended}));
};
UI
<FlatList
horizontal
data={this.state.RecommendedProviders}
renderItem={({item}) => {
console.log('#item/', item); /when i log this i can just see more than 4 item maybe 20 :D
}}
keyExtractor={(item, index) => index.toString()}
/>
Okay, first about that logger:
JSON value "5" of type NSNumber cannot be converted to Nsstring
This error is because keyExtractor expects a string as returned value. So, to avoid it, you can try this:
<FlatList
keyExtractor={(item, index) => 'item' + index}
/>
Finally, about your log:
I created an expo's snack with your gist, wich SDK is based on react-native 0.59, you can check it here. I also tried to simulate your async fetch with the code below and it worked fine.
new Promise((resolve, reject) => {
setTimeout(resolve, 2000, [
{ id: 1, username: 'test', service: 'example' },
{ id: 2, username: 'test', service: 'example' },
{ id: 3, username: 'test', service: 'example' },
{ id: 4, username: 'test', service: 'example' },
]);
}).then(res => {
const providers = [];
res.forEach(item => providers.push(item));
this.setState({ RecommendedProviders: providers }, () =>
console.log('object', this.state.RecommendedProviders)
);
});
I believe that may be the solution to downgrade your RN version, if it does not negatively impact your application.
Hope it will help you.
I passed Array but got Observer here's my code:
In Component1
data() {
return {
myWords: [],
}
}
//...
await axios.post(this.serverUrl + router, {
voca: text,
category: this.buttonGroup.category.text
})
.then(res => {
this.myWords.push({
voca: this.voca,
vocaHeader: this.vocaHeader,
category: res.data.savedVoca.category,
date: res.data.savedVoca.date,
id: res.data.savedVoca._id
})
this.myWords.push({voca:"test"})
})
.catch(err => {
console.log(err)
})
In Component2
props: {
myWordsProp: {
type: Array,
default: () => ([])
},
},
mounted() {
console.log(this.myWordsProp)
console.log(this.myWordsProp[0]) //returns undefined
},
And I expected an Array but I get Observer so I can't get values from this.myWordsProp[0] why?
//this.myWordsProp
[__ob__: Observer]
0: {
category: "ETC"
date: "2018-11-21T15:31:28.648Z"
id: "5bf57a503edf4e0016800cde"
voca: Array(1)
vocaHeader: Array(1)
...
}
1: {__ob__: Observer}
length: 2
__ob__: Observer {value: Array(2), dep: Dep, vmCount: 0}
__proto__: Array
//this.myWordsProp[0]
undefined
I found a clue that when I test it outside of axios it worked as I expected.
Vue wraps data and props into reactive objects. Use vue-devtools plugin in your browser as an alternative to viewing the ugly observer in the console.
In your code, the object behaves correctly. It’s only in the console that it ‘looks’ different.
Anyway, you can also click on the ... to expand the node and get the value from the console.
https://github.com/vuejs/vue-devtools
I found a solution It's because of sending props before get data from server.
This is my whole of postVocas function It returns promise
postVocas: function (voca) {
if (!voca || voca.length < 1) return
let router = "/api/voca"
let text = ""
text += `${this.vocaHeader[0].english}, ${this.vocaHeader[0].korean}\n`
voca.forEach((x, index) => {
text += `${voca[index].english}, ${voca[index].korean}\n`
})
return axios.post(this.serverUrl + router, {
voca: text,
category: this.buttonGroup.category.text
}).then(res => {
this.myWords.push({
voca: this.voca,
vocaHeader: this.vocaHeader,
category: res.data.savedVoca.category,
date: res.data.savedVoca.date,
id: res.data.savedVoca._id
})
}).catch(err => {
console.log(err)
})
},
And await till get data from server.
This one is function where execute My postVocas function.
sendVocaToTable: async function () {
let reformedText = this.reformText(this.text)
this.voca = this.formatTextToVoca(reformedText)
await this.postVocas(this.voca)
this.$router.push({
name: 'Table',
params: {
vocaProp: this.voca,
tableHeaderProp: this.vocaHeader
}
})
},