Use setState to change the value of a grandchild object - css

Everything I have tried from what I can find doesn't seem to be working. I'm really curious how to access and edit grandchild objects located in the state with react. If anyone could tell me what I'm doing wrong, it would be very helpful.
https://codesandbox.io/s/0mo32q85pp
Take a look at the following code...
App.js
lines: 41-58
getHomeworld = URL => {
fetch(URL)
.then(res => {
return res.json();
})
.then(homeWorldObject => {
console.log(homeWorldObject);
// this.setState({ <- Why isn't this working????
// ...this.state.starwarsChars,
// ...this.state.nextPage,
// ...this.state.prevPage,
// ...this.state.starwarsChars.homeworld = homeWorldObject
// });
})
.catch(err => {
throw new Error(err);
});
};
lines: 86-89
<CharacterList
characters={this.state.starwarsChars}
getHomeworld={this.getHomeworld}
/>
CharacterList.js
lines: 8-12
<Character
key={character.name}
characterDetails={character}
getHomeworld={props.getHomeworld}
/>
Character.js
lines: 18-29
{Object.keys(props.characterDetails).includes("homeworld") ? (
<div className="character-homeworld">
<Homeworld
homeworld={props.getHomeworld(props.characterDetails.homeworld)}
/>
</div>
) : (
<div className="character-homeworld">
<h4>Homeworld</h4>
<p>None</p>
</div>
)}
Homeworld.js
lines: 7-10
<div className="homeworld-details">
<p>Name: {props.name}</p>
<p>Rotation Period: {props.rotation_period}</p>
</div>
Expected Output:
If you look on the sandbox webpage, the "Name" and "Rotation Period" (Under "Homeworld") should display the values from here: https://swapi.co/api/planets/1/
Is there anyone who can help me figure this out?
EDIT:
I got really close making these changes (using my local machine, the code on the sandbox is still the original)...
App.js
let temp = {...this.state.starwarsChars} // use spread operator to clone it, so you don't mutate state on next line;
for (let character in temp) {
if (temp[character].homeworld === URL) {
temp[character].homeworld = homeWorldObject;
}
}
// console.log(temp);
this.setState({
starwarsChars: temp
});
Character.js
const Character = props => {
props.getHomeworld(props.characterDetails.homeworld);
console.log(props.characterDetails); // returns object{homeworld: {object}}
console.log(props.characterDetails.homeworld); // returns url
and...
<div className="character-homeworld">
<Homeworld
homeworld={props.characterDetails.homeworld}/>
</div>
However, the issue now is if I do console.log(props.characterDetails.homeworld);, it logs homeworld: url
and...
if I do console.log(props.characterDetails);, it logs the property of the character object as homeworld: {object}
...
What I want is the 2nd one, and I'm not sure why it's not consistent.
Update
For some strange reason, codesandbox is console logging both urls, and when I run with yarn start, it logs the url for one, and the object for another. Because of this... I am adding the github link here -> https://github.com/jamespagedev/Sprint-Challenge-React-Wars (so the error can properly be reproduced)
Edit 2:
I changed the sandbox code to the following so we are now only worrying about the code in 1 file -> https://codesandbox.io/s/0mo32q85pp
Here is the issue I am now seeing, and I'm not sure how to solve it...
getHomeworld = URL => {
let home;
fetch(URL)
.then(res => res.json())
.then(homeWorldObject => {
home = homeWorldObject;
console.log(home); // home is an object
});
console.log(home); // why is home undefined?
return home;
};
I've tried doing return homeWorldObject, but the main function just returns undefined. After doing the console logging, that was what I found, and I'm not sure why that is happening...

Related

Adding a new item when using useSWRInfinite pushes other items out of the list

I am building a comment system where new replies are added to the start (top) of the list. The pagination is cursor-based.
At the moment, I use mutate to add the newly created comment as its own page to the front of the list.:
const {
data: commentsPages,
: commentsPagesSize,
: setCommentsPagesSize,
//TODO: Not true on successive page load. But isValidating refreshes on refetches
isLoading: commentsLoading,
error: commentsLoadingError,
mutate: mutateCommentPages,
} = useSWRInfinite(
getPageKey,
([blogPostId, lastCommentId]) => BlogApi.getCommentsForBlogPost(blogPostId, lastCommentId));
<CreateCommentBox
blogPostId={blogPostId}
title="Write a comment"
onCommentCreated={(newComment) => {
const updatedPages = commentsPages?.map(page => {
const updatedPage: GetCommentsResponse = { comments: [newComment, ...page.comments], paginationEnd: page.paginationEnd };
return updatedPage;
})
mutateCommentPages(updatedPages, { revalidate: false });
}}
/>
The problem is, SWR immediately starts revalidating the list and pushes the comment at the bottom out of the data set. This behavior is kind of awkward.
Is my only choice do disable automatic revalidation completely? How would you handle this?

How to have Cypress go through every page on site to see if there are any console errors and if so, make it known to the user running the test

I want Cypress to go through every page to see on a website to see if there are any console errors and if so, make it known to the user running the test. (I'm thinking it would be useful for CSP checking to see if the site is throwing a console error because of a domain not being whitelisted.)
This package cypress-fail-on-console-error
may make it easier
test
import failOnConsoleError from 'cypress-fail-on-console-error';
failOnConsoleError();
const pages = [ "/page1", "/page2" ]
pages.forEach(page => {
it(`verifies the page ${page}`, () => {
cy.visit(page)
})
})
There's some interesting stuff on Cypress and CSP here
Testing Content-Security-Policy using Cypress ... Almost
You can use a combination of Cypress functionality to achieve this. You could store the list of links in an array of strings, use Cypress Lodash to iterate through each string as a separate test, and use the onBeforeLoad callback within cy.visit() to spy on console.error.
describe('Tests', () => {
// Define links
const links = ['/1', '/2', '/3'...]
// Iterate through the links array using Cypress Lodash
Cypress._.times(links.length, (index) => {
it('validates site loads with no errors', () => {
cy.visit(links[index], {
// set the `onBeforeLoad` callback to save errors as 'error'
onBeforeLoad(win) {
cy.stub(win.console, 'error').as('error');
}
});
// Validate error was not called
cy.get('#error').should('not.have.been.called');
});
});
});
A good deal of this answer was taken from this answer.
If you'd like to be specific about the errors that fail, try catching uncaught:exception
Cypress.on('uncaught:exception', (err) => {
if (err.message.includes('Content Security Policy')) {
return true
} else {
return false // only fail on the above message
}
})
describe('Testing Content Security Policy', () => {
const pages = [ "/page1", "/page2" ]
pages.forEach(page => {
it(`visiting page ${page}`, () => {
cy.visit(page)
})
})
})

Next.js getInitialProps not rendering on the index.js page

I really can't figure out what is wrong with this code on Next.js.
index.js :
import { getUsers } from "../utils/users";
import React from "react";
Home.getInitialProps = async (ctx) => {
let elements = [];
getUsers().then((res) => {
res.map((el) => {
elements.push(el.name);
});
console.log(elements);
});
return { elements: elements };
};
function Home({ elements }) {
return (
<div>
{elements.map((el, i) => {
<p key={i}>{el}</p>;
})}
</div>
);
}
export default Home;
This doesn't render anything on my main page but still console logs the right data on server side (inside the vscode console). I really can't figure out what's going on, I followed precisely the article on the next.js site.
The getUsers function is an async function that returns an array of objects (with name,surname props), in this case in the .then I'm grabbing the names and pushing them into an array that correctly logs out to the console.
How can I make this data that I get render on the page?? Surely something to do with SSR.
The problem is using async function. Try as following.
...
elements = await getUsers();
...
In your code, component is rendered before response is finished. So the data is not rendered. Suggest using "async...await...". Infact "async" and "await" are like a couple of one.

WP API and JS : Cannot read property 'wp:featuredmedia' of undefined

I write a component to display a list WP posts on a page build with nuxt.js and I just can not display the featured image.
The Vue Component
<template>
<div class="references__grid">
<div class="references__item" v-for="item in references">
<h3><nuxt-link :to="slugToUrl(item.slug)"><h2 class="is-title">{{ item.title }}</h2></nuxt-link></h3>
<div v-html="item.excerpt"></div>
<div>{{ item.image }}</div>
<strong class="more"><nuxt-link :to="slugToUrl(item.slug)">Lire la suite</nuxt-link></strong>
</div>
</div>
</template>
The request
getReferences() {
return new Promise((resolve, reject) => {
request.defaults.baseURL = this.baseUrl;
request.get(`posts?categories=46&per_page=6&_embedded`).then(response => {
const data = [...response.data];
if (response.status === 200 && response.data.length > 0) {
const filtered = {
total: response.headers["x-wp-total"],
totalPages: response.headers["x-wp-totalpages"],
data: data.map(item => ({
id: item.id,
title: item.title.rendered,
content: item.content.rendered,
excerpt: item.excerpt.rendered,
slug: item.slug,
image: item._embedded["wp:featuredmedia"][0].media_details.sizes.full.source_url
}))
};
resolve(filtered);
} else {
reject(response);
}
});
});
},
The WP Api seems ok: https://www.agencedebord.com/wp-json/wp/v2/posts?categories=46&per_page=61&_embed
The error message:
ERROR
TypeError: Cannot read property '0' of undefined
server-bundle.js:1525 filtered.data.data.map.item
server-bundle.js:1525:56
Array.map
server-bundle.js:1519 >__WEBPACK_IMPORTED_MODULE_1_axios___default.a.get.then .response
server-bundle.js:1519:24
next_tick.js:160 process._tickCallback
internal/process/next_tick.js:160:7
So why item._embedded is undefined?
There is no problem for item.id or item.slug... any clarification is appreciated.
Finally, I did not use "_embed" but I add a new endpoint following this answer : Get Image URL instead of Attachment Id in Rest API
I think that the request url is not correct, you should use _embed not _embedded
So it will be request.get(posts?categories=46&per_page=6&_embed)
Otherwise, the _embedded part will be missing in the json response.
The problem is sometimes item._embedded['wp:featuredmedia'] is returning undefined, you can confirm you get that by console.log(item._embedded['wp:featuredmedia']), the solution for that by wrapping it in if condition, if it not undefined to proceed if(item._embedded['wp:featuredmedia']){ return let imageUrl = item._embedded["wp:featuredmedia"][0].media_details.sizes.full.source_url
} else { return null}
I had one post that was giving this error. I had the if/else setup properly but it was still erroring. I went to the problem post and resaved the featured image and also changed to the ajax to load .full.source_url instead of .medium.source_url and that fixed the error.
I imported some photos from another wordpress. some of them had not featured images yet they somehow acting like they have. (has_post_thumbnail() was acting same way and returns true for that posts)
My solution is checking if they 'really' have thumbnail:
if(post.featured_media == 0 || post._embedded['wp:featuredmedia'][0].media_details == undefined){
imageSource = null;
}
else{
imageSource = post._embedded['wp:featuredmedia'][0].media_details.sizes.full.source_url;
}

angular2 google map infowindow opens so many times and it starts lagging

I'm using angular-google-map. In the project, I create hundreds of markers and all of them share one info window. Below is the code in the template:
<div *ngFor="let property of _properties">
<agm-marker [latitude]="property.Lat"
[longitude]="property.Lng"
[iconUrl]="_markerIconUrl"
(mouseOver)="infoWindow.open();"
(mouseOut)="infoWindow.close();">
</agm-marker>
</div>
...
<agm-info-window #infoWindow>
</agm-info-window>
At the begining it works well. But as I mouseover many markers, it starts slow down. Here is the screenshot of performance and memory:
It seems like garbage collection slows down the page and I really don't know how to fix it..
I tried these:
I thought it might be mouseover/mouseout emitter slows it down. But if I replace infoWindow.open() to other function, it works fine.
Then I checked the angular-google-map source code, and found the infoWindow.open():
open(infoWindow: AgmInfoWindow): Promise<void> {
return this._infoWindows.get(infoWindow).then((w) => {
if (infoWindow.hostMarker != null) {
return this._markerManager.getNativeMarker(infoWindow.hostMarker).then((marker) => {
return this._mapsWrapper.getNativeMap().then((map) => w.open(map, marker));
});
}
return this._mapsWrapper.getNativeMap().then((map) => w.open(map));
});
}
I thought it might be the Promise slows it down, but when I comment out w.open(map) and w.open(map, marker) it works fine.
So I think the problem might be because it called w.open(map) so many times? I found that open function is from Google Map API infoWindow. I tried clear the conent and set infoWindow conent='' and close it everytime, but still can't fix it.
I finally fixed it. The issue caused by too many change detections. And I found the solution here: https://github.com/angular/angular/issues/10883#issuecomment-240423378
What I did is called the infowindow.open() outside of Angular to avoid change detections:
map.component.html
<div *ngFor="let property of _properties">
<agm-marker [latitude]="property.Lat"
[longitude]="property.Lng"
[iconUrl]="_markerIconUrl"
(mouseOver)="_infoWindowOpen($event, infoWindow);"
(mouseOut)="_infoWindowClose($event, infoWindow);">
</agm-marker>
</div>
...
<agm-info-window #infoWindow>
</agm-info-window>
map.component.ts
import { Component, OnInit, NgZone } from '#angular/core';
...
constructor(private _zone: NgZone) {...}
...
private _infoWindowOpen(mouseEvent: any, infoWindow: any) {
infoWindow.hostMarker = mouseEvent.marker;
this._zone.runOutsideAngular(() => {
infoWindow.open();
});
}
...
private _infoWindowClose(mouseEvent: any, infoWindow: any) {
this._zone.runOutsideAngular(() => {
infoWindow.close();
});
}

Resources