Hi I'm currently having problems with formatting data I'm receiving from an API
Should I use reduce instead of forEach?
Here is the desired output, an array of objects with just the titles and prices:
[
{
title: "Meter",
price:"9.99"
},
{
title: "Plan One",
price:"11.99"
}
]
Here is how I am getting the data, setting it, refetching it every 60 seconds:
const [products, setProducts] = useState([]);
const formatProduct = (products) =>{
let result = [];
products.forEach((product)=>{
if(product.variants['price'] !== "0.00"){
result.push({product["title"],product.variants[0]["price"]})
}
})
return result
}
useEffect(() => {
const fetchData = async () => {
const axiosResponse = await axios(`https://www.today/products.json`);
setProducts(formatProduct(axiosResponse.data.products));
};
const reFetchData = setInterval(() => setProducts(fetchData()), 60000);
return () => {
clearInterval(reFetchData);
};
}, []);
Adding object to the result array you have to specify the name of props. Also you've missed index of variants when you compare a price:
const formatProduct = (products) =>{
let result = [];
products.forEach((product)=>{
if(product.variants[0]['price'] !== "0.00"){ //index 0
result.push({
title: product["title"], // specify the prop name
price: product.variants[0]["price"]
})
}
})
return result
}
There is another problem in your code. What happens if variant doesn't contain any item? I would check array length and use filter and map functions:
const formatProduct = (products) => {
return products
.filter(p => p.variants.lenght>0 && p.variants[0]!=="0.00")
.map(p => {title: p.title, price: p.variants[0].price});
}
Another problem is how you call setProducts(fetchData()) in the interval function. You shouldn't call setProducts() because you're already calling it in fetchData(). So it has to be:
const reFetchData = setInterval(() => fetchData(), 60000);
Related
I'm taking an array and filtering the value in a context:
const { responsible } = useResponsible()
const [ids, setIds] = useState([])
const filteredResponsible = responsible?.filter((resp) =>
ids.includes(resp.id)
)
The problem is that I need to make a map to get the corresponding value of each id, one by one. This ends up making the code too long:
const { filteredResponsible } = useResponsible
const responsibleName = filteredResponsible.map((resp) => resp.name)
const responsibleEmail = filteredResponsible.map((resp) => resp.email)
const responsibleAddress = filteredResponsible.map((resp) => resp.address)
...
And so on with each item in the array.
I'm using the React Hook Form's setValue to set the value in the inputs:
useEffect(() => {
setValue('name', `${responsibleName}`)
setValue('email', `${responsibleEmail}`)
setValue('address', `${responsibleAddress}`)
setValue('cep', `${responsibleCep}`)
setValue('district', `${responsibleDistrict}`)
setValue('city', `${responsibleCity}`)
setValue('state', `${responsibleState}`)
setValue('phone', `${responsiblePhone}`)
setValue('sex', `${responsibleSex}`)
}, [])
How can I make these maps smaller? Without having to make a map to get each item in the array?
There doesn't seem to be any reason to do those map calls on every render and to do them anywhere other than where you need them, since you only show using the result in a mount-only effect. Just do them there:
const { filteredResponsible } = useResponsible; // Is there really no `()` needed here?
useEffect(() => {
setValue("name", `${filteredResponsible.map(({name}) => name)}`);
setValue("email", `${filteredResponsible.map(({email}) => email)}`);
setValue("address", `${filteredResponsible.map(({address}) => address)}`);
// ...
}, []);
If you really need those distinct arrays on every render, unless you can change your data structures to be more amenable to your output I don't see you have a lot of options. You can at least avoid multiple loops through filteredResponsible:
const { filteredResponsible } = useResponsible; // ()?
const responsibleName = [];
const responsibleEmail = [];
const responsibleAddress = [];
for (const { name, email, address } of filteredResponsible) {
responsibleName.push(name);
responsibleEmail.push(email);
responsibleAddress.push(address);
}
And if that's really the case, you may want to avoid doing it on every render:
const { filteredResponsible } = useResponsible; // ()?
const { responsibleName, responsibleEmail, responsibleAddress } = useMemo(() => {
const responsibleName = [];
const responsibleEmail = [];
const responsibleAddress = [];
for (const { name, email, address } of filteredResponsible) {
responsibleName.push(name);
responsibleEmail.push(email);
responsibleAddress.push(address);
}
return { responsibleName, responsibleEmail, responsibleAddress };
}, [filteredResponsible]);
I have this code:
const fetchPokemonData = async () => {
const data = await fetch('https://pokeapi.co/api/v2/pokemon?limit=151', options)
let pokemons = []
await data.json().then(pokemon => pokemons.push(pokemon.results))
return pokemons}
const getPokemonsUrl = async () => {
const pokemonsUrls = []
const pokemonData = await fetchPokemonData()
pokemonData[0].map(pokemons => pokemonsUrls.push(pokemons.url))
return pokemonsUrls}
const createPokemonObject = async () => {
const urls = await getPokemonsUrl()
const arrayOfPokemons = []
urls.map(async url =>{
const data = await fetch(url)
const pokemon = await data.json()
const { name, id, sprites: {other: {dream_world: {front_default}}}, types, weight, stats } = pokemon
arrayOfPokemons.push(
{
name: name,
id: id,
image: front_default, types: types,
weight: weight,
stats: stats
}
)
})
console.log(arrayOfPokemons) //works
arrayOfPokemons.map(pokemon => console.log(pokemon)) // works only after setTimeout() delay }
The problem is when I try to log each pokemon outside urls.map() array function, there is no each individual Pokemon, because the data isn`t fetched yet (I tested that put by putting arrayOfPokemons inside setTimeout() function and after some delay time, each Pokemon was shown). Can someone please rewrite or explain the way to rewrite this code do that I get all individual pokemons outside of urls.map() function, because that is the best way for me to learn.
There are several issues:
push returns the length of the array, not data. Instead of using push, really map the data with a .map and capture the returned array.
.map(async will execute the async callbacks immediately and continue. There is no waiting for those callbacks to terminate their asynchronous code. Either use a normal for loop or use await Promise.all
Here is a correction of your code:
const fetchPokemonData = async () => {
const data = await fetch('https://pokeapi.co/api/v2/pokemon?limit=151');
const pokemon = await data.json();
return pokemon.results;
}
const getPokemonsUrl = async () => {
const pokemons = await fetchPokemonData();
return pokemons.map(pokemon => pokemon.url);
}
const createPokemonObject = async () => {
console.log("wait for it...");
const urls = await getPokemonsUrl();
const arrayOfPokemons = await Promise.all(urls.map(async url => {
const data = await fetch(url);
const pokemon = await data.json();
const { name, id, sprites: {other: {dream_world: {front_default}}}, types, weight, stats } = pokemon;
return {
name: name,
id: id,
image: front_default, types: types,
weight: weight,
stats: stats
};
}));
console.log(arrayOfPokemons);
}
createPokemonObject();
I'm currently fetching data from my db but for the simplicity of this Q, I have decided to manually create an example with fake data.
I'm building a search-bar for my users to look through all of the data coming from db and everything seems to be working fine when looking for a specific document, however, I want to reset the state to its original data when the input is empty.
This is what I've tried so far but with no success. Am I missing something?
const objects = [
{
_id: 0,
title: 'Title One',
},
{
_id: 1,
title: 'Title Two',
},
{
_id: 2,
title: 'Title Three',
},
]
const [keyword, setKeyword] = useState('')
const [list, setList] = useState([]);
useEffect(() => {
setList(objects);
}, [objects]);
const handleChange = () => (e) => {
e.preventDefault()
setKeyword(e.target.value)
if (keyword !== '') {
const result = objects.filter((object) => {
return object.title.toLowerCase().startsWith(keyword.toLowerCase())
})
setList(result)
} else {
// THIS IS WHERE THE PROBLEM RESIDES...
console.log('Original data')
setList(objects)
}
}
This is the current output:
What you're doing wrong is in these lines
setKeyword(e.target.value)
if (keyword !== '') {
The state is updated asynchronously, and the value of the keyword will be old.
What you can do is update the state in handleChange and then have a separate useEffect to update the results:
const [keyword, setKeyword] = useState('')
const [list, setList] = useState([]);
useEffect(() => {
setList(objects);
}, [objects]);
useEffect(() => {
if (keyword !== '') {
const result = objects.filter((object) => {
return object.title.toLowerCase().startsWith(keyword.toLowerCase())
})
setList(result)
} else {
// THIS IS WHERE THE PROBLEM RESIDES...
console.log('Original data')
setList(objects)
}
}, [keyword]);
const handleChange = () => (e) => {
e.preventDefault()
setKeyword(e.target.value)
}
State setters are asynchronous. The code following a state update (your if(keyword) may be run before the state update is complete (setKeyword)
Your code may be simplified if you merge keyword & list in a single object.
var [searchState, setSearchState] = useState({keyword: '', list: objects.slice());
handleChange=(evt)=>{
let word = evt.target.value;
let wordLower= word.toLowerCase();
setSearchState({
keyword:word,
list: word?objects.filter(o=>o.title.toLowerCase().startsWith(wordLowerCase):objects.slice()
});
};
Or you can use class based component where the state is a single object.
Each object should have around 250 arrays in it, but for some reason, each of the objects has a single array except for the last one, which has 1250.
How can I spread out the responses so I can access each one individually?
const [coinData, setCoinData] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
createLocalStorage();
let existingLocalStorage = JSON.parse(localStorage.getItem('items'));
const fetchData = async () => {
const data = await Promise.all(
existingLocalStorage.map(obj =>
coinGecko.get(`/coins/${obj[0].coin}/market_chart/`, {
params: {
vs_currency: 'usd',
days: obj[0].time
}
}),
)
);
setCoinData(data);
setLoading(false);
};
fetchData();
}, []);
Here's the response:
response
I'm using create-react-app, and testing with console.log in the browser
I was sending the times as strings ('day', 'week', 'month', 'year', 'max') I totally forgot I needed to convert them to number values. Since max was the only acceptable parameter, that's the only one that returned the response I was looking for
Try calling your method like below-
import axios from 'axios';
useEffect(() => {
createLocalStorage();
let existingLocalStorage = JSON.parse(localStorage.getItem('charts'));
const fetchData = async () => {
await axios.all([api1, api2]).then(axios.spread((...responses) => {
const resp1 = responses[0]
const resp2 = responses[1]
// use the results
})).catch(errors => {
// errors.
})
}
fetchData();
}, []);
Basically what I'm trying to achieve is to run some code only after a variable is not undefined anymore. However, I'm feeling very confused with the use of promises to await firebase responses, as well as with the use of React's useEffect hook.
I did some research that perhaps listeners are my answer to this? I couldn't really figure out how to implement them, though.
const weekday = moment().isoWeekday()
export const TimeGrid = () => {
const { userObject } = useContext(Context)
//(1) I wish to use the below to generate a list of times
const [LIST_OF_TIMES_FROM_DATABASE, SET_LIST_OF_TIMES_FROM_DATABASE] = useState([])
let businessId
function getBusinessId() {
if (userObject) businessId = userObject.businessId
}
getBusinessId()
//defines function that will run below inside of first useEffect
//also where the list of times is supposed to be set
function getWorkingHoursAccordingToDayOfTheWeek(day, snapshot) {
const workingHours = []
const ALL_AVAILABLE_TIMES = []
workingHours.push(snapshot.data().beginWeek)
workingHours.push(snapshot.data().endWeek)
const [begin, ending] = workingHours
let bgn = moment(begin, 'HH:mm')
const end = moment(ending, 'HH:mm')
let i = 0
while (bgn <= end) {
ALL_AVAILABLE_TIMES.push({
id: i,
type: 'time',
day: 'today',
time: bgn.format('HHmm'),
})
bgn = bgn.add(30, 'minutes')
i++
}
SET_LIST_OF_TIMES_FROM_DATABASE(ALL_AVAILABLE_TIMES) //(2) This is where I set the list, which won't work on its 1st run
}
useEffect(() => {
async function getTimesFromDB() {
const approved = await approvedBusinessService.doc(businessId)
const snapshotApproved = await approved.get()
if (snapshotApproved.exists) {
getWorkingHoursAccordingToDayOfTheWeek(weekday, snapshotApproved)
return
} else {
const pending = await businessPendingApprovalService.doc(businessId)
const snapshotPending = await pending.get()
if (snapshotPending.exists) {
getWorkingHoursAccordingToDayOfTheWeek(weekday, snapshotPending)
}
}
return
}
getTimesFromDB()
}, [userObject])
const allBookedTimes = allBookings.map(element => element.time)
//the below used to be 'listOfTimesJSON.map(item => item.time)' and used to work as it was a static JSON file
//(3) this is sometimes undefined... so all of the code below which relies on this will not run
const listOfTimes = LIST_OF_TIMES_FROM_DATABASE.map(item => item.time)
const [counts, setCounts] = useState({})
useEffect(() => {
const counts = {}
//(4) below will not work as listOfTimes won't exist (because of 'LIST_OF_TIMES_FROM_DATABASE' being undefined)
listOfTimes.forEach(x => {
counts[x] = (counts[x] || 0) + 1
if (allBookedTimes.includes(x)) counts[x] = counts[x] - 1
})
setCounts(counts)
}, [])
const sortedListOfTimesAndCount = Object.keys(counts).sort() //(5) undefined, because 'counts' was undefined.
const displayListOfTimes = sortedListOfTimesAndCount.map(
time =>
time > currentTime && (
<>
<li>
{time}
</li>
</>
),
)
return (
<div>
<ul>{displayListOfTimes}</ul>
</div>
)
}
as per the comments in your code your steps 3 ,4 ,5 are dependent on LIST_OF_TIMES_FROM_DATABASE
and it's also not in sequence,
And you are expecting it to execute in the sequence,
1) First, it will execute all the thing which is outside the useEffect and it's dependency whenever there is change in state, so keep that in mind
2) useEffect(() => {...},[]) this will get called as soon as component mounts, so in most cases, you will get listOfTimes as [], because at that time API call might be in requesting data from server.
3) There are few unnecessary things like const [counts, setCounts] = useState({}) and useEffect(() => {...} , []), this is kind of overuse of functionality.
Sometimes you can do the things simply but with all these it becomes complicated
Solution :
So what you can do is just put all the code inside displayListOfTimes function and check for LIST_OF_TIMES_FROM_DATABASE if available then and only then proceed with steps 3,4,5. So you will never get LIST_OF_TIMES_FROM_DATABASE undefined
NOTE : And as per the code LIST_OF_TIMES_FROM_DATABASE either be blank
array or array with data but never undefined, and if you are getting undefined then you can check that while setting up the state
const weekday = moment().isoWeekday()
export const TimeGrid = () => {
const { userObject } = useContext(Context)
//(1) I wish to use the below to generate a list of times
const [LIST_OF_TIMES_FROM_DATABASE, SET_LIST_OF_TIMES_FROM_DATABASE] = useState([])
let businessId
function getBusinessId() {
if (userObject) businessId = userObject.businessId
}
getBusinessId()
//defines function that will run below inside of first useEffect
//also where the list of times is supposed to be set
function getWorkingHoursAccordingToDayOfTheWeek(day, snapshot) {
const workingHours = []
const ALL_AVAILABLE_TIMES = []
workingHours.push(snapshot.data().beginWeek)
workingHours.push(snapshot.data().endWeek)
const [begin, ending] = workingHours
let bgn = moment(begin, 'HH:mm')
const end = moment(ending, 'HH:mm')
let i = 0
while (bgn <= end) {
ALL_AVAILABLE_TIMES.push({
id: i,
type: 'time',
day: 'today',
time: bgn.format('HHmm'),
})
bgn = bgn.add(30, 'minutes')
i++
}
SET_LIST_OF_TIMES_FROM_DATABASE(ALL_AVAILABLE_TIMES) //(2) This is where I set the list, which won't work on its 1st run
}
useEffect(() => {
async function getTimesFromDB() {
const approved = await approvedBusinessService.doc(businessId)
const snapshotApproved = await approved.get()
if (snapshotApproved.exists) {
getWorkingHoursAccordingToDayOfTheWeek(weekday, snapshotApproved)
return
} else {
const pending = await businessPendingApprovalService.doc(businessId)
const snapshotPending = await pending.get()
if (snapshotPending.exists) {
getWorkingHoursAccordingToDayOfTheWeek(weekday, snapshotPending)
}
}
return
}
getTimesFromDB()
}, [userObject])
const displayListOfTimes = () => { // <----- Start - HERE
if(LIST_OF_TIMES_FROM_DATABASE && LIST_OF_TIMES_FROM_DATABASE.length) {
const counts = {}
const allBookedTimes = allBookings.map(element => element.time)
//(3) ------ this will never be undefined
const listOfTimes = LIST_OF_TIMES_FROM_DATABASE.map(item => item.time)
//(4) ------ now it will work always
listOfTimes.forEach(x => {
counts[x] = (counts[x] || 0) + 1
if (allBookedTimes.includes(x)) counts[x] = counts[x] - 1
})
//(5) ------ now it will work
const sortedListOfTimesAndCount = Object.keys(counts).sort()
return sortedListOfTimesAndCount.map(
time =>
time > currentTime && (
<>
<li>
{time}
</li>
</>
),
)
} else {
return <li>Nothing to show for now</li>
}
}
return (
<div>
<ul>{displayListOfTimes}</ul>
</div>
)
}
The problem is because you useEffect which uses listOfTimes isn't being called when the listOfTimes value updates after a fetch request.
You can define listOfTimes within the useEffect and add a dependency on LIST_OF_TIMES_FROM_DATABASE
const [counts, setCounts] = useState({})
useEffect(() => {
// Defining it inside because if we don't and add allBookedTimes as a dependency then it will lead to an infinite look since references will change on each re-render
const allBookedTimes = allBookings.map(element => element.time)
const listOfTimes = LIST_OF_TIMES_FROM_DATABASE.map(item => item.time)
const counts = {}
listOfTimes.forEach(x => {
counts[x] = (counts[x] || 0) + 1
if (allBookedTimes.includes(x)) counts[x] = counts[x] - 1
})
setCounts(counts)
}, [LIST_OF_TIMES_FROM_DATABASE, allBookings]); // Add both dependencies. This will ensure that you update counts state when the state for times changes