How to avoid overrated item after set state? - javascript

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.

Related

two axios requests, one response

it appears the api im using breaks a list of 250 assets into multiple pages. im trying to call numerous pages to be listed in an ant design table
constructor(props) {
super(props);
this.state = {
data: [],
loading: true
}
}
componentDidMount() {
axios.all([
axios.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1&sparkline=true&price_change_percentage=24hr'),
axios.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=2&sparkline=true&price_change_percentage=24hr')
])
.then(axios.spread((res => {
const data = res.data;
this.setState({ data, loading: false })
})))
}
render() {
const { data } = this.state;
const tableData = data.map(row => ({
Rank: row.market_cap_rank,
Symbol: row.symbol,
Name: row.name,
Price: row.current_price,
marketCap: row.market_cap,
priceChange: row.price_change_percentage_24h,
sparkline: row.sparkline_in_7d.price
}))
const columns = [{
title: 'Rank',
dataIndex: 'Rank',
key: 'market_cap_rank',
}, {
title: 'Symbol',
dataIndex: 'Symbol',
key: 'symbol',
render: (value) => {
return <span>{value.toUpperCase()}</span>;
},
}, {
title: 'Name',
dataIndex: 'Name',
key: 'name',
}, {
title: 'Price',
dataIndex: 'Price',
key: 'current_price',
render: (value) => {
return <span>$<b>{value.toFixed(2)}</b></span>;
},
}, {
title: 'Market Cap',
dataIndex: 'marketCap',
key: 'market_cap',
render: (value) => {
return`$${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
},
...
<Table
pagination="false"
loading={this.state.loading}
dataSource={tableData}
columns={columns}
size="small"
/>
this works, but only displays the first page and not the second as well
sorry for the silly question, maybe someone can take a moment to assist me as this question probably stems from a lack of general understanding. it's sure nice to hear from other people on here! :)
You have to update your componentDidMount like below
axios.all([
axios.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1&sparkline=true&price_change_percentage=24hr'),
axios.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=2&sparkline=true&price_change_percentage=24hr')
])
.then(resArr =>{
const data = [];
resArr.map(res=> data.push(...res.data));
this.setState({ data, loading: false });
});
This is because the function you pass to axios.spread receives the result of the requests in two different arguments.
Like in the example from the axios doc
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
your axios.spread will receive separately the two pages :
You can then concatenate the two pages to have your data
axios
.all([
axios.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1&sparkline=true&price_change_percentage=24hr'),
axios.get('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=2&sparkline=true&price_change_percentage=24hr')
])
.then(axios.spread(((page1, page2) => {
const data = [...page1.data, ...page2.data];
this.setState({ data, loading: false })
})))
If you want to have more than a determinate number of pages you can make use of rest operator and flatten the array using spread and concat
axios
.all(arrayOfLinks)
.then(axios.spread(((...pages) => { // use rest operator to get an array of pages containing your data
const data = [].concat(...pages.data); // use spread operator in a concat to flatten your arrays of data
this.setState({ data, loading: false })
})))

How to get JSON object to React component?

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.

Is there a process/practice for replacing a JSON value? Specifically, replacing an API URL with a value provided by the API

I'm using the Star Wars API to practice, but I am running into a weird bug. I'm pretty sure I am the problem here but I don't know enough about these processes to find the issue.
I am creating profile cards with this info, but when I try to replace the homeworld url with the actual name, I am unable to change the value that appears in my react element.
This is a smaller version of the JSON object that I get from the API.
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "https://swapi.co/api/planets/1/",
},
I was trying to replace the url value of homeworld with the name of the actual homeworld before saving it to my this.state array. I've tried making the fetch calls from the element files (that didn't really feel proper). So I hacked some code together and watched it change with console.log();. It's not the prettiest.
fetch('https://swapi.co/api/people/')
.then(response => {
return response.json();
})
.then(array => {
console.log(array.results);
Promise.all(array.results.map(character => {
console.log(character.homeworld)
let home_url = character.homeworld;
fetch(home_url)
.then(home => {return home.json()})
.then(home_json => character.homeworld = home_json.name)
}))
.then(() => {
console.log(array.results)
this.setState({characters:array.results})
});
});
The console.log(); shows that the value for homeworld was changed to the string 'Tatooine'. This is the same all the way down to the profile card. So I was expecting this to be the value in the card to be 'Tatooine', but I end up with "https://swapi.co/api/planets/1/".
At this point I don't know where my lack of knowledge is. I'm not sure if it is an issue with JSON, React, Fetch/Promises. So if anyone is able to offer some insight on this issue that would be great. I can add more code to the post if needed. Cheers!
You need to return something in each .then call in order to keep passing updated data along. Also in Promise.all( array.results.map( you should return each element so that you don't end up with an array full of undefined.
Here is an example of how you can do this (note I'd recommend using async/await for at least the Promise.all section):
componentDidMount() {
fetch("https://swapi.co/api/people/")
.then(response => response.json())
.then(array => {
console.log(array.results);
return Promise.all(array.results.map(async character => {
console.log(character.homeworld);
const homeUrl = character.homeworld;
const home = await fetch(homeUrl);
const homeJson = await home.json();
return {
...character,
homeworld: homeJson,
}
}));
})
.then(characters => {
console.log(characters);
this.setState({ characters });
})
}
Again using async/await everywhere:
componentDidMount() {
this.fetchData();
}
async fetchData() {
const response = await fetch("https://swapi.co/api/people/");
const array = await response.json();
console.log(array.results);
const characters = await Promise.all(array.results.map(async character => {
console.log(character.homeworld);
const homeUrl = character.homeworld;
const home = await fetch(homeUrl);
const homeJson = await home.json();
return {
...character,
homeworld: homeJson,
}
}));
console.log(characters);
this.setState({ characters });
}
Then this.state.characters is an array of length 10. Here is a sample element:
{
birth_year: "41.9BBY"
created: "2014-12-10T15:18:20.704000Z"
edited: "2014-12-20T21:17:50.313000Z"
eye_color: "yellow"
films: (4) ["https://swapi.co/api/films/2/", "https://swapi.co/api/films/6/", "https://swapi.co/api/films/3/", "https://swapi.co/api/films/1/"]
gender: "male"
hair_color: "none"
height: "202"
homeworld: {name: "Tatooine", rotation_period: "23", orbital_period: "304", diameter: "10465", climate: "arid", …}
mass: "136"
name: "Darth Vader"
skin_color: "white"
species: ["https://swapi.co/api/species/1/"]
starships: ["https://swapi.co/api/starships/13/"]
url: "https://swapi.co/api/people/4/"
vehicles: []
}

Array prop returns Observer so can't access at [0]

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
}
})
},

In react, how to store object in array and iterate the array index by 1?

I have a page that displays multiple boxes and each box belongs to a specific company. Each company has multiple projects and each project has multiple releases.
Box 1:
Company Name / Project Name / Release Name
Box 2:
Company Name / Project Name / Release Name
I have a state defined as such:
this.state = {
companies: [],
projects: [],
releases: [],
activeProjects: []
}
And here, I am fetching all the data from the database:
componentWillMount() {
getCompanys().then(companies => {
const projectPromises = companies.map((company) => {
getProjects(company).then(projects => {
const releasePromises = projects.map((project) => {
return getReleases(project).then(releases => {
if(projects.length > 0 || releases > 0) {
this.setState({
companies: companies,
projects: projects,
releases: releases
});
}
})
})
})
});
})
}
which comes back with the following data:
Companys: (2) [{…}, {…}]0: {_id: {…}, company_name: "IBM", …}
Projects: [{…}]0: {_id: {…}, project_name: "Project 101", …}
Releases: (3) [{…}, {…}, {…}]0: {_id: {…}, release_name: "Release 103", …}
I am getting 2 companies, 1 project, and 3 releases.
If I wanted to store each of my company, project, and release into my activeProjects array, how would I achieve the following?
activeProjects: [
{
company: ,
project: ,
release:
},
{
company: ,
project: ,
release:
},
]
Can someone please help me out?
I want my end result to be someting like this:
activeProjects.map((project, index) => {
return(
**Box 1**
IBM / Project 101 / Release Name goes here
**Box 2**
Facebook / Project 102 / Release Name goes here
)
});
I would recommend structuring your data in the desired format from the beginning. What I mean by this is rather than splitting your companies, projects, and releases into separate arrays in your state, just keep them nested from the start.
(Also from what I can tell from your code, you are overwriting the projects and releases each time so you are only left with projects for the last company and releases for the last project.)
1. First structure state like this:
state = {
companies: [],
activeProjects: []
}
2. Then rework your componentWillMount method to be something like this (also I would recommend using async/await instead of callbacks for ease of readability).
async componentWillMount() {
const companies = await getCompanies();
for (const company of companies) {
company.projects = await getProjects(company);
for (const project of company.projects) {
project.releases = await getP
}
}
this.setState({ companies });
}
Here is a version that is a bit more complicated but more efficient because it can work on multiple companies/projects asynchronously rather than waiting for responses for each call before moving on:
async componentWillMount() {
const companies = await getCompanies();
const nestedCompanies = await Promise.all(companies.map(async company => {
const projects = await getProjects(company);
const nestedProjects = await Promise.all(projects.map(async project => {
const releases = await getReleases(project);
return {
...project,
releases
}
}));
return {
...company,
projects: nestedProjects
}
}));
this.setState({ companies: nestedCompanies });
}
Now your state will look like this:
{
companies: [
{
id: 1,
name: 'IBM',
projects: [
{
id: 1,
name: 'Project 101',
releases: [
{
name: 'Release Name'
},
// and so on
]
},
// and so on
]
},
// and so on
]
}
3. Now looping through the data in companies should be fairly simple. You can do something like this:
const activeProjects = this.state.companies.map(company => {
return company.projects.map(project => {
return project.releases.map(release => {
return `${company.name} / ${project.name} / ${release.name}`;
});
}).reduce((acc, e) => acc.concat(e), []);
}).reduce((acc, e) => acc.concat(e), []);
The above code will result in activeProjects being an array of strings, each of which have the format 'IBM / Project 101 / Release Name'. For this last step you could instead return some component like:
<Box company={ company.name } project={ project.name } release={ release.name }/>,
depending on how your project is structured.

Categories