Rendering React components with promises inside the render method
render()
method should render UI from this.props
and this.state
, so to asynchronously load data, you can use this.state
to store imageId: imageUrl
mapping.
Then in your componentDidMount()
method, you can populate imageUrl
from imageId
. Then the render()
method should be pure and simple by rendering the this.state
object
Note that the this.state.imageUrls
is populated asynchronously, so the rendered image list item will appear one by one after its url is fetched. You can also initialize the this.state.imageUrls
with all image id or index (without urls), this way you can show a loader when that image is being loaded.
constructor(props) {
super(props)
this.state = {
imageUrls: []
};
}
componentDidMount() {
this.props.items.map((item) => {
ImageStore.getImageById(item.imageId).then(image => {
const mapping = {id: item.imageId, url: image.url};
const newUrls = this.state.imageUrls.slice();
newUrls.push(mapping);
this.setState({ imageUrls: newUrls });
})
});
}
render() {
return (
<div>
{this.state.imageUrls.map(mapping => (
<div>id: {mapping.id}, url: {mapping.url}</div>
))}
</div>
);
}
API call returns a promise instead of data, inside react component
so basically this is what is happening:
- you have component 'X'
- inside component 'X' you create a
const data
which uses an async function within it to generate some properties etc. Remember its async, but your react component is not. So the 'Promise' of this async function would not have resolved by the time your return statement starts rendering on your screen. So ultimately when your<Line/>
component is rendered,data
is still a pending Promise` and not really actual data.
There are a few other complexities to why your code is not working, one of them being the async await
inside a .map
which needs to be dealt with in a whole other way, using something called Promise.all
, but what i have tired to explain above, is more of a concept on why your code is not working.
I'll try to give a simplified example that should explain the correct 'pattern' to do what you are doing:
import main from '../<some path>' //your current async function. assuming it does what its supposed to be doing
//this is how you deal with async in map.
const genFullData = async (labels, callback) => {
let promises = labels.map(async (year) => {
let result = await main(year, props.statSelection, 25);
return result;
}),
let dataResults = await Promise.all(promises);
let fullData = {
labels,
datasets: [
{
label: "25th percentile",
data: dataResults
},
],
};
callback(fullData);
}
const X = props => { //react comp
const [data, setData] = useState(undefined)
useEffect(() => {
genFullData(
props.labels,
(fullData) => setData(fullData)
)
},[props.labels])
return (
!data ? <h3>Loading...</h3> : <Line data={data} />
)
}
So ill explain the different parts of the above code:
First the
async genFullData function
. 2 things to notice. 1// see howasync
inside.map
has to be dealt with using something calledPromise.all
read up on it. But in essence,Promise.all
ensures that the function 'waits' until all the promises in the.map
loop are resolved (or rejected), and only then moves on with the code. 2// Second thing to notice is, how in the end, once the Promises are done with, i take the results, shove them into the data structure that i picked up from your code, and then i feed this 'final data' to a callback function, which you'll understand in a bit.Now lets look at the react component. //1 have initiated a local
data
state with a value ofundefined
//2 Inside auseEffect
, I fire thegenFullData
function, and in the callback that i pass to it, i update the data state to whatever thegenFullData
function returns.Finally the return statement of the comp renders a 'loading..' if the data state is
undefined
, and the minute the data state becomes 'defined', then the Line comp is rendered with the appropriate data being passed to it.
This last bit can be confusing so ill try to summarize again.
- comp renders for the first time.
- Init data state is set to undefined
genAllData
function is fired in a useEffect (which fires only once). This is an sync function, so it takes sometime till it actually manages to get the data.- In the meantime, the react component continues rendering (remember, data has not been fetched yet by
genAllData
) - React hits the return statement, where it checks to see if data is 'defined'. Well it is not, so it renders 'Loading..' on screen
- In the meantime,
genAllData
manages to successfully complete its operations, and the callback function is fired. - In this callback, we
setData
to whatevergenAllData
is providing to the callback. - This
setData
triggers the component to rerender. - return statement fires again.
- this time data IS 'defined', so the
Line
comp is rendered, with the appropriate data being passed into it.
Rendering async promises through function components
You're right. The result of getUnitPrice()
is a Promise, not a value, so what React does is it prints out the stringified version of that Promise. If you need the fulfilled value, you need a state value that will re-render the page if updated. Something like this:
const [price, setPrice] = useState('');
useEffect(() => {
getUnitPrice().then((p) => setPrice(p));
}, []);
...
<div>Price: {price}</div>
If you're using a class component, you can initialize the state the same way like this:
state = {
price: '',
}
async componentDidMount() {
const p = await getUniPrice();
this.setState({ price: p });
}
Render a simple list in React with promises
This code will handle the object that is returned by api and also moves the fetching to componentDidMount
.
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount() {
firebaseCon.content.get('text', { fields: ['id', 'title'] })
.then((response) => {
let data = [];
for (item in response) {
data.push(response[item]);
}
this.setState({ data });
});
}
render() {
let itemList = this.state.data.map(function(item) {
return <li className="item" key={item.id}>{item.title}</li>;
});
return (
<ul>
{itemList}
</ul>
)
}
A closer look at Promises' methods then
and catch
should make it clearer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
How to re-render React Component when promise resolves? | How to block render until data loads?
Writing a functional react component is simple with the new React Hooks. In the example below, I'm using useState
and useEffect
. The useState
hook is synonymous with this.state/this.setState
in a class-based React component. The useEffect
hook is similar to componentDidMount
+componentDidUpdate
. It also is capable of being componentDidUnmount
.
The way the code will execute is from top to bottom. Because it's functional, it will run through once and render with the default state set at the argument to useState
. It will not block on getting data from the API in the useEffect
function. Thus, you need to be able to handle loading without having data. Anytime props.apiConfig
or props.id
changes, the component will re-render and all the useEffect
again. It will only call useEffect
if props.apiConfig
and props.id
do change after first run. The only nasty part is that useEffect
cannot be an async
function, so you have to call the function getDataWrapper
without using await
. When the data is received by the API, it will store the data in state
, which will trigger a re-render of the component.
To Summarize:
- Render once with default state
- Call
useEffect
, which callsgetDataWrapper
- return component with initial values in
useState
- Call
- Once data is received by the API in the
useEffect
/getDataWrapper
function, set the state viasetState
& setisLoading
tofalse
- Re-render the component with updated value that
setState
now contains- Avoid the
useEffect
control path since the values in the second argument ofuseEffect
have not changed. (eg:props.apiConfig
&props.id
).
- Avoid the
import React, { useState, useEffect } from 'react';
import { getDataFromAPI } from './api';
const MyComponent = (props) => {
const [state, useState] = useState({
isLoading: true,
data: {}
});
useEffect(() => {
const getDataWrapper = async () => {
const response = await getDataFromAPI(apiConfig, props.id);
setState({
isLoading: false,
data: response
});
});
getDataWrapper();
}, [props.apiConfig, props.id]);
if(state.isLoading) { return <div>Data is loading from API...</div>
return (
<div>
<h1>Hello, World!</h1>
<pre>{JSON.stringify(state.data, null, 2)}</pre>
</div>
);
};
export default MyComponent;
How can I display the result of a promise on a webpage in a react export
The render
method of all React components is to be considered a pure, synchronous function. In other words, there should be no side effects, and no asynchronous logic. The error Error: Objects are not valid as a React child (found: [object Promise])
is the component attempting to render the Promise object.
Use the React component lifecycle for issuing side-effects. componentDidMount for any effects when the component mounts.
class App extends Component {
state = {
athena: null,
}
componentDidMount() {
athena.then(result => this.setState({ athena: result }));
}
render() {
const { athena } = this.state;
return athena;
}
}
If you needed to issue side-effects later after the component is mounted, then componentDidUpdate is the lifecycle method to use.
Class components are still valid and there's no plan to remove them any time soon, but function components are really the way going forward. Here's an example function component version of the code above.
const App = () => {
const [athenaVal, setAthenaVAl] = React.useState(null);
React.useEffect(() => {
athena.then(result => setAthenaVAl(result));
}, []); // <-- empty dependency array -> on mount/initial render only
return athenaVal;
}
The code is a little simpler. You can read more about React hooks if you like.
Related Topics
Playing Sound Notifications Using JavaScript
Passing Variable from JavaScript to Ruby on Rails
How to Pass Parameters in Get Requests with Jquery
How to Display PDF (Blob) on iOS Sent from My Angularjs App
Inline Ruby in :JavaScript Haml Tag
How to Execute Array of Promises in Sequential Order
Linguistic Meaning of 'Let' Variable in Programming
Access Elements of Parent Window from Iframe
How to Create Query Parameters in JavaScript
JavaScript Regexp Dynamic Generation from Variables
How to Programmatically Click on an Element in JavaScript
Embed a JavaScript Engine in an iOS Application
Preloader Wont Ignore Websocket - Pace Js
How to Read Console Logs of Wkwebview Programmatically
Javascript: Undefined !== Undefined