I am trying to set up a block of code to prepare to setState, however, I'm running into an issue mapping a list in the render section as reactjs is telling me map is not a function. I don't think I'm setting this up correctly initially and it should be an array of objects instead of object arrays.
My goal is to set up a list. The names on the left side. The sum total of ondinResult and cmfResult on the right side. Below is the result I should expect:
This is how the data from the API is after calling the GET request:
"fileResults": {
"incFiles": [
{
"assetManagerId": 5,
"name": "BlackRock",
"odinResult": {
"total": 5,
"success": 2,
"error": 3
},
"cmfResult": {
"total": 0,
"success": 0,
"error": 0
}
},
{
"assetManagerId": 8,
"name": "Barings",
"odinResult": {
"total": 0,
"success": 0,
"error": 0
},
"cmfResult": {
"total": 10,
"success": 8,
"error": 2
}
},
{
"assetManagerId": 11,
"name": "AIM Derivatives",
"odinResult": {
"total": 6,
"success": 4,
"error": 2
},
"cmfResult": {
"total": 0,
"success": 0,
"error": 0
}
},
{
"assetManagerId": 11,
"name": "AIM Derivatives",
"odinResult": {
"total": 0,
"success": 0,
"error": 0
},
"cmfResult": {
"total": 8,
"success": 2,
"error": 6
}
}
],
"odinTotal": 11,
"cmfTotal": 18
},
My code block I'm currently setting up before setState:
//mapping odin and cmf results then adding the totals together
let odinTotal = response.data.fileResults.incFiles.map(item => item.odinResult.total)
let cmfTotal = response.data.fileResults.incFiles.map(item => item.cmfResult.total)
const legendData = {
labels: response.data.fileResults.incFiles.map(item => item.name),
totals: odinTotal.map(function (num, idx) {
return num + cmfTotal[idx]
})
}
My result is this from the above:
After deconstructing my state I tried to map it out in under render but get an error of: "Cannot read property 'map' of undefined."
<ul>
{legendData.labels.map(item => (
<li key={item}>{item}</li>
))}
</ul>
It sounds like you are fetching some data when the component mounts, so you need to likely provide some initial empty array value to legendData's labels array.
state = {
legendData: {
labels: [],
totals: [],
},
}
Then as long as your data loading logic also returns and updates state with an array your render logic will work.
Another option is to use a guard pattern on the mapping function to ensure the labels property exists and has a length property.
<ul>
{legendData && legendData.labels.length && legendData.labels.map(item => (
<li key={item}>{item}</li>
))}
</ul>
A react component in which you use map should always have a Intial state to empty array or empty object based on requirement.
check for the condition:
{legendData && legendData.labels.length ?
legendData.labels.map(item =>(
<li key={item}>{item}</li>
)) : null}
Related
Im trying to take JSON data and pass it into my 'HistoryChart' Component to try and map the dates and prices into two arrays so that I can present them on my chart. However, I keep getting undefined errors.
Here is the JSON Data:
{
"_id": 1,
"name": "",
"brand": "",
"image": "",
"sources": [],
"history": [
{
"_id": 3,
"price": "299.99",
"product": 1,
"date": "2021-07-01"
},
{
"_id": 4,
"price": "399.99",
"product": 1,
"date": "2021-07-08"
},
{
"_id": 5,
"price": "499.99",
"product": 1,
"date": "2021-07-15"
},
{
"_id": 6,
"price": "599.99",
"product": 1,
"date": "2021-07-22"
},
{
"_id": 7,
"price": "699.99",
"product": 1,
"date": "2021-07-29"
}
]
}
Here is my HistoryChart Component:
function HistoryChart({product}) {
var dates = product.history.map(function(e){ //<-- The Problem lies here where it says cannot map undefined.
return e.date;
});
var prices = product.history.map(function(e){
return e.price;
});
return (
<div>
<Line
data={{
labels: dates,
datasets: [{
label: `Average Price History (ID: ${product._id})`, //<-- This part works
backgroundColor:/* 'transparent' */ '#00ad0e',
borderColor: '#00ad0e',
data: prices,
}]
}}
width={100}
height={50}
options={{ maintainAspectRatio: true }}
/>
</div>
)
}
I am also using redux to get the Data:
const productDetails = useSelector(state => state.productDetails)
const {error, loading, product} = productDetails
And the data is passed into the HistoryChart Component like this:
<HistoryChart product={product}/>
Any Help would be Much appreciated, Thanks.
Sorry if this is not your principal problem, but same time when .map resulting in undefined the most simple adjust is verify if your array is undefined.
So in my projects i always check first if array is undefined, i will use your code to do a example
function HistoryChart({product}) {
if (product !== undefined){
var dates = product.history.map(function(e){
return e.date;
});
var prices = product.history.map(function(e){
return e.price;
});
}
Try this aproach and let me know if this work.
Cause of Error
As the data come from server, it take some time to load. and you get the undefine error because 1st time you want to access history of object product which is not yet loaded successfully.
Solution
const price = product && product.history.map(//do what you want)
use key values of object this way not cause any error because if product is not loaded it does'nt call map function and when product object loaded successfully it will call map function
I am trying to build a pie chart in react js which uses highcharts (https://api.highcharts.com/highcharts/) is accepting only the following format for pie chart data (or maybe I'm wrong): Sample Fiddle here: https://jsfiddle.net/react_user1/e9cbsrdL/1/
data: [
{name: 'abc', y: 10},
{name: 'def', y: 90}
]
The data I get from my API looks something like this:
const counts:[
{
"id": "all",
"type": "all",
"count": 1403
},
{
"id": "bad",
"type": "bad",
"count": 0
},
{
"id": "failed",
"category": false,
"type": "failed",
"count": 58
},
{
"id": "changed",
"category": true,
"type": "changed",
"count": 123
}
So I am trying to achieve three things here:
1. Remove the first {}, with the "id": "all"
2. Rename the key: "id" to name & "count" to y
3. Remove the keys: "type" & "category" & their data
Thanks for any help you could provide, even a partial answer that can help would be appreciated.
I think you can use Array.prototype.filter() and Array.prototype.map() combination.
With filter() you can remove the value what you don't need - in your case all - then with map() you can create a new structure for you array.
From the documentations - link mentioned above:
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
Just like this:
const counts = [
{
"id": "all",
"type": "all",
"count": 1403
},
{
"id": "bad",
"type": "bad",
"count": 0
},
{
"id": "failed",
"category": false,
"type": "failed",
"count": 58
},
{
"id": "changed",
"category": true,
"type": "changed",
"count": 123
}
];
const result = counts.filter(f => f.id !== 'all')
.map(e => ({ name: e.id, y: e.count }));
console.log(result);
I hope this helps!
You can also provide data as an array of arrays:
series: [{
data: (() => counts.map(
item => [item.id, item.count]
))()
}]
Live demo: https://jsfiddle.net/BlackLabel/op4s13dm/
try this
renderarrayobjects = () => {
return this.state.arrayname.map((Arrayelement, Indexvalue) => {
return (
<View style={styles.btnColor}>
<Text style={styles.tagtext}>{Arrayelement}</Text>
<TouchableOpacity
Indexvalue={Indexvalue}
onPress={() => {
this.remove(Arrayelement);
}}>
<Image style={styles.editskill} source={deletarray} />
</TouchableOpacity>
</View>
);
}); };
The index value is the Index value.
this will render a list of Array items and we add an image that will be pressed to delete array the cross image will appear after every element of an array
removePeople(Arrayelement) {
var array = [...this.state.Arrayelement];
var index = array.indexOf(Arrayelement);
if (index !== -1) {
array.splice(index, 1);
this.setState({Arrayelement: array});
} }
this method will delete array objects.
Hope it helps .feel free for doubts
I am trying to seperate items by an attribute named 'ProductGroupName' that is returned in the restful API. I want to loop through and create a seperate array for each section with the items listed as that attribute inside each.
I can manipulate my SQL on the server side to change how results are given but it would cause problems; here is an example result:
{
"message": "Success",
"data": [
{
"StockID": 69323,
"TradeName": "NAN OPTIPRO HA 1 GLD 800G",
"ProductGroupName": "BABY FOODS",
"SOH": 24,
"MinimumSOH": 0,
"Retail": 3199,
"AverageRetail": 0,
"Cost": 2848,
"RealCost": 2791,
"Reorder": true,
"Message": null,
"ListCost": null,
"Markup": 12,
"PLU": "476358",
"NoDiscount": true
},
{
"StockID": 18057,
"TradeName": "NAN PRO 2 GLD 800G",
"ProductGroupName": "BABY FOODS",
"SOH": 19,
"MinimumSOH": 0,
"Retail": 2050,
"AverageRetail": 0,
"Cost": 2301,
"RealCost": 1918,
"Reorder": false,
"Message": null,
"ListCost": null,
"Markup": -10,
"PLU": "436178",
"NoDiscount": true
},
{
"StockID": 74206,
"TradeName": "OPTIFAST VLCD SHAKE BANANA 12X53G (NEW)",
"ProductGroupName": "WEIGHT LOSS",
"SOH": 6,
"MinimumSOH": 0,
"Retail": 4799,
"AverageRetail": 0,
"Cost": 3937,
"RealCost": 3086,
"Reorder": true,
"Message": null,
"ListCost": null,
"Markup": 10,
"PLU": "294454",
"NoDiscount": true
}],
}
This is the standard output of all results though, so changing the result would complicate how I have written my REST service.
What is the best way to group these items?
I was considering looping through them, and when a new 'ProductGroup' is found, a new Array is created and that item is inserted, but that doesn't seem very optimised and I would like to know if theres a better way to achieve this.
One of the simplest way to organize the result of the request in a way you described in your question is to apply Array.prototype.reduce to result's "data":
result.data.reduce((acc, item) => {
const name = item['ProductGroupName'];
acc[name] = acc[name] || [];
acc[name].push(item);
return acc;
}, {});
With this approach you will get following object
{BABY FOODS: Array(2), WEIGHT LOSS: Array(1)}
I'm trying to extract "translations" Array "text" and "verses" array "verse_key" data from below json response using Alamofire and swift3.
{
"verses": [
{
"id": 1,
"verse_number": 1,
"chapter_id": 1,
"verse_key": "1:1",
"text_madani": "بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ",
"text_indopak": "بِسْمِ اللّٰهِ الرَّحْمٰنِ الرَّحِيْمِ",
"text_simple": "بسم الله الرحمن الرحيم",
"juz_number": 1,
"hizb_number": 1,
"rub_number": 1,
"sajdah": null,
"sajdah_number": null,
"page_number": 1,
"audio": {
"url": "versesAbdulBaset/Mujawwad/mp3/001001.mp3",
"duration": 6,
],
"format": "mp3"
},
"translations": [
{
"id": 102574,
"language_name": "english",
"text": "In the name of Allah, the Beneficent, the Merciful.",
"resource_name": "Shakir",
"resource_id": 21
}
],
}
],
"meta": {
"current_page": 1,
"next_page": null,
"prev_page": null,
"total_pages": 1,
"total_count": 7
}
}
I'm new to swift and I can't find a way to achieve this. How can I get the values of "translations" Array "text" and "verses" array "verse_key" ?
thanks advance
Use swiftyJSON.
switch response.result{
case .success(let data) :
let json = JSON(data)
let verses = json["verses"].Stringvalue
print(verses) //get all verses
print(verses["verse_key"].Stringvalue) // get "verse_key"
break
You can take each values from this json by giving the key names. If you want to get the "verses" , use json["verses"].You can also use JSONdecoder.
So this is a problem that I have no idea where to even start so even just a pointer in the right direction would be great.
So I have data that looks like so:
data = {
"agg": {
"agg1": [
{
"keyWeWant": "*-20.0",
"asdf": 0,
"asdf": 20,
"asdf": 14,
"some_nested_agg": [
{
"keyWeWant2": 20,
"to": 25,
"doc_count": 4,
"some_nested_agg2": {
"count": 7,
"min": 2,
"max": 5,
"keyWeWant3": 2.857142857142857,
"sum": 20
}
},
{
"keyWeWant2": 25,
"to": 30,
"doc_count": 10,
"some_nested_agg2": {
"count": 16,
"min": 2,
"max": 10,
"keyWeWant3": 6.375,
"sum": 102
}
}
]
},
{
...
},
{
...
},
...
]
}
}
Now from the example, within 'agg' there are N 'agg1' results, within each 'agg1' result there is a 'keyWeWant'. Each 'agg1' result also has a list of 'some_nested_agg' results which each contain a 'keyWeWant2'. Each 'keyWeWant2' value is associated a single 'keyWeWant' value somewhere up in the hierarchy. Similarly each 'keyWeWant2' also contains a set of results for 'some_nested_agg2' (not a list but rather a map this time). Each of the set of results contains a 'keyWeWant3'.
Now I want to flatten this structure while still preserving the association between 'keyWeWant', 'keyWeWant2', and 'keyWeWant3' (I'm essentially de-normalizing) to get something like so:
What I want the function to look like:
[
{
"keyWeWant" : "*-20",
"keyWeWant2" : 20,
"keyWeWant3" : 2.857142857142857
},
{
"keyWeWant" : "*-20",
"keyWeWant2" : 25,
"keyWeWant3" : 6.375
},
{
...
},
{
...
}
]
This is an example where there is only depth 3 but there could be arbitrary depth with some nested values being lists and some being arrays/list.
What I would like to do is write a function to take in the keys I want and where to find them, and then go get the keys and denormalize.
Something that looks like:
function_name(data_map, {
"keyWeWant" : ['agg', 'agg1'],
"keyWeWant2" : ['agg', 'agg1', 'some_nested_agg'],
"keyWeWant" : ['agg', 'agg1', 'some_nested_agg', 'some_nested_agg2']
})
Any ideas? I'm familiar with Java, Clojure, Java-script, and Python and am just looking for a way to solve this that's relatively simple.
Here is a JavaScript (ES6) function you could use:
function flatten(data, keys) {
var key = keys[0];
if (key in data)
keys = keys.slice(1);
var res = keys.length && Object.keys(data)
.map( key => data[key] )
.filter( val => Object(val) === val )
.reduce( (res, val) => res.concat(flatten(val, keys)), []);
return !(key in data) ? res
: (res || [{}]).map ( obj => Object.assign(obj, { [key]: data[key] }) );
}
// Sample data
var data = {
"agg": {
"agg1": [
{
"keyWeWant": "*-20.0",
"asdf": 0,
"asdf": 20,
"asdf": 14,
"some_nested_agg": [
{
"keyWeWant2": 20,
"to": 25,
"doc_count": 4,
"some_nested_agg2": {
"count": 7,
"min": 2,
"max": 5,
"keyWeWant3": 2.857142857142857,
"sum": 20
}
},
{
"keyWeWant2": 25,
"to": 30,
"doc_count": 10,
"some_nested_agg2": {
"count": 16,
"min": 2,
"max": 10,
"keyWeWant3": 6.375,
"sum": 102
}
}
]
},
]
}
};
// Flatten it by array of keys
var res = flatten(data, ['keyWeWant', 'keyWeWant2', 'keyWeWant3']);
// Output result
console.log(res);
Alternative using paths
As noted in comments, the above code does not use path information; it just looks in all arrays. This could be an issue if the keys being looked for also occur in paths that should be ignored.
The following alternative will use path information, which should be passed as an array of sub-arrays, where each sub-array first lists the path keys, and as last element the value key to be retained:
function flatten(data, [path, ...paths]) {
return path && (
Array.isArray(data)
? data.reduce( (res, item) => res.concat(flatten(item, arguments[1])), [] )
: path[0] in data && (
path.length > 1
? flatten(data[path[0]], [path.slice(1), ...paths])
: (flatten(data, paths) || [{}]).map (
item => Object.assign(item, { [path[0]]: data[path[0]] })
)
)
);
}
// Sample data
var data = {
"agg": {
"agg1": [
{
"keyWeWant": "*-20.0",
"asdf": 0,
"asdf": 20,
"asdf": 14,
"some_nested_agg": [
{
"keyWeWant2": 20,
"to": 25,
"doc_count": 4,
"some_nested_agg2": {
"count": 7,
"min": 2,
"max": 5,
"keyWeWant3": 2.857142857142857,
"sum": 20
}
},
{
"keyWeWant2": 25,
"to": 30,
"doc_count": 10,
"some_nested_agg2": {
"count": 16,
"min": 2,
"max": 10,
"keyWeWant3": 6.375,
"sum": 102
}
}
]
},
]
}
};
// Flatten it by array of keys
var res = flatten(data, [
['agg', 'agg1', 'keyWeWant'],
['some_nested_agg', 'keyWeWant2'],
['some_nested_agg2', 'keyWeWant3']]);
// Output result
console.log(res);
There is probably a better way to solve this particular problem (using some ElasticSearch library or something), but here's a solution in Clojure using your requested input and output data formats.
I placed this test data in a file called data.json:
{
"agg": {
"agg1": [
{
"keyWeWant": "*-20.0",
"asdf": 0,
"asdf": 20,
"asdf": 14,
"some_nested_agg": [
{
"keyWeWant2": 20,
"to": 25,
"doc_count": 4,
"some_nested_agg2": {
"count": 7,
"min": 2,
"max": 5,
"keyWeWant3": 2.857142857142857,
"sum": 20
}
},
{
"keyWeWant2": 25,
"to": 30,
"doc_count": 10,
"some_nested_agg2": {
"count": 16,
"min": 2,
"max": 10,
"keyWeWant3": 6.375,
"sum": 102
}
}]
}]}
}
Then Cheshire JSON library parses the data to a Clojure data structure:
(use '[cheshire.core :as cheshire])
(def my-data (-> "data.json" slurp cheshire/parse-string))
Next the paths to get are defined as follows:
(def my-data-map
{"keyWeWant" ["agg", "agg1"],
"keyWeWant2" ["agg", "agg1", "some_nested_agg"],
"keyWeWant3" ["agg", "agg1", "some_nested_agg", "some_nested_agg2"]})
It is your data_map above without ":", single quotes changed to double quotes and the last "keyWeWant" changed to "keyWeWant3".
find-nested below has the semantics of Clojure's get-in, only then it works on maps with vectors, and returns all values instead of one.
When find-nested is given a search vector it finds all values in a nested map where some values can consist of a vector with a list of maps. Every map in the vector is checked.
(defn find-nested
"Finds all values in a coll consisting of maps and vectors.
All values are returned in a tree structure:
i.e, in your problem it returns (20 25) if you call it with
(find-nested ['agg', 'agg1', 'some_nested_agg', 'keyWeWant2']
my-data).
Returns nil if not found."
[ks c]
(let [k (first ks)]
(cond (nil? k) c
(map? c) (find-nested (rest ks) (get c k))
(vector? c) (if-let [e (-> c first (get k))]
(if (string? e) e ; do not map over chars in str
(map (partial find-nested (rest ks)) e))
(find-nested ks (into [] (rest c)))) ; create vec again
:else nil)))
find-nested finds the values for a search path:
(find-nested ["agg", "agg1", "some_nested_agg", "keyWeWant2"] my-data)
; => (20 25)
If all the paths towards the "keyWeWant's are mapped over my-data these are the slices of a tree:
(*-20.0
(20 25)
(2.857142857142857 6.375))
The structure you ask for (all end results with paths getting there) can be obtained from this tree in function-name like this:
(defn function-name
"Transforms data d by finding (nested keys) via data-map m in d and
flattening the structure."
[d m]
(let [tree (map #(find-nested (conj (second %) (first %)) d) m)
leaves (last tree)
leaf-indices (range (count leaves))
results (for [index leaf-indices]
(map (fn [slice]
(if (string? slice)
slice
(loop [node (nth slice index)]
(if node
node
(recur (nth slice (dec index)))))))
tree))
results-with-paths (mapv #(zipmap (keys m) %) results)
json (cheshire/encode results-with-paths)]
json))
results uses a loop to step back if a leaf-index is larger than that particular slice. I think it will work out for deeper nested structures as well -if a next slice is always double the size of a previous slice or the same size it should work out -, but I have not tested it.
Calling (function-name my-data my-data-map) leads to a JSON string in your requested format:
[{
"keyWeWant": "-20.0",
"keyWeWant2": 20,
"keyWeWant3": 2.857142857142857 }
{
"keyWeWant": "-20.0",
"keyWeWant2" 25,
"keyWeWant3" 6.375 }]
/edit
I see you were looking for a relatively simple solution, that this is not. :-) maybe there is one without having it available in a library. I would be glad to find out how it can be simplified.