I'm fairly new to React and I'm trying to lazy load a markdown file stored on the server.
I've tried setting up an async arrow function that fetches the file and runs it through marked.
I found this demo here https://codesandbox.io/s/7zx3jlrry1 which I've tried following but haven't figured out how to follow it.
class Markdown extends React.Component {
constructor(props) {
super(props)
this.state = {
// some state
}
}
render() {
let markdown = React.lazy(async () => {
// fetch the file
const response = await fetch(path)
if (!response.ok) {
return (<p>Response was not ok</p>)
} else {
// if successful, return markdown
let blob = await response.blob()
return marked(blob)
}
})
return (
<React.Suspense fallback={<div class="markdown">Loading</div>}>
<div class="markdown" id={this.props.id} dangerouslySetInnerHTML={{__html: markdown}} />
</React.Suspense>
)
}
}
When I try debugging it the arrow function isn't actually executed, and the inner HTML of the div is "[object Object]".
Any help as to how I can achieve this would be greatly appreciated.
You get [object Object] in your html because dangerouslySetInnerHTML expects a function returning the object {__html: '<div>some html string</div>'}. Other than that you are not using recommended way of fetching data through network requests. Please read on to get more details on how to perform your task.
React Suspense is used to lazy load Components not for fetching data as the react docs state:
In the future we plan to let Suspense handle more scenarios such as data fetching.
React.Suspense lets you specify the loading indicator in case some components in the tree below it are not yet ready to render. Today, lazy loading components is the only use case supported by :
You don't require lazyload in this scenario. Use react-lifecycle methods in order to do things like fetching data at the correct time. What you require here is react-lifecylce method componentDidMount. Also you can use component state to manipulate what is rendered and what is not. e.g you can show error occured or loading by setting variables.
class Markdown extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true,
error: false,
html: ""
}
}
componentDidMount = () => {
this.fetchMarkdown()
}
fetchMarkdown = async () => {
const response = await fetch(path)
if (!response.ok) {
// if failed set loading to false and error to true
this.setState({ loading: false, error: true })
} else {
// If successful set the state html = markdown from response
let blob = await response.text()
this.setState({ loading: false, html: blob })
}
}
getMarkup = () => {
return { __html: this.state.html }
}
render() {
if (this.state.error) {
return <div>Error loading markup...</div>
}
else if (this.state.loading){
return <div>Loading...</div>
}
else {
return <div class="markdown" id={this.props.id} dangerouslySetInnerHTML={this.getMarkup()} />
}
}
}
Related
In my React.js app, I am fetching a quote from API and storing it in the state object, When trying to access the properties of the object which is stored in state. Returning null.
Code:
import React, { Component } from "react";
export default class Quotes extends Component {
constructor(props) {
super(props);
this.state = {
quote: null,
};
}
componentDidMount() {
fetch("https://quote-garden.herokuapp.com/api/v2/quotes/random")
.then((response) => response.json())
.then((quote) => this.setState({ quote: quote.quote }))
.catch((error) => console.log(error));
}
render() {
console.log(this.state.quote.quoteText); //This is returning null
console.log(this.state.quote); //This is working fine.
return (
<div>
<p>// Author of Quote</p>
</div>
);
}
}
I am new to React.js, I spining my head for this problem around 2 hourse. I didn't get any solution on web
output showing quote object
When I try to console.log(this.state.quote) it prints out this output, well it is fine.
and when I try console.log(this.state.quote.quoteText) It will return can not read property
output showing can not find property
You must note the you are trying to fetch data asynchronously and also you fetch data in componentDidMount which is run after render, so there will be an initial rendering cycle wherein you will not have the data available to you and this.state.quote will be null
The best way to handle such scenarios is to maintain a loading state and do all processing only after the data is available
import React, { Component } from "react";
export default class Quotes extends Component {
constructor(props) {
super(props);
this.state = {
quote: null,
isLoading: true,
};
}
componentDidMount() {
fetch("https://quote-garden.herokuapp.com/api/v2/quotes/random")
.then((response) => response.json())
.then((quote) => this.setState({ quote: quote.quote, isLoading: false }))
.catch((error) => console.log(error));
}
render() {
if(this.state.isLoading) {
return <div>Loading...</div>
}
console.log(this.state.quote.quoteText); //This is returning proper value
console.log(this.state.quote);
return (
<div>
<p>// Author of Quote</p>
</div>
);
}
}
When you are accessing the quoteText without checking whether the key in the object is present or not it throws an error -
render(){
if(this.state.quote && this.state.quote.hasOwnProperty('quoteText')){
console.log(this.state.quote.quoteText);
}
}
I am fairly new to JavaScript/React/NextJS. I have written a React app and I am now trying to convert it so that all of it renders on the server.
I went through the tutorials on https://nextjs.org/learn/basics/getting-started and then i tried to apply that knowledge to my app.
I have the following requirements in my app
I have a master detail scenario. The main page renders a list of articles, clicking on one of those articles takes the user to articles details.
I want to have clean urls, such as "/article/123" and not "article?id=123".
I am using a 3rd party library to get the data from the server, therefore I have to use promises to get the data
I need to use Class Components not functional components (for other reasons can not move to functional components).
I want all the rendering to occur on the server not the client
I am unclear how in "getInitialProps" i would call my async method in my 3rd party library and return the data. Nothing i try is working and googling hasn't helped.
My project structure is
- components
|- header.js
|- footer.js
|- layout.js
- pages
|- index.js
|- article
|- [id].js
- scripts
|- utils.js
index.js just renders 2 links for 2 articles
export default function Index() {
return (
<div>
<Link href="/article/[id]" as={`/article/1`}>
<a>Article 1</a>
</Link>
<Link href="/article/[id]" as={`/article/2`}>
<a>Article 2</a>
</Link>
</div>
);
}
utils.js has a util method to call the 3rd party library method
export function getDataFromThirdPartyLib(articleId) {
return thirdPartyLib.getMyData({
"articleId" : articleId,
}).then(function (item) {
return item;
});
}
article/[id].js has
function getData(articleId){
console.log("getData")
getDataFromThirdPartyLib(articleId)
.then((item) => {
console.log("got data")
return {
item: item
}
});
}
class ArticleDetails extends React.Component {
static async getInitialProps(ctx) {
const data = await getData(articleId);
// I also tried the following
// const data = getData(articleId);
return{
data : data
}
}
render() {
console.log("IN RENDER")
console.log("this.props.data") // THIS IS UNDEFINED
// my HTML
}
}
Problem :
console logging shows that the data is always undefined in render(). I can see my logging from getting the data and data is obtained, but these render after my render logging, so render is not refreshed once the data is obtained.
Different attempt 1
I tried having "getData" inside my component definition (as apposed to outside it) but this always fails as it says the "getData" is not a function.
class ArticleDetails extends React.Component {
static async getInitialProps(ctx) {
const data = await getData(articleId); // FAILS AS SAYS getData is not a function
return{
data : data
}
}
getData(articleId){
console.log("getData")
getDataFromThirdPartyLib(articleId)
.then((item) => {
return {
item: item
}
});
}
render() {
console.log("IN RENDER")
console.log(this.props.data) // THIS IS UNDEFINED
// my HTML
}
}
Different attempt 2
I tried having the logic in "getData" directly in "getInitialProps" but that also doesn't work as it says getInitialProps must return an object.
class ArticleDetails extends React.Component {
static async getInitialProps(ctx) {
getDataFromThirdPartyLib(articleId)
.then((item) => {
return {
data: data
}
});
}
render() {
console.log("IN RENDER")
console.log(this.props.data) // THIS IS UNDEFINED
// my HTML
}
}
Working version - but not SSR
The only way i can get this to work is to write it like a class component in a React App. But I believe "componentDidMount" is not called on the server, its called in the Client. So this defeats my attempt of getting everything to render in the server.
class ArticleDetails extends React.Component {
state = {
item: null,
componentDidMount() {
this.getData();
}
getData(articleId){
console.log("getData")
getDataFromThirdPartyLib(articleId)
.then((item) => {
self.setState({
item: item
}
});
}
render() {
console.log("IN RENDER")
console.log(this.state.item) // THIS HAS A VALUE
// my HTML
}
}
SOLUTION as provided by Steve Holgado
The problem was that my "getData" method was not returning the promise as Steve said.
However my "getData" method was a little more complex than I put in this post originally which was why when I first tried Steve's solution it did not work.
My getData is nesting two promises. If i put the return on BOTH promises it works perfectly.
function getData(articleId){
return getDataFromThirdPartyLib(articleId)
.then((item) => {
return getSecondBitOfDataFromThirdPartyLib(item)
.then((extraInfo) => {
return {
item: item,
extraInfo : extraInfo
}
});
return {
item: item
}
});
}
You need to return the promise from getData in article/[id].js:
function getData(articleId){
// return promise here
return getDataFromThirdPartyLib(articleId)
.then((item) => {
return {
item: item
}
});
}
...otherwise undefined is returned implicitly.
Also, where are you getting articleId from?
Is it supposed to come from the url?
static async getInitialProps(ctx) {
const articleId = ctx.query.id;
const data = await getData(articleId);
return {
data: data
}
}
Hope this helps.
I'm learning react and it's great, but i've ran into an issue and i'm not sure what the best practice is to solve it.
I'm fetching data from an API in my componentDidMount(), then i'm setting some states with SetState().
Now the problem is that because the first render happens before my states have been set, im sending the initial state values into my components. Right now i'm setting them to empty arrays or empty Objects ({ type: Object, default: () => ({}) }).
Then i'm using ternary operator to check the .length or if the property has a value.
Is this the best practice or is there some other way that i'm unaware of?
I would love to get some help with this, so that i do the basics correctly right from the start.
Thanks!
I think the best practice is to tell the user that your data is still loading, then populate the fields with the real data. This approach has been advocated in various blog-posts. Robin Wieruch has a great write up on how to fetch data, with a specific example on how to handle loading data and errors and I will go through his example here. This approach is generally done in two parts.
Create an isLoading variable. This is a bolean. We initially set it to false, because nothing is loading, then set it to true when we try to fetch the data, and then back to false once the data is loaded.
We have to tell React what to render given the two isLoading states.
1. Setting the isLoading variable
Since you did not provide any code, I'll just follow Wieruch's example.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: null,
};
}
componentDidMount() {
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi }));
}
...
}
export default App;
Here we are using the browser's native fetch() api to get the data when the component mounts via the use of componentDidMount(). This should be quite similar to what you are doing now. Given that the fetch() method is asynchronous, the rest of the page will render and the state will be up dated once the data is received.
In order to tell the user that we are waiting for data to load, we simply add isLoading to our state. so the state becomes:
this.state = {
dataFromApi: null,
isLoading: false,
};
The state for isLoading is initially false because we haven't called fetch() yet. Right before we call fetch() inside componentDidMount() we set the state of isLoading to true, as such:
this.setState({ isLoading: true });
We then need to add a then() method to our fetch() Promise to set the state of isLoading to false, once the data has finished loading.
.then(data => this.setState({ dataFromAPi: data.dataFromApi, isLoading: false }));
The final code looks like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: [],
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi, isLoading: false }));
}
...
}
export default App;
2. Conditional Rendering
React allows for conditional rendering. We can use a simple if statement in our render() method to render the component based on the state of isLoading.
class App extends Component {
...
render() {
const { hits, isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
return (
<ul>
{dataFromApi.map(data =>
<li key={data.objectID}>
<a href={data.url}>{data.title}</a>
</li>
)}
</ul>
);
}
}
Hope this helps.
It Depends.
suppose you are fetching books data from server.
here is how to do that.
state = {
books: null,
}
if, your backend api is correctly setup.
You will get either empty array for no books or array with some length
componentDidMount(){
getBooksFromServer().then(res => {
this.setState({
books: res.data
})
})
}
Now In Your render method
render() {
const { books } = this.state;
let renderData;
if(!books) {
renderData = <Spinner />
} else
if(books.length === 0) {
renderData = <EmptyScreen />
}
else {
renderData = <Books data = { books } />
}
return renderData;
}
If you are using offline data persistence In that case initially you won't have empty array.So This way of handling won't work.
To show the spinner you have to keep a variable loader in state.
and set it true before calling api and make it false when promise resolves or rejects.
finally read upon to state.
const {loader} = this.state;
if(loader) {
renderData = <Spinner />
}
I set initial state in constructor. You can of course set initial state of component as static value - empty array or object. I think better way is to set it using props. Therefore you can use you component like so <App items={[1,2,3]} /> or <App /> (which takes value of items from defaultProps object because you not pass it as prop).
Example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // or items: {...props.items}
};
}
async componentDidMount() {
const res = await this.props.getItems();
this.setState({items: res.data.items})
}
render() {
return <div></div>
}
};
App.defaultProps = {
items: []
}
I am having issues with Promises pending and not resolving in time. When I try to use async to wait for a value, what I end up getting "Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead." and that's probably because I'm calling this peculiar method I'm trying to use that returns a promise in the render lifecycle method. Well, I tried using .then to retrieve the value but that didn't work either.
I'm going to be laying out a couple of files and explaining the best I can what does what and if there are better ways to do what I am trying to do, suggestions would be wonderful! If it's fixable, even better! Any help is greatly appreciated!
App.js (main app)
Components
- Navigation.js (Navigation bar.)
- MainContent.js(Main content: once you click on a navigation item, everything inside of main content changes)
MainContent.js
tabHandler = (tab) => {
//what do I do here? Immediately if I place the async keyword in the definition, the compiler hates me, but if I don't, then I don't get what I want.
const test = this.props.tabAPICall(tab).then(value => { console.log(value) })
console.log(test);
//if(tab === all the different navigation tabs, render stuff differently){
//as an example:
// return <Songs />
//}
//else
}
render(){
const { chosenTab } = this.props;
return (
<React.Fragment>
<ul.className="main-content">
{
chosenTab !== "" ? this.tabHandler(chosenTab) : null
}
</ul>
</React.Fragment>
)
}
the tabAPICall comes from App.js here:
tabAPICall = (tab) => {
const { token, baseUrl } = this.state;
fetch(`${baseUrl}/albums`, {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + token }
})
.then((response) => response.json())
.then((data) => {
return data;
})
chosenTab gets updated at the app level to trigger a re-render which is what I want
You should set data fetching from API to your component state. And display data in the state:
in the tabHandler, after we get the data, set it to this.state.song through setState({ song: value }). When the data in state in changed. React will do the re-render and inject new data in state to your component.
constructor() {
this.state = { song: null }
}
componentDidMount() {
this.tabHandler(this.props.chosenTab)
}
componentWillReceiveProps(nextProps) {
if (nextProps.chosenTab != this.props.chosenTab) {
this.tabHandler(nextProps.chosenTab)
}
}
tabHandler = (tab) => {
//what do I do here? Immediately if I place the async keyword in the definition, the compiler hates me, but if I don't, then I don't get what I want.
this.props.tabAPICall(tab).then(value => { this.setState({ song: value }) })
}
render(){
const { chosenTab } = this.props;
return (
<React.Fragment>
<ul.className="main-content">
{ this.state.song ? <Song someProp={song} /> : null }
</ul>
</React.Fragment>
)
Hey I'm new is react my requirement is that when a user clicks on a button an ajax get request get fired to the
server and based of receieved response I have to prepare the html and display it.
below is my code it is not working .. it can be solved in jquery by using async: false but i don't have to use that
any idea how to solve using axios
import React from "react";
import axios from "axios"
class UserItems extends React.Component {
constructor(props) {
super(props);
this.state = {
useritem: ''
}
}
prepareHtmlOnAjaxResponse(){
var user_item = this.state.useritem
// html prepation done here
return preparedHtml;
}
getDatFromServeronclick() {
// getting user data from server is done in this function
// when data is receieved it is stored in a state
var self = this;
var promise = axios.get("http://localhost:4000/user/1/items.json")
promise.then(function (response) {
self.setState({ useritem: response.data.items })
self.prepareHtmlOnAjaxResponse() // prepare html
})
console.log("executing first and returning null")
}
render() {
var result = this.getDatFromServeronclick() // getting undefined value this has to be called onclick
return (
<div>
{result} / result is undefined
</div>
);
}
}
export default UserItems;
You have to use self.setState function instead of self.state assignment, otherwise React wouldn't trigger rerender of the component.
var promise = axios.get("http://localhost:4000/user/1/items.json")
promise.then(function (response) {
self.setState({ useritems: response.data.items })
})
From React's documentation:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
Then in your render function
<button onClick={() => this.getDatFromServeronclick() }> {this.state.useritems.map(user => user.title)} </button>
you can replace user.title with whatever keys your object useritems has.