Javascript Dictionary Value coming out as undefined - javascript

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})
})
}

Related

how to use useState inside inner function scope in react.js hooks

I am completely new to react.js.
I am fetching async data from my server that uses express.js. After I get that data I want to set my house parameters when I open that page for the first time.
const Houses = () => {
const [house, setHouse] = useState({});
...
useEffect(() => {
const fetchData = () => {
fetch('http://localhost:9000/houses')
.then(res => res.text())
.then(res => {
if (res) {
let houseData = JSON.parse(res);
console.log(houseData); // server res prints correctly - prints: { name: 'sweet house', address: 'fukuoka hakata' }
setHouse({...house, houseData});
console.log(house); // doesnt change after sethouse - prints : {}
}
});
}
fetchData();
}, []);
...
}
Im getting the data from my server without any problem.
The problem is that the house parameters dont get updated. I know its a different scope, what I need to know is how I do it in this case. Thanks
You cannot access the house state immediately after setting it, setHouse may be batched.
See more of State: https://reactjs.org/docs/state-and-lifecycle.html
You are trying to do
let houseData = JSON.parse(res);
// houseData = { name: 'sweet house', address: 'fukuoka hakata' }
setHouse({ ...house, houseData: houseData });
instead of setHouse(houseData). Since houseData is an Object, you can directly set it.
Replace
setHouse({...house, houseData})
with
setHouse(houseData)

Navigating JSON tree to data from Reddit API

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!

Have I mismanaged a promise? or mismanaged a data type?

I would love to hear some ideas to solve this problem. Here's a javascript function. Make note of the comments, I'll refer to those specific lines later.
async function getGroupId(name) {
const response = await fetch(
environmentData.localFunctionsUrl + functionPath,
{
body: JSON.stringify({
command: `GetInterests`,
path: `/interest-categories/`
}),
method: `POST`
}
)
const groupId = await response.json().then((json) => {
return json.map((obj) => {
if(obj.name.substring(0, `YYYY-MM-DD`.length) === name.substring(0, `YYYY-MM-DD`.length)) {
return obj.id //LET'S CALL THIS LINE "yellow"
}
})[0] //AND THIS LINE, LET'S CALL IT "yellow-ish"
})
const retObj = { [groupId]: true } //LET'S CALL THIS LINE "orange"
return retObj
}
That function is called in my code like this:
async function registerSubscriber(data) {
const emailToHash = data.paymentIntent.metadata.contact_email
const response = await fetch(
environmentData.localFunctionsUrl + functionPath,
{
body: JSON.stringify({
command: `UpsertMember`,
path: `/members/`+ crypto.createHash(`md5`).update(emailToHash.toLowerCase()).digest(`hex`),
mailchimp_body: {
email_address: emailToHash,
merge_fields: {
COURSEDATE: data.paymentIntent.metadata.courseData_attr_name.substring(
0,
data.paymentIntent.metadata.courseData_attr_name.indexOf(`:`)
),
FNAME: data.paymentIntent.metadata.contact_firstname,
LNAME: data.paymentIntent.metadata.contact_surname
},
// HERE: NEXT LINE
interests: await getGroupId(data.paymentIntent.metadata.courseData_attr_name)
}
}),
method: `PATCH`
}
)
const json = await response.json()
return json
}
So, THIS ALL WORKS in my local environment. This code properly interacts with a lambda function I've created to interact with MailChimp's API.
Here's the problem: in my production environment (Netlify, fyi), line "yellow" is reached (a match is made in the if statement), so presumably there's an element available in line "yellow-ish".
But certainly groupId in line "orange" is undefined.
Theory 1:
My working theory is it's failing due to race-condition. I mean, perhaps line "orange" is being returned before line "yellow" produces the relevant data. But I'm (almost) certain I've structured the promises correctly -- the async and await keywords
Theory 2:
My other working theory is that I've mismanaged the data types. Is there a reason that {[groupId]:true} may work in a windows environment but not in a linux env (both env's running the same version of node).
Related to this theory: the value returned in line "yellow" sometimes begins with a number, sometimes with a letter. This shouldn't matter, but I mention it because line "orange" will sometimes like like this:
{ '123abc': true }
And sometimes without quotes like this:
{ abc123: true }
I presume this difference in syntax is a known behaviour in Javascript -- is it just how object keys are handled?
Something that caught my attention was the map inside the following method:
const groupId = await response.json().then((json) => {
return json.map((obj) => {
if(obj.name.substring(0, `YYYY-MM-DD`.length) === name.substring(0, `YYYY-MM-DD`.length)) {
return obj.id //LET'S CALL THIS LINE "yellow"
}
})[0] //AND THIS LINE, LET'S CALL IT "yellow-ish"
})
While this could provide you with the value you're looking for, it's generally bad practice, because a map would loop through the whole array and it'll replace each value of the array that doesn't match your conditions with undefined, so if the value you're looking for is not at index 0 then you'll most certainly get undefined when you run the method. The method you need is find which will get a value/object out of the array and make it directly accessible.
Try the following code:
async function getGroupId(name) {
const response = await fetch(
environmentData.localFunctionsUrl + functionPath,
{
body: JSON.stringify({
command: `GetInterests`,
path: `/interest-categories/`
}),
method: `POST`
}
);
const responseJson = await response.json();
const group = responseJson.find(obj => obj.name.substring(0, `YYYY-MM-DD`.length) === name.substring(0, `YYYY-MM-DD`.length));
return {
[group.id]: true
};
}
async function registerSubscriber(data) {
const {
paymentIntent: {
metadata: {
contact_email,
courseData_attr_name,
contact_firstname,
contact_surname
}
}
} = data;
const interests = await getGroupId(courseData_attr_name);
const response = await fetch(
environmentData.localFunctionsUrl + functionPath,
{
body: JSON.stringify({
command: 'UpsertMember',
path: `/members/${crypto.createHash(`md5`).update(contact_email.toLowerCase()).digest(`hex`)}`,
mailchimp_body: {
email_address: contact_email,
merge_fields: {
COURSEDATE: courseData_attr_name.substring(0, courseData_attr_name.indexOf(`:`)),
FNAME: contact_firstname,
LNAME: contact_surname
},
interests
}
}),
method: 'PATCH'
}
);
const json = await response.json();
return json
}
Unfortunately, I can't say for certain if your handling of the JSON response is wrong without any sample (dummy) data, but usually a JSON object is not an array, so you can't handle it with a map.
Side note, you should try to stick to either async/await or then/catch.
Your should work well, If you get groupId undefined then json.map((obj) => {...} returns empty array.
Try to debug or add console.log:
const groupId = await response.json().then((json) => {
console.log('server response', json);
return json.map((obj) => {
if(obj.name.substring(0, `YYYY-MM-DD`.length) === name.substring(0, `YYYY-MM-DD`.length)) {
return obj.id //LET'S CALL THIS LINE "yellow"
}
})[0] //AND THIS LINE, LET'S CALL IT "yellow-ish"
})
console.log('groupId', groupId);
const retObj = { [groupId]: true } //LET'S CALL THIS LINE "orange"
return retObj
PS: As mentioned in other comments you should stick to await or then and not try to mix them

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 do we mock fetch in Redux Async Actions?

In the Writing Tests section of Redux,http://rackt.org/redux/docs/recipes/WritingTests.html, how does the store.dispatch(actions.fetchTodos()) not invoke the fetch method, if store.dispatch is literally calling actions.fetchTodos?
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] })
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done)
store.dispatch(actions.fetchTodos())
})
Everytime I try to run something similar to this, I keep getting a fetch is not defined. Even if I use nock. So I have to spy my action to not get the call to fetch.
Here is my unit test:
it('should request a password reset, and then return success on 200', (done) => {
nock('http://localhost:8080/')
.post('/password-reset-requests')
.reply(200);
var email = "test#email.com";
const expectedActions=[
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
];
const store = mockStore({}, expectedActions, done);
store.dispatch(Actions.addPasswordResetRequest());
here is the action:
export default function addPasswordResetRequest(email){
return dispatch => {
dispatch(requestAddPasswordResetRequest(email));
return addPasswordResetRequestAPI(email)
.then(() =>{
dispatch(requestAddPasswordResetRequestSuccess());
})
.catch((error) => {
dispatch(requestAddPasswordResetRequestFailure(error));
});
};
}
and the function that calls fetch:
export const addPasswordResetRequestAPI = (email) => {
return fetch(
SETTINGS.API_ROOT + '/password-reset-requests',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
code: NC_SETTINGS.GROUP.code
})
}
)
.then(handleResponse);
};
I'm not sure if the way I am doing is sufficient for the purpose of just testing actions, but then I do run into the problem of store.dispatch only returning the first element of expectedActions, and it doesn't equal the list I supply in the spied addPasswordResetRequest. Below includes the spied action.
it('should request a password reset, and then return success on 200', (done) => {
nock('http://localhost:8080/')
.post('/password-reset-requests')
.reply(200);
Actions.addPasswordResetRequest = spy(() => {
return ([
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
]
);
});
var email = "test#email.com";
const expectedActions=[
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
];
const store = mockStore({}, expectedActions, done);
store.dispatch(Actions.addPasswordResetRequest());
The action "addPasswordResetRequest" isn't an action per-say.
it's a composite action with 3 sub-actions
startAction =requestAddPasswordResetRequest,
successAction =requestAddPasswordResetRequestSuccess
failAction =requestAddPasswordResetRequestFailure
I generally tests each action separately. so i would have something like
describe("requestAddPasswordResetRequest", () => {
it("shows the loading spinner or whatever", ...);
it("does some other state change maybe", ...);
});
describe("requestAddPasswordResetRequestSuccess", () => {
it("hides the loading spinner or whatever", ...);
it("changes the password state or something", ...);
});
describe("requestAddPasswordResetRequestFailure", () => {
it("hides the loading spinner or whatever", ...);
it("shows the error somehow", ...);
});
//each test would be something like
it("changes the password state or something", ()=>{
const action = requestAddPasswordResetRequestSuccess({
some : "payload from the server"
});
const newState = myReducer({ state : "somestate" }, action);
expect(newState).to.be.eql("expected result for that action");
});
Notice how in the test i don't need the store or any async logic. thats the beauty of redux (and functional stuff in general), it's simple :)
after this i would have a separate test for the whole thing and make sure that the correct simple actions get dispatched by the composite action, in which i would mock everything (including store and the "fetch" thing, since i just want to test that the actions get fired in the correct order).
if the actions are dispatched in the correct order and each action work separably i would be pretty confident that the thing works as expected.
Hope this helps.

Categories

Resources