Update object array key with new value in javascript/React-native - javascript

I have the following Object array stored using AsyncStorage:
[
{
"groupId": 1,
"id": 1,
"name": "a",
},
{
"groupId": 2,
"id": 2,
"name": "ab",
},
{
"groupId": 3,
"id": 3,
"name": "abc",
},
{
"groupId": 2,
"id": 4,
"name": "abcd",
},
]
I'm retrieving the data to display it in a Flatlist but instead of displaying the groupId, I would like to display the groupName for each item.
I have another key with groups stored:
[
{
"groupName": "g1",
"id": 1,
},
{
"groupName": "g2",
"id": 2,
}
]
I made a function to retrieve the groupName based on groupId. This works fine:
const getGroupName = async (groupIdToFind) => {
const existingsGroups = (await AsyncStorage.getItem("groups")) || "[]";
const existingsGroupsParsed = JSON.parse(existingsGroups);
const groupDataFound = existingsGroupsParsed.find(
(results) => results.id.toString().indexOf(groupIdToFind) > -1
);
if (groupDataFound) {
return groupDataFound.groupName;
}
return false;
};
And I thought of modifying the data retrieved from the storage in my loadFlatlist function but this is where I'm struggling.
const loadFlatlist = async (storageKey) => {
try {
let itemsOnStorage = await AsyncStorage.getItem(storageKey);
itemsOnStorage = JSON.parse(itemsOnStorage);
if (itemsOnStorage !== null) {
let finalItemsOnStorage = "";
itemsOnStorage.map(async function (obj) {
console.log(obj.groupId); // Correctly returns 1, 2, 3 ...
let retrievedGroupName = await getGroupName(obj.groupId);
console.log(retrievedGroupName); // Correctly returns g1, g2 ...
finalItemsOnStorage = await update(itemsOnStorage, {
groupId: { $set: retrievedGroupName },
});
});
console.log(finalItemsOnStorage); // Returns same content as itemsOnStorage
setDataOnDevice(itemsOnStorage); // This will be loaded in my flatlist
}
} catch (err) {
alert(err);
}
};
The problem is in loadFlatlist, I did not manage to replace the groupId by the groupName. finalItemsOnStorage has the same content as itemsOnStorage.
Can you see what is wrong with my function? Is there a better way to do this?

Try like this
const loadFlatlist = async (storageKey) => {
try {
let itemsOnStorage = await AsyncStorage.getItem(storageKey);
itemsOnStorage = JSON.parse(itemsOnStorage);
const existingsGroups = (await AsyncStorage.getItem("groups")) || "[]";
const existingsGroupsParsed = JSON.parse(existingsGroups);
if (itemsOnStorage !== null) {
let finalItemsOnStorage = [];
itemsOnStorage.forEach(item => {
let index = existingsGroupsParsed.findIndex(obj => obj.id ===
item.groupId)
if(index != -1) {
finalItemsOnStorage.push({...item,
groupId:existingsGroupsParsed[index].groupName})
}
});
console.log(finalItemsOnStorage);
setDataOnDevice(finalItemsOnStorage);
}
} catch (err) {
alert(err);
}
};

Related

Having issues with deleting object from an array where match is found

I have the follow function that will delete object from array. It also returns the array tree without the items that was deleted. My issues is that it works when my objToFindBy is null deleting everything where {group: null} is found however it error with promise rejection if I set objToFindBy {group: 'some string'}
This code should delete all occurrences where the objToFindBy is a match, example {group: null} will find everywhere will the group is null and delete all object and then return the full tree without the objects that was deleted
findAndDeleteAll(tree, 'items', {group: null}) // work and delete all where match. then returns the tree without deleted objects
findAndDeleteAll(tree, 'items', {group: 'd575c91f-4765-4073-a948-5e305116610c'}) // promise rejection
const tree ={
"type": "app",
"info": "Custom Layout",
"items": [
{
"id": "d575c91f-4765-4073-a948-5e305116610c",
"title": "Fc",
"group": null
},
{
"id": "890d5a1e-3f03-42cd-a695-64a17b6b9bea",
"title": null,
"group": null
},
{
"id": "cbe00537-0bb8-4837-8019-de48cb04edd6",
"title": null,
"group": "d575c91f-4765-4073-a948-5e305116610c",
},
{
"id": "b8751c32-2121-4907-a229-95e3e49bcb39",
"title": null,
"group": "d575c91f-4765-4073-a948-5e305116610c"
}
],
"Children": []
}
var findAndDeleteAll = function findAndDeleteAll(tree, childrenKey, objToFindBy) {
var treeModified = false;
var findKeys = Object.keys(objToFindBy);
var findSuccess = false;
findKeys.forEach(function (key) {
(0, _lodash2.default)(tree[key], objToFindBy[key]) ? findSuccess = true : findSuccess = false;
});
if (findSuccess) {
Object.keys(tree).forEach(function (key) {
return delete tree[key];
});
return tree;
}
function innerFunc(tree, childrenKey, objToFindBy) {
if (tree[childrenKey]) {
var _loop = function _loop(index) {
var findKeys = Object.keys(objToFindBy);
var findSuccess = false;
findKeys.forEach(function (key) {
(0, _lodash2.default)(tree[childrenKey][index][key], objToFindBy[key]) ? findSuccess = true : findSuccess = false;
});
if (findSuccess) {
tree[childrenKey].splice(index, 1);
treeModified = true;
}
if (tree[childrenKey][index].hasOwnProperty(childrenKey)) {
innerFunc(tree[childrenKey][index], childrenKey, objToFindBy);
}
};
for (var index = tree[childrenKey].length - 1; index >= 0; index--) {
_loop(index);
}
}
}
innerFunc(tree, childrenKey, objToFindBy);
return treeModified ? tree : false;
};
how about shorter solution?
const findAndDeleteAll = (tree, childrenKey, nestedKey, nestedValue) => {
return{...tree, [childrenKey]: tree[childrenKey].filter((row) => {
return row[nestedKey] !== nestedValue;
})}
}
const a = findAndDeleteAll(tree, 'items', 'group', null) // work and delete all where match. then returns the tree without deleted objects
const b = findAndDeleteAll(tree, 'items', 'group', 'd575c91f-4765-4073-a948-5e305116610c') // promise rejection
console.warn(a);
console.warn(b);
Your function would be so much simper, reusable, better, if you send not the redundant tree - but instead deleteFrom(tree.items, "group", null);. think about it.
const deleteFrom = (arr, pr, val) => arr.filter(ob => ob[pr] !== val);
const tree = {
type: "app",
info: "Custom Layout",
items: [
{ id: "10c", title: "Fc", group: null },
{ id: "bea", title: null, group: null },
{ id: "dd6", title: null, group: "10c" },
{ id: "b39", title: null, group: "10c" },
],
Children: []
};
const items = deleteFrom(tree.items, "group", null);
console.log(items); // Only the filtered items array
const newTree = {...tree, items};
console.log(newTree); // Your brand new tree!

Convert keys from key value pair to capital case using javaScript

I am trying to convert the keys in JSON to the capital case using Javascript. I am successful to some extent. However, it is not creating the arrays in the correct way. It is inserting numbers before every object inside an array.
Input:
{
"id": "123",
"retweetCheck": {
"result": "OK",
"checks": [
{
"cId": "123"
},
{
"cId": "456"
}
]
},
"tweetCheck": {
"result": "OK",
"cId": "345",
"check": "Fail"
}
}
Code to convert the keys to capital case:
var responseContent = context.getVariable("response.content") || "";
responseContent = JSON.parse(responseContent) || "";
transformedCapitalizedObj = keysToCapitalCase(responseContent);
var finalResponseObj = {
Data: transformedCapitalizedObj
};
context.setVariable("response.content", JSON.stringify(finalResponseObj));
The function
function objEntries(obj) {
const keys = Object.keys(obj);
const keyValuePairs = keys.map(key => {
const value = obj[key];
return [key, value];
});
return keyValuePairs;
}
function keysToCapitalCase(objToProcess) {
if (!objToProcess || typeof objToProcess !== "object") return null;
var finalObj = {};
objToProcess = objEntries(objToProcess);
objToProcess.forEach(function (entry) {
var key = entry[0];
var value = entry[1];
key = key.charAt(0).toUpperCase() + key.slice(1);
if (typeof value == "object" || (value instanceof Array)) {
value = keysToCapitalCase(value);
}
finalObj[key] = value;
});
return finalObj;
}
The output I am getting currently is:
{
"Data":{
"RetweetCheck":{
"Checks":{
"0":{
"CId":"123"
},
"1":{
"CId":"456"
}
},
"Result":"OK"
},
"Id":"123",
"TweetCheck":{
"CId":"345",
"Check":"Fail",
"Result":"OK"
}
}
}
But ideally, the output should look like this:
{
"Data": {
"Id": "123",
"RetweetCheck": {
"Result": "OK",
"Checks": [
{
"CId": "123"
},
{
"CId": "456"
}
]
},
"TweetCheck": {
"Result": "OK",
"CId": "345",
"Check": "Fail"
}
}
}
It is basically inserting a serial number before each object inside an array instead of []. How this can be rectified. Any help will really do wonders.
When you call your function keysToCapitalCase(), first check if you have an array (with ES6, you can do this using Array.isArray()), and if you do, you can map the objects / inner arrays within that array to the result of recursively calling your keysToCapitalize function. Otherwise, if you get a standard object that isn't an array, you can perform your standard object mapping:
const obj = { "id": "123", "retweetCheck": { "result": "OK", "checks": [{ "cId": "123" }, { "cId": "456" } ] }, "tweetCheck": { "result": "OK", "cId": "345", "check": "Fail" } };
function keysToCapitalCase(objToProcess) {
if (!objToProcess || typeof objToProcess !== "object") return null;
if(Array.isArray(objToProcess)) {
return objToProcess.map(obj => keysToCapitalCase(obj));
}
var finalObj = {};
objToProcess = Object.entries(objToProcess); // if you can support it, `Object.entries()` does what `objEntries` does
objToProcess.forEach(function(entry) {
var key = entry[0];
var value = entry[1];
key = key.charAt(0).toUpperCase() + key.slice(1);
if (typeof value == "object") {
value = keysToCapitalCase(value);
}
finalObj[key] = value;
});
return finalObj;
}
var finalResponseObj = {Data: keysToCapitalCase(obj)};
console.log(finalResponseObj);
I would probably write the above method in a similar way, but instead using some inbuilt functions to make it a little more concise, such as .map() and Object.fromEntries():
const obj = { "id": "123", "retweetCheck": { "result": "OK", "checks": [{ "cId": "123" }, { "cId": "456" } ] }, "tweetCheck": { "result": "OK", "cId": "345", "check": "Fail" } };
const cap = str => str.charAt(0).toUpperCase() + str.slice(1);
const keysToCapitalCase = (objToProcess) => {
if (Object(objToProcess) !== objToProcess) return null;
return Array.isArray(objToProcess)
? objToProcess.map(obj => keysToCapitalCase(obj))
: Object.fromEntries(Object.entries(objToProcess).map(([key, val]) => [
cap(key), Object(val) === val ? keysToCapitalCase(val) : val
]));
}
const finalResponseObj = {Data: keysToCapitalCase(obj)};
console.log(finalResponseObj);
As indicated by the {} brackets, instead of the wanted [] brackets you are creating an empty object and not an Array.
To create an Array just change your var finalObj = {}; to var finalObj = [];
with an Array finalObj[key] = value will no longer work, you will now have to use finalObj.push(value)

filter object by two nested values

I'm facing a problem with filter method. On my page there's an input to search matches by team names. Filter value is being stored to React state. Matches object looks like this:
[
{
"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": [
{
[...]
}
]
},
"teamRed": {
"id": 37,
"name": "nameForTeamRed",
"playerList": [
{
[...]
}
]
},
"localDate": "2020-01-01",
"localTime": "00:00:00",
"referee": null,
"commentator1": null,
"commentator2": null,
"streamer": null,
"stage": {
"id": 2,
"name": "GROUPSTAGE"
},
"onLive": true,
"finished": false
},
]
I tried tons of methods to filter matches by team name, for example:
let criteria = {
teamBlue: {
name: this.state.filter
},
teamRed: {
name: this.state.filter
}
};
let filteredMatches = this.state.matches.filter(function(item) {
for (let key in criteria) {
if (item[key] === undefined || item[key] !== criteria[key])
return false;
}
return true;
});
console.log(filteredMatches);
but none of them worked.
Is there any way to filter these matches so when I type "blue" into my input, it will show all matches where team name contains "blue"?
Thanks in advance!
Try updating the condition to:
if (!item[key] || item[key].name !== criteria[key].name)
let filteredMatches = this.state.matches.filter(function(item) {
let flag = true;
for (let key in criteria) {
// update this to
if (!item[key] || item[key].name !== criteria[key].name)
flag = false;
}
return flag;
});
The name property is missing :
if (key in item && item[key].name !== criteria[key].name)
You're comparing objects with === which will return false. You either need to use a deep comparison method from a library, or implement it yourself like below:
const matches = [ {"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": []
},
"teamRed": {
"id": 37,
"name": "nameForTeamRed",
"playerList": []
},
}, {"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": []
},
"teamRed": {
"id": 37,
"name": "nameForTeamRead",
"playerList": []
},
}]
const criteria = {
teamBlue: {
name: 'nameForTeamBlue',
},
teamRed: {
name: 'nameForTeamRed',
}
}
const filteredMatches = matches.filter((item) => {
const allCriteriaMatched = Object.entries(criteria)
.every(([key, value]) => {
const matched = Object.entries(value).every(([criteriaKey, criteriaValue]) => {
const itemValue = item[key][criteriaKey]
const matched = itemValue == criteriaValue
if (!matched) console.log('Item %s does not matched criteria %s. Item\'s value is %s, but criteria value is %s', item[key]['id'], criteriaKey, itemValue, criteriaValue, criteriaValue)
return matched
})
if (!matched) return false
return true
}, {})
return allCriteriaMatched
})
console.log(filteredMatches);
Basically, you just need to go 1 level deeper :D if your criteria can have multiple nested objects, then there's no point doing it manually. You can try to map criteria to run against matches so that you don't use === on objects, but only primitives.

Recursively Add Elements To Array Based on Length

I'm trying to create a recursive function that will return a list of strings representing every attribute for a given schema. It must merge this with the attributes for a given document, to include elements for each time an array element occurs in the document.
For example. If you run the following code, I expect the result to include friends.1.addresses.1.country. However it does not. I think this is due to the fact that it's not recursively including all the array element possibilities. Since only 0 is set for the other array possibilities in the parentKey & postKey variables.
Any ideas how to fix this?
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip'
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
}
]
};
console.log(main()); // Should print `friends.1.addresses.1.country` as one of the elements in the array but does not.
function main() {
const result = schemaAttributes.reduce((accumulator, currentValue) => {
accumulator.push(currentValue);
const attributeParts = currentValue.split(".");
attributeParts.forEach((a, index) => {
const isLastPartNumber = !isNaN(parseInt(a));
if (isLastPartNumber) {
const parentKey = attributeParts.slice(0, index).join(".");
const postKey = attributeParts.slice(index + 1).join(".");
const numberOfItems = get(myDocument, parentKey).length;
for (let i = 1; i < numberOfItems; i++) {
accumulator.push([parentKey, i, postKey].filter((a) => Boolean(a)).join("."));
}
}
});
return accumulator;
}, []);
return [...new Set(result)];
}
function get(object, key) {
const keyParts = key.split(".");
let returnValue = object;
keyParts.forEach((part) => {
if (returnValue) {
returnValue = returnValue[part];
}
});
return returnValue;
}
Expected Result (order does not matter):
[
"id",
"friends",
"friends.0",
"friends.1",
"friends.0.name",
"friends.1.name",
"friends.0.addresses",
"friends.1.addresses",
"friends.0.addresses.0",
"friends.1.addresses.0",
"friends.1.addresses.1",
"friends.0.addresses.0.country",
"friends.1.addresses.0.country",
"friends.1.addresses.1.country",
"friends.0.addresses.0.zip",
"friends.1.addresses.0.zip",
"friends.1.addresses.1.zip"
]
Below traversing all paths matching your schemaAttribute, assuming a zero in your schemaAttribute is a wildcard for the position in array.
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip'
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
}
]
};
const out = new Set()
schemaAttributes.forEach(attr => {
out.add(attr)
traverse(myDocument, attr.split('.'), 0, [], path => out.add(path.join('.')))
})
function traverse (node, attrPath, idxAttrPath, outPath, cb) {
if (idxAttrPath === attrPath.length) {
return cb(outPath)
}
if (!node) { // can not explore further
return
}
const attr = attrPath[idxAttrPath]
if (attr === '0') {
if (!Array.isArray(node)) { // can not explore further
return
}
node.forEach((n, i) => {
outPath.push(i)
traverse(node[i], attrPath, idxAttrPath + 1, outPath, cb)
outPath.pop()
})
} else {
outPath.push(attr)
traverse(node[attr], attrPath, idxAttrPath + 1, outPath, cb)
outPath.pop()
}
}
console.log('out', [...out].sort((a, b) => a.localeCompare(b)))
An alternative (in the spirit more efficient) would be to consider a trie so we don't explore every schemaAttribute from the start. A 'nice' property being that the fields are printed in order and we don't have to sort as done in the first approach (although it does not matter to you)
Note that the traverse function is almost identical
Note2: Notice the cb(outPath) done for every traversal, not only for leaves.
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip'
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
}
]
};
function f2 (schemaAttributes, doc) {
// build a tree out of schema attributes
const root = {}
schemaAttributes.forEach(sa => {
node = root
sa.split('.').forEach(attr => {
node[attr] = node[attr] || {}
node = node[attr]
})
})
// explore the tree
function traverse (node, treeNode, outPath, cb) {
cb(outPath)
if (Object.keys(treeNode).length === 0) { // a leaf
return // cb(outPath)
}
if (!node) {
return
}
Object.keys(treeNode).forEach(attr => {
if (attr === '0') {
if (!Array.isArray(node)) { // can not explore further
return
}
node.forEach((n, i) => {
outPath.push(i)
traverse(node[i], treeNode[attr], outPath, cb)
outPath.pop()
})
} else {
outPath.push(attr)
traverse(node[attr], treeNode[attr], outPath, cb)
outPath.pop()
}
})
}
const out = []
traverse(doc, root, [], p => out.push(p.join('.')))
return out.slice(1) // discard the empty string
}
console.log(f2(schemaAttributes, myDocument))
Regarding the presence of friends.2.addresses.0.zip the underlying idea is that the path to the leaf should be present even if path in the document is at some point undefined.
So the adaptation is to fake the path on the document so we can continue traversing it up until the tree leaf is reached
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip',
'bob.moran.everywhere' // for properties as well
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
},
{
"name": "Pumba",
"addresses": [] // empty addresses! should show ..friends.2.adresses.0....
}
]
};
function f3 (schemaAttributes, doc) {
// build a tree out of schema attributes
const root = {}
schemaAttributes.forEach(sa => {
node = root
sa.split('.').forEach(attr => {
node[attr] = node[attr] || {}
node = node[attr]
})
})
// explore the tree
function traverse (node, treeNode, outPath, cb, virtualPath) {
cb(outPath)
if (Object.keys(treeNode).length === 0) { // a leaf
return //cb(outPath)
}
Object.keys(treeNode).forEach(attr => {
if (attr === '0') {
if (!node || node.length == 0) {
node = [{}] // fake the path for arrays
}
node.forEach((n, i) => {
outPath.push(i)
traverse(node[i], treeNode[attr], outPath, cb)
outPath.pop()
})
} else {
if (!node) { // fake the path for properties
node = {}
}
outPath.push(attr)
traverse(node[attr], treeNode[attr], outPath, cb)
outPath.pop()
}
})
}
const out = []
traverse(doc, root, [], p => out.push(p.join('.')))
return out.slice(1)
}
console.log(f3(schemaAttributes, myDocument))
You could use try out this piece of code:
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip'
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
}
]
};
main(); // Should print `friends.1.addresses.1.country` as one of the elements in the array but does not.
function extractKeysRecursively(obj) {
const attributes = Object.keys(obj)
return attributes
flatMap(key =>
typeof obj[key] === 'object'
? [isNaN(key) && key, ...extractKeysRecursively(obj[key], ( ( parentKey && parentKey + '.' ) || '' ) + key )]
: ( ( parentKey && parentKey + '.' ) || '' ) + key
)
.filter(key => !!key)
}
function main() {
console.log(schemaAttributes.concat(extractKeysRecursively(myDocument)))
}
Lemme know you need some extra help (:

How to map an array of objects and fetch network data for each in JS?

I am making apps in React Native. I have to fetch an array of data of categories from a URL and then for each category I have to fetch assets from their respective URLs.
My data is stored in the following format:
From the mainURL:
{
"id": 1,
"name": "Home",
"type": "",
"url": "",
"subCategories": [
{
"id": 92,
"name": "Documentary",
"type": "JTV_LEAF",
"url": "yyy",
}
]
}
From each category URL,
[
{
"id": "1",
"title": "Inception",
"type": "vod"
}
]
How do I fetch data for each category using map and reduce and axios?
This is what I have written so far. I am getting undefined at the end.
export const fetchNavigationFeed = (navUrl, subId) => {
return dispatch => {
const url = navUrl
.replace("__JTV__SUBSCRIBER__ID__", subId);
dispatch({ type:FETCH_NAVIGATION_FEED });
return axios.get(url)
.then(response => {
let categories = [];
for (var i = 0; i < response.data.subCategories.length; i++) {
var cat = response.data.subCategories[i];
var category = new Category(cat);
categories.push(category);
}
console.log(categories);
let promises = [];
categories.map(category => {
let request = axios.get(category.url)
.then(assetsJson => {
let assets = [];
for (var i = 0; i < assetsJson.data.length; i++) {
var ass = assetsJson.data[i];
var asset = new Asset(ass);
assets.push(asset);
}
category.assets = assets;
});
promises.push(request);
});
axios.all(promises)
.then(axios.spread(...args) => {
console.log(args);
});
return categories;
})
.then(categories => {
// console.log(categories);
dispatch({ type:FETCH_NAVIGATION_FEED_SUCCESS, payload:categories });
});
}
}
Here is a working example and jest test:
code.js
import axios from 'axios';
export const FETCH_NAVIGATION_FEED = 'FETCH_NAVIGATION_FEED';
export const FETCH_NAVIGATION_FEED_SUCCESS = 'FETCH_NAVIGATION_FEED_SUCCESS';
class Category {
constructor(json) {
this.id = json.id;
this.name = json.name;
this.url = json.url;
}
}
class Asset {
constructor(json) {
this.id = json.id;
this.title = json.title;
}
}
export const fetchNavigationFeed = (navUrl, subId) => {
return async (dispatch) => {
dispatch({ type: FETCH_NAVIGATION_FEED });
const url = navUrl
.replace('__JTV__SUBSCRIBER__ID__', subId);
const response = await axios.get(url);
const categories = [];
const promises = [];
response.data.subCategories.forEach((subCategory) => {
const category = new Category(subCategory);
categories.push(category);
const promise = axios.get(category.url).then((subResponse) => {
category.assets = [];
subResponse.data.forEach((asset) => {
category.assets.push(new Asset(asset));
});
});
promises.push(promise);
});
// wait for all the promises simultaneously
await Promise.all(promises);
dispatch({ type: FETCH_NAVIGATION_FEED_SUCCESS, payload: categories });
}
}
code.test.js
import axios from 'axios';
import {
fetchNavigationFeed,
FETCH_NAVIGATION_FEED,
FETCH_NAVIGATION_FEED_SUCCESS
} from './code';
const getMock = jest.spyOn(axios, 'get');
getMock.mockImplementation((url) => {
switch (url) {
case 'mainUrl-mySubId':
return Promise.resolve({
data: {
"id": 1,
"name": "home",
"subCategories": [
{
"id": 2,
"name": "sub1",
"url": "sub1Url",
},
{
"id": 3,
"name": "sub2",
"url": "sub2Url",
}
]
}
});
case 'sub1Url':
return Promise.resolve({
data: [
{
"id": 4,
"title": "asset1"
},
{
"id": 5,
"title": "asset2"
}
]
});
case 'sub2Url':
return Promise.resolve({
data: [
{
"id": 6,
"title": "asset3"
},
{
"id": 7,
"title": "asset4"
}
]
});
}
});
test('getData', async () => {
const asyncDispatch = fetchNavigationFeed('mainUrl-__JTV__SUBSCRIBER__ID__', 'mySubId');
const dispatch = jest.fn();
await asyncDispatch(dispatch);
expect(dispatch).toHaveBeenCalledTimes(2);
const firstCallArgs = dispatch.mock.calls[0];
expect(firstCallArgs).toEqual([{
type: FETCH_NAVIGATION_FEED
}]);
const secondCallArgs = dispatch.mock.calls[1];
expect(secondCallArgs).toEqual([{
type: FETCH_NAVIGATION_FEED_SUCCESS,
payload: [
{
id: 2,
name: 'sub1',
url: 'sub1Url',
assets: [
{
"id": 4,
"title": "asset1"
},
{
"id": 5,
"title": "asset2"
}
]
},
{
id: 3,
name: 'sub2',
url: 'sub2Url',
assets: [
{
"id": 6,
"title": "asset3"
},
{
"id": 7,
"title": "asset4"
}
]
}
]
}]);
});
Note: you can use axios.all() but according to this thread it uses Promise.all() under the hood anyway.
You could do something like this:
fetch(url)
.then((response) => response.json())
.then(async (data) => {
const categories = data.subCategories
.map(category => new Category(category.id, category.name, category.type, category.url))
for(category of categories) {
let assets = await fetch(category.url).then(rsp => rsp.json())
assets = assets.map(asset => Asset(assets.id, assets.title, assets.type, ass.thumbnail));
category.assets = assets;
}
return categories
}).then(categoriesWithAssets => {/*done*/})

Categories

Resources