Related
I want to fetch data from API and trying to use in index.js
Error:
TypeError: Cannot read properties of undefined (reading 'icon')
When I console.log it prints
Empty Array of index.js > response data of weatherSlice.js > and finally response data of index.js as an array
I was getting undefined error and tried this and kinda worked .
{getCity.length !== 0 && (
<Typography variant="h6" component="div">
<div>Wind : {getCity.current.gust_kph} kph</div>
<div>Pressure: {getCity.current.pressure_in} in</div>
</Typography>
)}
But this time it gives same error on this block
{getCity.length !== 0 && (
<Grid item xs={3} sx={{mb:3}}>
<div className="label">{getDate()}</div>
<div className="label"> <img src={`${getCity.forecast.forecastday[1].condition.icon}`} alt="" /></div> // Gives undefined (reading"icon")
<div className="label" >22 C</div>
</Grid>)}
Index.js
const dispatch = useDispatch();
const [selectedCity, setSelectedCity] = useState('Ankara');
const getCity = useSelector((state) => state.weather.item);
const [datee , setDate ] = useState('');
useEffect(() => {
dispatch(fetchDefault(selectedCity))
setDate(getDate())
}, [dispatch])
weatherSlice
export const fetchDefault = createAsyncThunk('weather/getWeather', async (selectedCity) => {
const res = await axios(`http://api.weatherapi.com/v1/forecast.json?key=ebb6c0feefc646f6aa6124922211211&q=${selectedCity}&days=10&aqi=no&alerts=no
`)
return res.data});
export const weatherSlice = createSlice({
name: "weather",
initialState : {
item : [],
},
reducers:{},
extraReducers:{
[fetchDefault.fulfilled]: (state , action) => {
state.item = action.payload;
console.log(state.item)
},
[fetchDefault.pending]: (state , action) => {
console.log("sadsad")
}
}
After the state.weather.item state is populated it's now an object, not an array. The initial condition getCity.length !== 0 works/passes because getCity.length is undefined and undefined !== 0 evaluates true. The issue is occurring after you start accessing into the state.
The fetched city data is an object with location, current, and forecast properties.
// 20211114135700
// https://api.weatherapi.com/v1/forecast.json?key=ebb6c0feefc646f6aa6124922211211&q=seattle&days=10&aqi=no&alerts=no
{
"location": {
"name": "Seattle",
"region": "Washington",
"country": "United States of America",
"lat": 47.61,
"lon": -122.33,
"tz_id": "America/Los_Angeles",
"localtime_epoch": 1636927019,
"localtime": "2021-11-14 13:56"
},
"current": {
"last_updated_epoch": 1636926300,
"last_updated": "2021-11-14 13:45",
"temp_c": 16.1,
"temp_f": 61.0,
"is_day": 1,
"condition": {
"text": "Light rain",
"icon": "//cdn.weatherapi.com/weather/64x64/day/296.png",
"code": 1183
},
"wind_mph": 13.6,
"wind_kph": 22.0,
"wind_degree": 190,
"wind_dir": "S",
"pressure_mb": 1014.0,
"pressure_in": 29.94,
"precip_mm": 1.0,
"precip_in": 0.04,
"humidity": 90,
"cloud": 100,
"feelslike_c": 16.1,
"feelslike_f": 61.0,
"vis_km": 3.2,
"vis_miles": 1.0,
"uv": 4.0,
"gust_mph": 18.8,
"gust_kph": 30.2
},
"forecast": {
"forecastday": [
{
"date": "2021-11-14",
"date_epoch": 1636848000,
"day": {
"maxtemp_c": 16.2,
"maxtemp_f": 61.2,
"mintemp_c": 11.5,
"mintemp_f": 52.7,
"avgtemp_c": 14.9,
"avgtemp_f": 58.8,
"maxwind_mph": 16.1,
"maxwind_kph": 25.9,
"totalprecip_mm": 21.1,
"totalprecip_in": 0.83,
"avgvis_km": 9.3,
"avgvis_miles": 5.0,
"avghumidity": 93.0,
"daily_will_it_rain": 1,
"daily_chance_of_rain": 99,
"daily_will_it_snow": 0,
"daily_chance_of_snow": 0,
"condition": {
"text": "Heavy rain",
"icon": "//cdn.weatherapi.com/weather/64x64/day/308.png",
"code": 1195
},
"uv": 1.0
},
"astro": {
...
},
"hour": [
...
]
},
...
]
}
}
You are attempting to render the forecast, but here's where the code really goes sideways. You're assuming there's at least 2 elements (i.e. the getCity.forecast.forecastday.length>= 2), and then if there is, assume theres aconditionproperty. When there isn't andgetCity.forecast.forecastday[1].condition` is undefined, this is the error you are seeing.
From what I can tell, the condition property is nested within a day field. Since it's unclear what properties are guaranteed to exist in the response data your best bet is to:
First make sure you are accessing into the correct path
Use null-checks/guard-clauses or Optional Chaining operator to prevent accidental null/undefined accesses
The updated object property path is as follows:
getCity.forecast.forecastday.[1].day.condition.icon
If any of these are potentially undefined or otherwise not returned in the data, the corrected access using the Optional Chaining operator is as follows:
getCity.forecast?.forecastday?.[1]?.day?.condition?.icon
Which is the equivalent null-check/guard-clause version of:
getCity.forecast
&& getCity.forecast.forecastday
&& getCity.forecast.forecastday[1]
&& getCity.forecast.forecastday[1].day
&& getCity.forecast.forecastday[1].day.condition
&& getCity.forecast.forecastday[1].day.condition.icon
Use the same sort of check for the current weather data:
{getCity.current && (
<Typography variant="h6" component="div">
<div>Wind : {getCity.current.gust_kph} kph</div>
<div>Pressure: {getCity.current.pressure_in} in</div>
</Typography>
)}
Finally, update the initial state for the weather slice to match the data invariant, it should be an object.
initialState : {
item : {},
},
I have a dataset in the following format, it's an array of arrays called filtered_data
["docmetaID", "pathwaySeqID", "srcstrID", "accstrID", "processDescription", "productID", "collection", "doctype", "studytype"]
["1", "108", "Feces, Brownwater", "Process Residual Stream", "Adsorption", "PREC", "SANAGRI", "JOUR", ""]
["1", "108", "Feces, Brownwater", "Process Residual Stream", "Desorption", "PREC", "SANAGRI", "JOUR", ""]
["1", "108", "Feces, Brownwater", "Process Residual Stream", "Precipitation", "PREC", "SANAGRI", "JOUR", ""]
I can filter this array for a specific column, say processDescription by using:
filtered_data = parsed.data.filter(function(result) {
return result[5] === 'Adsorption';
})
However I would like to do multiple filters but that are generated dynamically, such that I go through a loop and just add what i need to the return statement, like below:
filtered_data = filtered_data.filter(function(result) {
return result[5] === 'Adsorption' || result[5] === 'Desorption' && results[6] === 'PREC'
})
I have experimented with a few ideas including possibly using eval() to generate the return statement. Other examples I've seen have been using an array of objects for their dataset and not an array of arrays like mine and use .every() but I just can't seem to apply it to my case.
I also thought about running the filter for each iteration and pushing the results into another array but that seems a bit clumsy.
Note that I will need the condition to be OR or AND depending on the filter type, filter type being result[6] or result[5] etc. For example:
&& results[6] === 'PREC'
is AND from the but result[5] needs to be OR from the example above
First, the data structure you have is not very object oriented. It's more like CSV format. I would suggest converting it to an array of objects:
let data = [
["docmetaID", "pathwaySeqID", "srcstrID", "accstrID", "processDescription", "productID", "collection", "doctype", "studytype"],
["1", "108", "Feces, Brownwater", "Process Residual Stream", "Adsorption", "PREC", "SANAGRI", "JOUR", ""],
["1", "108", "Feces, Brownwater", "Process Residual Stream", "Desorption", "PREC", "SANAGRI", "JOUR", ""],
["1", "108", "Feces, Brownwater", "Process Residual Stream", "Precipitation", "PREC", "SANAGRI", "JOUR", ""]
];
let [header, ...rows] = data;
let arr = rows.map(row => Object.fromEntries(
row.map((val, i) => [header[i], val])
));
console.log(arr);
Then, you could format your filters as objects too, which would have the same properties. For instance, the filter you have in your example:
result[5] === 'Adsorption' || result[5] === 'Desorption' && results[6] === 'PREC'
...could be structured as follows:
[{
productID: 'Adsorption'
}, {
productID: 'Desorption',
collection: 'PREC'
}]
So one object in such a filter structure represents AND-conditions. Other objects represent alternatives, so they relate to each other with a logical OR.
Here is how such a filter object can be applied to the data set:
function transform(data) { // from the code above
let [header, ...rows] = data;
return rows.map(row => Object.fromEntries(
row.map((val, i) => [header[i], val])
));
}
function filter(rows, conditions) {
return rows.filter(row =>
// Perform OR-operation on condition objects
conditions.some(condition =>
// Perform AND-operation on condition-object properties
Object.entries(condition).every(([k, v]) => row[k] == v)
)
);
}
// Demo:
let data = [["docmetaID", "pathwaySeqID", "srcstrID", "accstrID", "processDescription", "productID", "collection", "doctype", "studytype"],["1", "108", "Feces, Brownwater", "Process Residual Stream", "Adsorption", "PREC", "SANAGRI", "JOUR", ""], ["1", "108", "Feces, Brownwater", "Process Residual Stream", "Desorption", "PREC", "SANAGRI", "JOUR", ""], ["1", "108", "Feces, Brownwater", "Process Residual Stream", "Precipitation", "PREC", "SANAGRI", "JOUR", ""]];
let conditions = [{ processDescription: 'Adsorption'}, {processDescription: 'Desorption', productID: 'PREC' }];
let rows = transform(data);
let result = filter(rows, conditions);
console.log(result);
Using the original data format
Although it is best practice to work with the object array format all the way, I will provide here the code for when you have reasons to stick with the original format. The format for the filter object will remain the same:
function filter(data, conditions) {
let [header, ...rows] = data;
let headerToIndex = Object.fromEntries(header.map((name, i) => [name, i]));
return [header, ...rows.filter(row =>
// Perform OR-operation on condition objects
conditions.some(condition =>
// Perform AND-operation on condition-object properties
Object.entries(condition).every(([k, v]) => row[headerToIndex[k]] == v)
)
)];
}
// Demo:
let data = [["docmetaID", "pathwaySeqID", "srcstrID", "accstrID", "processDescription", "productID", "collection", "doctype", "studytype"],["1", "108", "Feces, Brownwater", "Process Residual Stream", "Adsorption", "PREC", "SANAGRI", "JOUR", ""], ["1", "108", "Feces, Brownwater", "Process Residual Stream", "Desorption", "PREC", "SANAGRI", "JOUR", ""], ["1", "108", "Feces, Brownwater", "Process Residual Stream", "Precipitation", "PREC", "SANAGRI", "JOUR", ""]];
let conditions = [{ processDescription: 'Adsorption'}, {processDescription: 'Desorption', productID: 'PREC' }];
let result = filter(data, conditions);
console.log(result);
The output includes the header row. If you don't want that, then just omit [header, ... and the closing bracket from the return statement.
The easiest way would be to set an array of conditions and check every one.
You could achieve this by doing it so:
// let conditions be a two dimensional array with
// the table column to check and the value to compare to
let conditions = [];
conditions.push([5, "Adsorption"]);
conditions.push([5, "Desorption"]);
array.filter((row, index) => {
// skip the header with the index-test
// and example
return index && conditions.every((condition) => row[condition[0]] == condition[1]);
// or example
return index && conditions.filter((condition) => row[condition[0]] == condition[1]).length
// or example (faster, doesn't have to to test all)
for (let i in conditions)
if (index && row[conditions[i][0]] == conditions[i][1])
return true;
return false;
})
I want to paginate through all the results for a specific of a ChartMogul Customers.All() API call. I'm using the Chartmogul NPM package.
It works, I can get all the results, but I have more results than the max number of records allowed in a single call, so I need to page through multiple pages of results. And I'm stuck how to elegantly do it.
I could do multiple calls first grabbing the total amount of pages, followed by a loop that goes through all pages. Saving all results in one big object.
This is the call, with the page variable should get updated to x page.
ChartMogul.Customer.all(config, {
status: 'Active',
per_page: 200,
page: page
}, function (err, res) {
if (err) reject(err);
console.log(res);
resolve(res.entries);
});
Each API response from chartmogul contains the following details
{
"entries":[
{
"id": 25647,
"uuid": "cus_de305d54-75b4-431b-adb2-eb6b9e546012",
"external_id": "34916129",
"external_ids": ["34916129"],
"data_source_uuid": "ds_610b7a84-c50f-11e6-8aab-97d6db98913a",
"data_source_uuids": ["ds_610b7a84-c50f-11e6-8aab-97d6db98913a"],
"name": "Example Company",
"company": "",
"email": "bob#examplecompany.com",
"status": "Active",
"lead_created_at": "2015-01-01T10:00:00-04:00",
"free_trial_started_at": "2015-01-09T10:00:00-04:00",
"customer-since": "2015-06-09T13:16:00-04:00",
"city": "Nowhereville",
"state": "Alaska",
"country": "US",
"zip": "0185128",
"attributes":{
"tags": ["engage", "unit loss", "discountable"],
"stripe":{
"uid": 7,
"coupon": true
},
"clearbit":{
"company":{
"name": "Example Company",
"legalName": "Example Company Inc.",
"domain": "examplecompany.com",
"url": "http://examplecompany.com",
"category":{
"sector": "Information Technology",
"industryGroup": "Software and Services",
"industry": "Software",
"subIndustry": "Application Software"
},
"metrics":{
"raised": 1502450000,
"employees": 1000,
"googleRank": 7,
"alexaGlobalRank": 2319,
"marketCap": null
},
},
"person":{
"name":{
"fullName": "Bob Kramer"
},
"employment":{
"name": "Example Company"
}
}
},
"custom":{
"CAC": 213,
"utmCampaign": "social media 1",
"convertedAt": "2015-09-08 00:00:00",
"pro": false,
"salesRep": "Gabi"
}
},
"address":{
"address_zip": "0185128",
"city": "Nowhereville",
"country": "US",
"state": "Alaska"
},
"mrr": 3000,
"arr": 36000,
"billing-system-url": "https:\/\/dashboard.stripe.com\/customers\/cus_4Z2ZpyJFuQ0XMb",
"chartmogul-url": "https:\/\/app.chartmogul.com\/#customers\/25647-Example_Company",
"billing-system-type": "Stripe",
"currency": "USD",
"currency-sign": "$"
},
{"...49 more...": "...entries..."}
],
"has_more": true,
"per_page": 50,
"page": 1,
"current_page": 1,
"total_pages": 4
}
So how can I loop through all pages of a response in a simple, elegant way?
This pattern should get you well on your way.
Use a function that returns the main promise and when there are more you return a recursive call to that function (another promise) or you return the combined results.
Following is untested and having never used this API might need some tweaking.
function getEntries(config, page=0, entries = []) {
// return promise
return ChartMogul.Customer.all(config, {
status: 'Active',
per_page: 200,
page: page
}).then(res => {
// merge into main array
entries = [...entries, ...res.entries];
// return another promise if there are more ... or return the combined array
return res.has_more ? getEntries(config, res.page+1, entries) : entries;
});
}
getEntries(config)
.then(entries => console.log(entries) )
.catch(err => console.error(err) )
Like charlietfl I recommend you use promises. The difference is I would start all the requests at the same time and then wait for all of them to finish.
Depending on the number of pages you need to get this method is faster, but it might send too many requests if you need more than 40 requests/second
This can only be done if you know how many pages there are beforehand.
(This has not been tested since i don't have a api key)
// start a promise for every page given
function startRequests(pages) {
return pages.map((page) => ChartMogul.Customer.all(config, {
status: 'Active',
per_page: 200,
page: page
}))
}
// an array of all the pages you want to get from the server.
// the function below could be used to generate the page numbers
// Array.from({length: numberOfPages}, (x,i) => i + 1);
let pages = [1,2,3,4]
let requestPromises = startRequests(pages)
// wait for the promises to finish
Promise.all(requestPromises)
.then(function(results) {
// combine all the results into a single array
return results.reduce((combinedResults, result) => [...combinedResults, result.entries], [])
}).then((combinedResults) => {
// do whatever is needed
}).catch(err => console.error(err));
Basically I want to see how many people purchase the same vendor and how many purchased different brands together. I already know the brands beforehand
I would like to loop through all the objects in this JSON response and if the "vendor" value for all "line_items" are the same then console log "these orders have same brands"+ order number. If they are different console log "these orders have different brands" + order number
{
"orders": [
{
"email": "bob.norman#hostmail.com",
"order_number": 1001,
"line_items": [
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
}
]
},
{
"email": "john.candy#hostmail.com",
"order_number": 1002,
"line_items": [
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Apple"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
}
]
}
]
}
Let's start with a function that checks if all elements of an array are equal:
const allEqual = ([x, ...ys]) =>
ys.every(y => y === x);
console.log(
allEqual([1, 2, 2]), // false
allEqual([1, 1, 1]), // true
allEqual([1]), // true
allEqual([]) // true
);
With this out of the way, we can check if a list of item is from a single vendor like so:
const itemsAreSameVendor = items =>
allEqual(items.map(({ vendor }) => vendor));
To check if an order is a "single vendor order", we write:
const orderIsSingleVendor = ({ line_items }) =>
itemsAreSameVendor(line_items);
Now, finding the single orders is a matter of:
const singleCount = orderData.orders.filter(orderIsSingleVendor).length;
const multiCount = orderData.orders.length - singleCount;
Or, you can use reduce to make two handy groups:
const allEqual = ([x, ...ys]) =>
ys.every(y => y === x);
const itemsAreSameVendor = items =>
allEqual(items.map(({ vendor }) => vendor));
const orderIsSingleVendor = ({ line_items }) => itemsAreSameVendor(line_items);
console.log(
orderData().orders.reduce(
({ singleVendorOrders, multiVendorOrders }, order) => {
// Check if this order contains multiple vendors
const orderType = orderIsSingleVendor(order)
? singleVendorOrders
: multiVendorOrders;
// Push to the right list
orderType.push(order.order_number);
return { singleVendorOrders, multiVendorOrders };
},
// Format of the result
{ singleVendorOrders: [], multiVendorOrders: [] }
)
)
function orderData() {
return {
"orders": [{
"email": "bob.norman#hostmail.com",
"order_number": 1001,
"line_items": [{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
}
]
},
{
"email": "john.candy#hostmail.com",
"order_number": 1002,
"line_items": [{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Apple"
},
{
"id": 466157049,
"variant_title": "green",
"vendor": "Windows"
}
]
}
]
};
}
P.S.: You haven't really shown what exact part you need help with... If you add your own attempt to the question, you'll get better answers and people will be able to point to existing answers solving similar problems.
Loading the JSON into your code
First you have to load the JSON. If you want to load it from a local archive you'd have to load it creating a function for this. Since loading archives is an asynchronous action, you need to pass a callback to tell the function what to do with that data after it finishes loading. In that callback you'll tell your program what to do with the JSON.
Here's a function implementation to do this. You could also make it for general use and give it a param to especify the file location that then replaces the 'my_data.json' part.
If you want to get the JSON using a GET call to an API or your own backend using Node.js (since the tags of the question say node.js) you can use Node.js's HTTPS module .
With this module you'll want to use especifically this part which is a GET passing either options or an URL with a callback.
Handling the JSON
Once you have your JSON ready and stored in a variable you'll need to parse it first since it'll come as an string.
var parsedJSON = JSON.parse(myJson); //Where myJson is a variable where it is stored
Then you'll have your Javascript object with nested array of objects ready to go.
Now onto the loop:
parsedJSON.orders.forEach((order) => {
let areEqual = order['line_items'].reduce((result, item, currentIndex, items) => {
if (currentIndex === 0) {
return true;
}
if (currentIndex - 1 < items.length) {
const previousVendor = items[currentIndex - 1].vendor; //Previous one
if (y.vendor !== previousVendor) //We compare them
return result && false; //Since they are different we use the logic operator && with false so this is enough to say that there is an instance where there are at least two different
}
return result && true; // <= If they are equal. We can just return acc aswell
});
if (areEqual) console.log(order['order_number']);
});
If you want this to work for IE11 and less be aware that you can't use arrow functions like I did. Just change them for regular function expressions. Here's more info about the reduce function from Javascript array's
I am trying to extract "animal" and "fish" hashtags from the JSON object below. I know how to extract the first instance named "animal", but I have no idea how to extract both instances. I was thinking to use a loop, but unsure where to start with it. Please advise.
data = '{"hashtags":[{"text":"animal","indices":[5110,1521]},
{"text":"Fish","indices":[122,142]}],"symbols":[],"user_mentions":
[{"screen_name":"test241","name":"Test
Dude","id":4999095,"id_str":"489996095","indices":[30,1111]},
{"screen_name":"test","name":"test","id":11999991,
"id_str":"1999990", "indices":[11,11]}],"urls":[]}';
function showHashtag(data){
i = 0;
obj = JSON.parse(data);
console.log(obj.hashtags[i].text);
}
showHashtag(data);
Use Array.prototype.filter():
let data = '{"hashtags":[{"text":"animal","indices":[5110,1521]},{"text":"Fish","indices":[122,142]}],"symbols":[],"user_mentions":[{"screen_name":"test241","name":"Test Dude","id":4999095,"id_str":"489996095","indices":[30,1111]}, {"screen_name":"test","name":"test","id":11999991, "id_str":"1999990", "indices":[11,11]}],"urls":[]}';
function showHashtag(data){
return JSON.parse(data).hashtags.filter(e => /animal|fish/i.test(e.text))
}
console.log(showHashtag(data));
To make the function reusable, in case you want to find other "hashtags", you could pass an array like so:
function showHashtag(data, tags){
let r = new RegExp(tags.join("|"), "i");
return JSON.parse(data).hashtags.filter(e => r.test(e.text))
}
console.log(showHashtag(data, ['animal', 'fish']));
To get only the text property, just chain map()
console.log(showHashtag(data, ['animal', 'fish']).map(e => e.text));
or in the function
return JSON.parse(data).hashtags
.filter(e => /animal|fish/i.test(e.text))
.map(e => e.text);
EDIT:
I don't really get why you would filter by animal and fish if all you want is an array with ['animal', 'fish']. To only get the objects that have a text property, again, use filter, but like this
let data = '{"hashtags":[{"text":"animal","indices":[5110,1521]},{"text":"Fish","indices":[122,142]}],"symbols":[],"user_mentions":[{"screen_name":"test241","name":"Test Dude","id":4999095,"id_str":"489996095","indices":[30,1111]}, {"screen_name":"test","name":"test","id":11999991, "id_str":"1999990", "indices":[11,11]}],"urls":[]}';
function showHashtag(data){
return JSON.parse(data).hashtags
.filter(e => e.text)
.map(e => e.text);
}
console.log(showHashtag(data));
For me, Lodash can be of great use here, which have different functions in terms of collections. For your case i'd use _.find function to help check the array and get any of the tags with the creteria passed in as second argument like so:
.find(collection, [predicate=.identity], [fromIndex=0])
source npm package
Iterates over elements of collection, returning the first element
predicate returns truthy for. The predicate is invoked with three
arguments: (value, index|key, collection).
with your case this should work
var data = '{ "hashtags": [ { "text": "animal", "indices": [ 5110, 1521 ] }, { "text": "Fish", "indices": [ 122, 142 ] } ], "symbols": [], "user_mentions": [ { "screen_name": "test241", "name": "Test \n Dude", "id": 4999095, "id_str": "489996095", "indices": [ 30, 1111 ] }, { "screen_name": "test", "name": "test", "id": 11999991, "id_str": "1999990", "indices": [ 11, 11 ] } ], "urls": [] }';
var obj = JSON.parse(data);
_.find(obj.hashtags, { 'text': 'animal' });
// => { "text": "animal", "indices": [ 5110, 1521 ] }
For simple parsing like this one, I would use the plain old obj.forEach() method, it is more readable and easy to understand, especially for javascript beginner.
obj = JSON.parse(data).hashtags;
obj.forEach(function(element) {
console.log(element['text']);
});