Navigating JSON tree to data from Reddit API - javascript

I will try and keep this short. I am a current student and have been attempting to retrieve the top 5 posts from the front page of Reddit and display the title and URL on an HTML page. It is probably something really simple, but I can't get it to work. I want to pass the data to my Handlebars template from a single variable. I keep receiving an unhandled promise warning. Here is my code.
let url = 'https://www.reddit.com/.json?limit=5';
let settings = { method: "Get"};
let redditData = ""
fetch(url, settings)
.then(res => res.json())
.then(data => {
redditData = [
{
title: data.children.data.title,
url: data.children.data.url_overriden_by_dest
}
];
});

The data object is structured differently than they way you've coded it. Here is how to extract the information you want from the first child:
let url = 'https://www.reddit.com/.json?limit=5';
let settings = { method: "Get"};
let redditData = ""
fetch(url, settings)
.then(res => res.json())
.then(data => {
redditData = [
{
title: data.data.children[0].data.title,
url: data.data.children[0].data.url_overriden_by_dest
}
];
});

You might want to check some documentation on how the function fetch works here.
Also, check how promises work here.
That being said, you have to access the object properties, only when the Promise has finished retrieving the data. One way is using the Promise.allSettled function.
Please see the following snippet working, with a slightly different way of organizing it:
const url = "https://www.reddit.com/.json?limit=5";
const promise = fetch(url).then(res => res.json());
function formatHandlebarsData(data) {
const childrenObj = data.value.data.children;
return childrenObj.map(element => ({
title: element.data.title,
url: element.data.url
}));
}
Promise.allSettled([promise]).then(([data]) => {
const handlebarsData = formatHandlebarsData(data);
// You can use the object now
console.log(handlebarsData);
});

Awesome. I was able to get it working with
let url = 'https://www.reddit.com/.json?limit=5';
let settings = { method: "Get"};
let redditData = ""
fetch(url, settings)
.then(res => res.json())
.then(data => {
redditData = [
{
title: data.data.children[0].data.title,
url: data.data.children[0].data.url_overriden_by_dest
}
];
});
Thanks!

Related

How to filter items in array by region?

So I'm getting incidents reports from Google Cloud API and displaying it. I'm trying to only display the ones that are happening on the US.
As you can see, the raw data from the API is like this:
I want to filter it, so I only get the ones that have us on it, how can I do it? This is the code now:
export const getGoogleStatus = async () => {
const response = await axios.get('https://status.cloud.google.com/incidents.json')
console.log('Raw Data: ', response.data)
const status = response.data.map((e) => {
return {
name: e.affected_products.map((e) => {
return e.title
}),
status: e.most_recent_update.status,
location: e.most_recent_update.affected_locations.map((e) => {
return e.title
}),
}
})
return status
}
You can use filter method in order to filter elements that doesn't match a specific criteria. Since you want only the items that are in the US, therefore you can check if it includes its code using include, which I believe it will be (us- as a substring.
export const getGoogleStatus = async () => {
const response = await axios.get('https://status.cloud.google.com/incidents.json')
console.log('Raw Data: ', response.data)
const status = response.data.map((e) => {
return {
name: e.affected_products.map((e) => {
return e.title
}),
status: e.most_recent_update.status,
location: e.most_recent_update.affected_locations.map((e) => {
return e.title
}).filter((e)=>e.includes('(us-')), //filters all elements that doesn't include '(us-' as a substring
}
})
return status
}
That seems like a static JSON file so you can use filter() to check if the location contains (us- as shown below:
location: e.most_recent_update.affected_locations.map((e) => {
return e.title
}).filter((r) => r.includes("(us-"))
If you want affected_products in US, then you can use the same filter on e.affected_products itself and check if affected_locations has any location from US.

How do I refactor this series of API calls to execute faster?

I have a series of API calls I need to make in order to render a grid of image tiles for selection by the user. Right now it takes 3-5 seconds for the page to load and I think it's because I've accidentally added some extra loops, but I'm struggling to discern where the wasted flops are. This is technically a question about NFT data, but the problem is algorithmic not crypto related.
The call sequence is:
Call "Wallet" API to get all assets associated with an address - API doc
On success, call "Asset Metadata" API to get further info about each asset API Doc
Loop step 2 until all assets have a metadata response
This is my code that works (unless there is no assets associated with a wallet), but is just very slow. I'm sure there is a better way to handle this, but I'm struggling to see how. Thanks for your time!
// API Request
var myHeaders = new Headers();
myHeaders.append("X-API-Key", CENTER_API_KEY); //API Key in constants file
var requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
const [nftData, updatenftData] = useState();
const [apiState, updateapiState] = useState("init");
const [renderNFT, updaterenderNFT] = useState([]);
useEffect(() => {
const getData = async () => {
let resp = await fetch(walletAPICall, requestOptions);
let json = await resp.json()
updatenftData(json.items);
updateapiState("walletSuccess");
}
const getRender = async () => {
let nftTemp = [];
for (let i=0;i<nftData.length;i++) {
let tempAddress = nftData[i].address;
let tempTokenId = nftData[i].tokenId;
let resp = await fetch(`https://api.center.dev/v1/ethereum-mainnet/${tempAddress}/${tempTokenId}`, requestOptions)
let json = await resp.json()
// console.log(json);
nftTemp.push(json);
}
updaterenderNFT(nftTemp);
updateapiState("NftDataSuccess");
}
if (apiState=="init") {
getData();
}
else if (apiState=="walletSuccess") {
getRender();
}
}, [requestOptions]);
getRender fetches data items sequentially.
You should do it in parallel using Promise.all or Promise.allSettled
Something like this...
function fetchItem(item) {
const res = await fetch(item.url);
return res.json();
}
await Promise.all[...data.map(fetchItem)]

Javascript Dictionary Value coming out as undefined

I have a normal dictionary in my react App that looks like this:
{firstName: "Jackson", lastName: "Metivier", post: "another another post post", postId: 4, time: "Fri, 24 Jul 2020 22:59:25 GMT", …}
and I'm trying to add a key and value pair to it which i've only been able to do doing this:
post['key'] = value
and I go console.log the dictionary after and it looks like this:
{firstName: "Jackson", lastName: "Metivier", picture: "blob:http://localhost:3000/30bc97e2-32f6-401f-8a80-a9cf9cec78c9", post: "Another post", postId: 3, time: "Fri, 24 Jul 2020 22:58:31 GMT",userId: 1}
so the key value pair is clearly added to the dictionary... but then when I try and call the value of 'picture' it comes out undefined.
also, when I try and call the picture value from my render statement, it is also undefined.
I have no idea what is happening, any insight into this matter would be greatly appreciated...
Here's my full code on the component I'm working on with the areas in question marked.
const Blog = () => {
const history = useHistory();
const [userData, setUserData] = useState({ 'data': '' })
const [userPosts, setUserPosts] = useState({ 'data': [] })
const [postPicture, setPostPicture] = useState('')
const [getPosts, setGetPosts] = useState(false)
const [newPostPicture,setNewPostPicture] = useState('')
const getPicture = (user_data) => {
user_data.data.map((post) => {
fetch(`/profile_picture/${post.userId}`, {
method: 'GET',
headers: {
'Origin': 'localhost:3000',
'Access-Control-Request-Method': 'POST',
'Acces-Control-Request-Headers': {
'Content-Type': 'JSON'
}
}
})
.then(res => res.blob().then(data =>
post['picture'] = URL.createObjectURL(data) // WHERE I'M ADDING THE VALUE
))
})
setUserPosts(user_data)
}
const getUserPosts = () => {
fetch('/blog', {
method: 'GET',
headers: {
'Origin': 'localhost:3000',
'Access-Control-Request-Method': 'POST',
'Acces-Control-Request-Headers': {
'Content-Type': 'JSON'
}
}
})
.then(res => res.json()
.then(data => {
getPicture(data)
}))
}
useEffect(() => {
getUserPosts()
}, []);
if (getPosts) {
getUserPosts()
console.log('new posts!')
setGetPosts(false)
}
userPosts.data.map((post) => {
console.log(post) // WHERE I'M SUCCESSFULLY CONSOLE LOGGING THE DICTIONARY AND ALSO GET UNDEFINED WHEN I CALL THE PICTURE KEY
})
return (
<div className='profile'>
<MakePost
setGetPosts = {setGetPosts}
/>
WHERE I ALSO GET UNDEFINED WHEN I CALL THE PICTURE KEY
</div>
)
}
export default Blog
Thank you!
I think the issue stems from your use of the Array.map function—more specifically, your use of the asynchronous fetch function therein. Since the promise returned by fetch is asynchronous, it's likely you're calling setUserPosts with the outdated user_data before any of the in-place modifications have a chance to occur upon Promise fulfillment.
To resolve this issue, you'll need to wait for all of your fetches to complete and the corresponding assignments to occur before you call setUserPosts. Luckily, this is fairly simple to achieve by passing an async function to map and using Promise.all to await completion.
Also, although it's not directly connected to your issue: the purpose of Array.map is to produce a new array based on the application of some function to the elements of an existing one, not to iterate through elements of an array for the purpose of modifying them in place (since post is being passed by reference). In general, in React, it's best to avoid the sort of in-place modification you're performing; instead, operations like map allow you to generate modified copies for later use. To make copies of objects (like user_data and post), either use the spread operator (e.g., let newObj = { ...obj, newProp: newVal}) or Object.assign (e.g., let newObj = Object.assign({}, obj); newObj.newProp = newVal). Doing this is more in line with React's principles and will probably avoid headaches down the road.
To see what I mean, here's a simplified version of what you're doing (using setTimeout to simulate your async fetch call and console.log in place of setUserData). I've written two approaches that illustrate how to use an asynchronous function with the await keyword in the map call to ensure that values are loaded before you do something with them (one using the spread operator, one using Object.assign):
// Dummy data
const user_data = {data: [
{firstName: "Jackson", lastName: "Metivier", post: "another another post post", postId: 4, time: "Fri, 24 Jul 2020 22:59:25 GMT"},
{firstName: "Jackson", lastName: "Metivier", post: "another post", postId: 5, time: "Fri, 25 Jul 2020 00:00:28 GMT"},
]}
console.log('Current approach (not working)')
user_data.data.map(post => {
asyncFunc().then(data => {
post['picture'] = 'my_picture'
})
})
doSomething(user_data)
// Using spread operator
Promise.all(user_data.data.map(async post =>
({...post, picture: await asyncFunc()})
)).then(user_data_data => {
console.log('Using spread operator')
doSomething({...user_data, data: user_data_data})
})
// Using Object.assign
const user_data_copy = Object.assign({}, user_data)
Promise.all(user_data.data.map(async post => {
const new_post = Object.assign({}, post)
new_post['picture'] = await asyncFunc()
return new_post
})).then(user_data_data => {
console.log('Using Object.assign')
user_data_copy.data = user_data_data
doSomething(user_data_copy)
})
// Dummy functions
function asyncFunc () {
return new Promise((res, rej) => {
setTimeout(() => { res('data') }, 1000);
})
}
function doSomething (data) {
console.log(data)
}
So, in the case of your code, your function would become something like the following (using the spread operator example):
const getPicture = (user_data) => {
Promise.all(user_data.data.map(async (post) => {
const res = await fetch(`/profile_picture/${post.userId}`, {
method: 'GET',
headers: {
'Origin': 'localhost:3000',
'Access-Control-Request-Method': 'POST',
'Acces-Control-Request-Headers': {
'Content-Type': 'JSON'
}
}
})
const data = await res.blob()
return {...post, picture: URL.createObjectURL(data)}
})).then((user_data_data) => {
setUserPosts({...user_data, data: user_data_data})
})
}

Fetch multiple URLs at the same time?

I'm looking for a way to fetch multiple URLs at the same time. As far as I know the API can only retrieve the data I want with a single product lookup so I need to fetch multiple products at once with the url structure "/products/productID/". Note, this is in VUEJS. This is what my code looks like so far:
In my productServices.js:
const productsService = {
getCategory(productID){
const url = `${config.apiRoot}/products/${productID}`
return fetch(url, {
method: 'GET',
headers: {
'content-type': 'application/json',
'Authorization': `Bearer ${authService.getToken()}`
},
})
}
}
In my view:
data() {
return {
featuredProduct: [13,14,15],
productName: [],
productImg: []
}
}
async mounted(){
const response = await productsService.getCategory(this.featuredProduct)
const resJSON = JSON.parse(response._bodyInit)
this.loading = false
this.productName = resJSON.name
this.productImg = resJSON.custom_attributes[0].value
}
So I need to hit all three featuredProduct IDs and store the data. I'm not really sure how to loop through multiple URLS. All of my other API calls have had all the data readily available using search params but for the specific data I need here ( product image ), it can only be seen by calling a single product.
Any help is much appreciated!
Like Ricardo suggested I'd use Promise.all. It takes in an array of promises and resolves the promise it returns, once all the passed ones have finished (it resolves the promises in the form of an array where the results have the same order as the requests).
Docs
Promise.all([
fetch('https://jsonplaceholder.typicode.com/todos/1').then(resp => resp.json()),
fetch('https://jsonplaceholder.typicode.com/todos/2').then(resp => resp.json()),
fetch('https://jsonplaceholder.typicode.com/todos/3').then(resp => resp.json()),
]).then(console.log)
Using map + Promise.all (tested)
Promise.all([1, 2, 3].map(id =>
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(resp => resp.json())
)).then(console.log);
if you have multiple products in an array which need to be fetched, you could just use:
Code not tested
Promise.all(productIds.map(productId =>
fetch(`https://url/products/${productId}`)
)).then(() => {/* DO STUFF */});
Little suggestion on storing your data:
If you store everything in one array, it makes to whole job way easier. So you could do
fetchFunction().then(results => this.products = results);
/*
this.products would then have a structure something like this:
Array of Obejcts: {
name: "I'm a name",
displayName: "Please display me",
price: 10.4
// And so on
}
*/
Because you have an array of products, I'd start by changing your state names:
data() {
return {
productIds: [13, 14, 15],
productNames: [],
productImages: [],
};
},
Then you can use Promise.all to fetch the products in parallel:
async mounted() {
const responses = await Promise.all(
this.productIds.map(id => productsService.getCategory(id))
);
responses.forEach((response, index) => {
const resJSON = JSON.parse(response._bodyInit);
this.productNames[index] = resJSON.name;
this.productImages[index] = resJSON.custom_attributes[0].value;
});
this.loading = false;
}
You could also consider refactoring getCategory do the parsing for you and return an object containing a name and an image - that way, mounted wouldn't have to know about the internal response structure.
Check the Promise.all method
Maybe you can create the calls that you need by iterating into your data and then request them in bulk.

How to modify an object after axios get request is resolved

I am setting the res.data of an object containerInfo in Vue afer making an axios get request, like so:
methods: {
searchContainers(containerSearch) {
this.containerQuery = JSON.parse(containerSearch.container_query)[0];
this.imageBranch = JSON.parse(containerSearch.container_query)[1];
this.containerInfo["image_branch"] = this.imageBranch
this.url = this.containerQuery.split('/');
axios.get(`http://localhost:8000/get?name=${this.url[0]}/${this.url[1]}/${this.url[2]}&ver=${this.url[3]}`)
.then(res => this.containerInfo = res.data)
.then(this.containerInfo = [...this.containerInfo, this.imageBranch]);
}
I want to add this.imageBranch to the containerInfo object after the data object is received/set in vue.
The issue is that the axios res, once received (takes a few secs), deletes the this.imageBranch key/value added. I know I should probably add it after the promise is resolved but I can't figure out how.
Can someone please help!
Instead of using two .then, use only one and execute all your code inside the arrow function, like this:
methods: {
searchContainers(containerSearch) {
this.containerQuery = JSON.parse(containerSearch.container_query)[0];
this.imageBranch = JSON.parse(containerSearch.container_query)[1];
this.containerInfo["image_branch"] = this.imageBranch
this.url = this.containerQuery.split('/');
axios.get(`http://localhost:8000/get?name=${this.url[0]}/${this.url[1]}/${this.url[2]}&ver=${this.url[3]}`)
.then(res => {
this.containerInfo = res.data;
this.containerInfo = [...this.containerInfo, this.imageBranch]
});
}
I recommend using await/async syntax which is clear and straightforward. So the codes should be like this
async searchContainers() {
const res = await axios.get(`http://localhost:8000/get?name=${this.url[0]}/${this.url[1]}/${this.url[2]}&ver=${this.url[3]}`);
this.containerInfo = res.data;
this.containerInfo = [...this.containerInfo, this.imageBranch];
}
I figured it out. Needed to set a key in object as such: this.containerInfo.image_branch = this.imageBranch

Categories

Resources