I am learning ReactJS by creating ReactJS version of my blog. While I was testing google page speed, I am noticed the "prioritize visible content", fine, while articles are loading I have added a placeholder article with title of a loading message and lorem ipsum description and a sample default header image. I have run the page speed again, still the same issue with one difference. The placeholder article is rendered, but the title, image and description are not rendered. It is a static text, cannot imagine why it´s not showing. I have tried to simulate low internet connection and reload the page and yes, the text inside component is rendered with some delay, even its just static text.
The code is available here: https://github.com/erikkubica/reactjs-simple-blog-test See the src/modules/article/ArticleListItemPlaceholder.js and ArticleList.js
I have also noticed that the logo is also missing at this moment. Cannot imagine why, if on non-react website it´s not. Also the styles are loaded, navigation component is rendered...
See in action http://reactjs.netlime.eu/
Screenshot about the problem:
Thank you, I will be happy to get any explanation, good practices,... to learn more.
UPDATE:
Problem resolved.
Problem was that while custom fonts was not loaded browser made the text invisible. I have added fontFamily: "Arial" into inline style of elements which fixed the issue. Thanks to Mr Lister.
Also big thanks to John Ruddell for giving some best practice.
Your issue is you are setting state async but trying to render things in a semi sync manner.
async fetchData() {
//const response = await fetch("/static/articles.json");
let data = null; //await AsyncStorage.getItem('#MySuperStore:articles_storage_' + this.state.category);
if (data === null) {
const response = await fetch("https://www.netlime.eu/wp-json/get-posts/v2/all/");
data = await response.json();
await AsyncStorage.setItem('#MySuperStore:articles_storage_' + this.state.category, JSON.stringify(data));
}
try {
data = JSON.parse(data);
} catch (e) {
}
// your issue is here
______________________________________________
this.setState({articles: data});
this.createArticleList();
______________________________________________
// change to this.
this.setState({articles: data}, () => {
this.createArticleList();
});
}
because setState is async you are creating the articleList state item before the articles are set on state. so the UI never gets updated
Edit
To be honest you shouldn't use a secondary state variable to hold an array of the articles to be rendered. create them on the fly.
export default class ArticleList extends React.Component {
constructor(props) {
super(props);
this.state = {
articles: [],
category: this.props.category ? this.props.category : 0
};
}
async fetchData() {
let data = null; //await AsyncStorage.getItem('#MySuperStore:articles_storage_' + this.state.category);
if (data === null) {
const response = await fetch("https://www.netlime.eu/wp-json/get-posts/v2/all/");
data = await response.json();
await AsyncStorage.setItem('#MySuperStore:articles_storage_' + this.state.category, JSON.stringify(data));
}
try {
data = JSON.parse(data);
} catch (e) {
}
this.setState({articles: data});
}
componentDidMount() {
this.fetchData();
}
render() {
const articles = this.state.articles.map((article) => {
return (
<ArticleListItem
key={article.ID}
article={article}
/>
);
});
return (
<div>
{ articles.length ? articles : <ArticleListItemPlaceholder/>}
</div>
);
}
}
Related
I created a react app with many nested routes.
One of my nested route is using a backend api that returns a complete HTML content.
And I need to display that exact content with same HTML and styling in my UI.
I'm able to successfully achieve it by manipulating the DOM according to axios response using createElement and appendChild inside useEffect method.
But, the whole philosophy behind using react is, to NOT modify the DOM and let react work on it by simly updating the states or props.
My question is:
Is there a cleaner way to use api returned HTML in a react app?
Here is sample relevant code:
Item.js
...
...
useEffect( ()=>{
const fetchAndUpdateItemContent = async () => {
try {
const response = await axios.get(url);
var contentDiv = document.createElement("div");
contentDiv.innerHTML = response.data.markup;
document.getElementById(‘itemContent’)
.appendChild(contentDiv);
} catch (err) {
.....
console.error(err);
......
}
}
};
fetchAndUpdateItemContent();
},[itemId])
return (
<div id=‘itemContent'/>
);
}
What did NOT work
Ideally I should be able to have a state as itemContent in Item.js and be able to update it based upon server response like this. But when I do something like below, whole HTML markup is displayed instead of just the displayable content.
const [itemContent, setItemContent] = useState(‘Loading ...');
...
useEffect( ()=>{
const fetchAndUpdateItemContent = async () => {
try {
const response = await axios.get(url);
setItemContent(response.data.markup)
} catch (err) {
.....
console.error(err);
......
}
}
};
fetchAndUpdateItemContent();
},[itemId])
return (
<div id=‘itemContent'>
{itemContent}
</div>
You're actually trying to convert an HTML string to a JSX. You can assign it into react component props called dangerouslySetInnerHTML
Eg:
const Item = () => {
const yourHtmlStringResponse = '<h1>Heading 1</h1><h2>Heading 2</h2>'
return <div dangerouslySetInnerHTML={{__html: yourHtmlStringResponse}}></div>
}
You can try it here dangerouslySetInnerHTML-Codesandbox
I believe you can use dangerouslySetInnerHTML
I want to scrape specific JSON from specific request link "http://website.com/request/api" on page.
I have to scroll to the bottom of the page to get all the articles (already coded). At each scroll, I would like to get the JSON corresponding to the articles just displayed.
So there are 2 problems:
The fact that the same URL query "http://website.com/request/api" is also used to returns other JSON which is not useful for me (other elements of the page).
The fact of having several JSONs to collect and assemble
For problem 1, I thought of adding a condition to my code to get only the JSON beginning with a precise text "Data : object"?
For the problem 2, I should be able to write in a file or the buffer the different JSON selected by assembling them.
Do you know how I could do it?
page.on('response', async(response) => { const request => response.request();
if (request.url().includes('/api/graphql/')){
const text = await response.text();
fs.writeFile('./tmp/response.json', JSON.stringify((text)));
console.log(text);
}
})
i have resolve the problem.
listener = page.on('response', async response => {
const isXhr = ['xhr','fetch','json'].includes(response.request().resourceType())
try {
if (isXhr){
if (response.url().includes('/api/graphql/')) {
const resp = await response.buffer();
if (resp.includes('Data : object')) {
fs.writeFileSync('./tmp/response.json', resp, { flag: 'a+' })
}
}
}}
catch(e){}
})
I have this code:
with a url of: block.hacash.org
I am using Puppeteer on Firefox, headless, hosted on VPS.
function hacdName() {
const extractedElements = document.querySelectorAll('td.t2.gray.dts');
const items = [];
for (let element of extractedElements) {
items.push(element.innerText);
}
return items;
}
let items = await page.evaluate(hacdName);
console.log(items)
However the output will only return the first 10 (ten) td.t2.gray.dts and will not return the rest.
Example:
[
'00000000097f5ff183be...',
'0000000019bcba6a3eae...',
'0000000014895c593f29...',
'00000000077088d50229...',
'0000000001a143b70894...',
'0000000013c9089db9cb...',
'0000000006b7a707923c...',
'000000001bfa7c9c68ec...',
'00000000030593fa3c73...',
'000000001af596b772c5...',
'000000000394daca889b...'
]
How can I scrape all td.t2.gray.dts to block 0 or until I consumed all show more button.
Another question :
Why does puppeteer on td.tr.gray.dts return (e.g. 000000000394daca889b...) not the whole (000000000394daca889b9f472f6ede90a9e835bc516d7f76f37718e5d827e6b2)?
In order to get all elements using the Puppeteer you need to have them on page first, to do this you can create a function that will click on the "Show More" button until it's visible
async function loadAllResources(page) {
let moreButton;
do {
moreButton = await page.$('#blocks .pmore .button');
if (moreButton) {
await moreButton.click();
await page.waitFor(1000); // some wait until the rows will be loaded
}
} while(moreButton)
}
and just call it before scraping all data await loadAllResources(page);
As for the question with no full text, it's because you're getting the innerHtml. To obtain the whole text you need to get the title from the link
function hacdName() {
const extractedElements = document.querySelectorAll('td.t2.gray.dts a');
const items = [];
for (let element of extractedElements) {
items.push(element.title);
}
return items;
}
Also, I will suggest you pay attention to the API and monitor the network tab when you're loading this resource, for example, fetching the data from the URL http://block.hacash.org/api/block/list?last=170246&limit=1000 will be much easier and faster and can solve the same task faster.
So I am using the DraftJS package with React along with the mentions plugin. When a post is created, I store the raw JS in my PostreSQL JSONField:
convertToRaw(postEditorState.getCurrentContent())
When I edit the post, I set the editor state as follows:
let newEditorState = EditorState.createWithContent(convertFromRaw(post.richtext_content));
setEditorState(newEditorState);
The text gets set correctly, but none of the mentions are highlighted AND I can't add new mentions. Does anyone know how to fix this?
I am using the mention plugin: https://www.draft-js-plugins.com/plugin/mention
to save data
function saveContent() {
const content = editorState.getCurrentContent();
const rawObject = convertToRaw(content);
const draftRaw = JSON.stringify(rawObject); //<- save this to database
}
and retrieval:
setEditorState(()=> EditorState.push(
editorState,
convertFromRaw(JSON.parse(draftRaw)),
"remove-range"
););
it should preserve your data as saved.
the example provided (which works ok) is for inserting a new block with mention, saving the entityMap as well.
mentionData is jus a simple object {id:.., name:.., link:... , avatar:...}
One more thing:
initialize only once:
in other words do not recreate the state.
const [editorState, setEditorState] = useState(() => EditorState.createEmpty() );
und then populate something like:
useEffect(() => {
try {
if (theDraftRaw) {
let mtyState = EditorState.push(
editorState,
convertFromRaw(JSON.parse(theDraftRaw)),
"remove-range"
);
setEditorState(mtyState);
} else editorClear();
} catch (e) {
console.log(e);
// or some fallback to other field like text
}
}, [theDraftRaw]);
const editorClear = () => {
if (!editorState.getCurrentContent().hasText()) return;
let _editorState = EditorState.push(
editorState,
ContentState.createFromText("")
);
setEditorState(_editorState);
};
I´m new on Angular and try to parse Adobe IDML (XML) files for showing them up in a browser. Let me explain that:
There is the following XML-Structure ("->" means gives info about next line):
IDML (designmap.xml) ->
Spread (spread_xy.xml) ->
Page ->
TextFrames ->
Story
Rectangle ->
Image
I have written a backend that translates these files in JSON-Arrays and I load these JSON-Arrays via HTTP. So far so good.
The following is my corresponding IDML-Service in the Frontend:
export class IdmlService {
apiUrl = environment.backendApi + 'idml/';
constructor(private http: HttpClient) { }
renderSpread(currentFolder, currentSpreadId): Observable<HttpResponse<any[]>> {
return this.http.get<MenuItem[]>(
this.apiUrl + currentFolder + '?spreadId=' + currentSpreadId, { observe: 'response' }).pipe(
retry(3)
);
}
}
I call "renderSpread"-method in my publicationComponent like this:
renderPublication(currentFolder: String, currentSpreadId: String): void {
this.idmlService.renderSpread(currentFolder, currentSpreadId)
.subscribe(resp => {
this.currentSpreadJson = resp.body;
// console.log(this.currentSpreadJson);
});
}
and bind the "currentSpreadJson" to the child component called spreadComponent in the template:
<div class="publication">
<app-spread [currentSpreadJson]="currentSpreadJson"></app-spread>
</div>
In the spreadComponent I construct my currentSpread and bind the necessary rest of JSON via "currentElementsJson" to the next child elementsComponent like this:
#Input() currentSpreadJson: any;
currentSpread: Spread;
currentElementsJson: any;
ngOnInit() {
setTimeout(() => { // THE MAIN PROBLEM
this.render();
}, 3000);
}
render(): void {
this.currentSpread = {
id: this.currentSpreadJson.idPkg_Spread.Spread['Self'],
[...some other vars...]
};
[...doing some stuff...]
this.currentElementsJson = this.currentSpreadJson.idPkg_Spread.Spread;
}
Here´s the template:
<div id="{{currentSpread.id}}" class="spread box_shadow" [ngStyle]="{'width': currentSpread.width + 'px', 'height': currentSpread.height + 'px'}">
<app-page [currentSpreadJson]="currentSpreadJson"></app-page>
<app-element [currentElementsJson]="currentElementsJson"></app-element> // <-- here
</div>
So here my question: Such an IDML could become very huge. This strategy goes deeper in the XML-Tree and the problem is that I always need to do a timeout onInit. I tried some other lifecycles like ngAfterViewInit and so on, but I think the upper strategy is not the thing I need for my project. I heared about "async await"-functions, but I don´t really know how to implement that in this context and I´m not sure if this is the solution. It would be nice if somebody could give me a hint. Thank you.
Regards, Dominik
This worked for me now. I changed this:
ngOnInit() {
setTimeout(() => { // THE MAIN PROBLEM
this.render();
}, 3000);
}
to that:
ngAfterContentChecked() {
this.render();
}
Now the nested components wait until the Content is available.
Regards