Wait for react-promise to resolve before render - javascript

So I have a large set of data that I'm retrieving from an API. I believe the problem is that my component is calling the renderMarkers function before the data is received from the promise.
So I am wondering how I can wait for the promise to resolve the data completely before calling my renderMarkers function?
class Map extends Component {
componentDidMount() {
console.log(this.props)
new google.maps.Map(this.refs.map, {
zoom: 12,
center: {
lat: this.props.route.lat,
lng: this.props.route.lng
}
})
}
componentWillMount() {
this.props.fetchWells()
}
renderMarkers() {
return this.props.wells.map((wells) => {
console.log(wells)
})
}
render() {
return (
<div id="map" ref="map">
{this.renderMarkers()}
</div>
)
}
}
function mapStateToProps(state) {
return { wells: state.wells.all };
}
export default connect(mapStateToProps, { fetchWells })(Map);

You could do something like this to show a Loader until all the info is fetched:
class Map extends Component {
constructor () {
super()
this.state = { wells: [] }
}
componentDidMount() {
this.props.fetchWells()
.then(res => this.setState({ wells: res.wells }) )
}
render () {
const { wells } = this.state
return wells.length ? this.renderWells() : (
<span>Loading wells...</span>
)
}
}

for functional components with hooks:
function App() {
const [nodes, setNodes] = useState({});
const [isLoading, setLoading] = useState(true);
useEffect(() => {
getAllNodes();
}, []);
const getAllNodes = () => {
axios.get("http://localhost:5001/").then((response) => {
setNodes(response.data);
setLoading(false);
});
};
if (isLoading) {
return <div className="App">Loading...</div>;
}
return (
<>
<Container allNodes={nodes} />
</>
);
}

Calling the render function before the API call is finished is fine. The wells is an empty array (initial state), you simply render nothing. And after receiving the data from API, your component will automatically re-render because the update of props (redux store). So I don't see the problem.
If you really want to prevent it from rendering before receiving API data, just check that in your render function, for example:
if (this.props.wells.length === 0) {
return null
}
return (
<div id="map" ref="map">
{this.renderMarkers()}
</div>
)

So I have the similar problem, with react and found out solution on my own. by using Async/Await calling react
Code snippet is below please try this.
import Loader from 'react-loader-spinner'
constructor(props){
super(props);
this.state = {loading : true}
}
getdata = async (data) => {
return await data;
}
getprops = async (data) =>{
if (await this.getdata(data)){
this.setState({loading: false})
}
}
render() {
var { userInfo , userData} = this.props;
if(this.state.loading == true){
this.getprops(this.props.userData);
}
else{
//perform action after getting value in props
}
return (
<div>
{
this.state.loading ?
<Loader
type="Puff"
color="#00BFFF"
height={100}
width={100}
/>
:
<MyCustomComponent/> // place your react component here
}
</div>
)
}

Related

React: Async function not being called

Why is my aync call fetchButtonTeams() below not being called. I am trying to print its results in console.log(this.state.data) below. Even if i call it in the render() I get infinite loops or errors. Can anyone suggest what to do?
I just want to print the results in console.log in render()
class TeamFilter extends Component {
constructor() {
super();
this.state = { data: [] };
}
async fetchButtonTeams() {
const response = await fetch(`/api/teams`);
const json = await response.json();
console.log(json)
this.setState({ data: json });
}
handleTeamSelection = e => {
this.props.setTeam(e.target.title);
this.props.fetchTeams(e.target.title)
};
render() {
let test = ['Chaos', 'High Elves', 'Orcs']
this.fetchButtonTeams()
console.log(this.state.data)
return (
<DropdownButton id="dropdown-team-button" title={this.props.team_name}>
{test.map(cls => (
<div key={cls}>
<Dropdown.Item onClick={this.handleTeamSelection} title={cls}>{cls}</Dropdown.Item>
</div>
))}
</DropdownButton>
)
}
}
const mapStateToProps = state => {
return {
team_name: state.team_name
}
};
const mapDispatchToProps = dispatch => {
return {
fetchCards: path => dispatch(fetchCards(path)),
fetchTeams: params => dispatch(fetchTeams(params)),
setTeam: team_name => dispatch({ type: "SET_TEAM", team_name })
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamFilter)
The reason you get infinite loops when you call the function on the render method is because each time the function is calling setState which in turn runs the function again and again, triggering an infinite loop.
I don't see where you are calling fetchButtonTeams() anywhere in your component, but a good idea for fetching data is putting the method inside a componentDidMount lifecycle method and console log inside the render method.You can learn more about lifecycle hooks here.
For your code:
class TeamFilter extends Component {
constructor() {
super();
this.state = { data: [] };
}
componentDidMount() {
this.fetchButtonTeams();
}
async fetchButtonTeams() {
const response = await fetch(`/api/teams`);
const json = await response.json();
console.log(json);
this.setState({ data: json });
}
handleTeamSelection = e => {
this.props.setTeam(e.target.title);
this.props.fetchTeams(e.target.title);
};
render() {
let test = ["Chaos", "High Elves", "Orcs"];
console.log(this.state.data);
return (
<DropdownButton id="dropdown-team-button" title={this.props.team_name}>
{test.map(cls => (
<div key={cls}>
<Dropdown.Item onClick={this.handleTeamSelection} title={cls}>
{cls}
</Dropdown.Item>
</div>
))}
</DropdownButton>
);
}
}
const mapStateToProps = state => {
return {
team_name: state.team_name
};
};
const mapDispatchToProps = dispatch => {
return {
fetchCards: path => dispatch(fetchCards(path)),
fetchTeams: params => dispatch(fetchTeams(params)),
setTeam: team_name => dispatch({ type: "SET_TEAM", team_name })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamFilter);

State is being initialized with [object Object] in setState

In the async function below, I call stationData just to confirm that I'm passing an array of objects into bartData (which is just an empty array). Attached is a response of the array of Objects that I am receiving. However, when trying to use this.state.bartData (to confirm that it does have the array of objects), my return function is returning bartData as undefined. Any ideas?
import React from 'react';
const bartKey = process.env.REACT_API_BART_API_KEY;
class StationBaseRoutes extends React.Component{
constructor(props){
super(props);
this.state = {
isLoading: true,
station: [],
stationAbbv: 'ALL',
destination: '',
bartData: []
}
}
componentDidMount(){
this.getAllStationRoutes();
}
async getAllStationRoutes(){
try{
setInterval(async () => {
const response = await fetch(`http://api.bart.gov/api/etd.aspx?cmd=etd&orig=${this.state.stationAbbv}&key=${bartKey}&json=y`);
const jsonResponse = await response.json();
const apiData = jsonResponse.root;
const stationData = apiData.station;
console.log(stationData);
this.setState(({
isLoading: false,
bartData: stationData
}), () => {
console.log(`Callback: ${this.state.bartData}`)
})
}, 20000)
} catch(error){
console.log(error);
}
}
getRoutes = () => {
console.log(`bartData: ${this.bartData}`)
}
render(){
const {station, destination} = this.state;
return(
<div>
<h2>Calling get routes: {this.getRoutes()}</h2>
<h2>Origin: {station}</h2>
<h3>Destination: {destination}</h3>
</div>
)
}
}
export default StationBaseRoutes;
Responses: https://imgur.com/gallery/Luk9MCX
There's a couple of bugs here.
First of all, getRoutes() is using this.bartData instead of this.state.bartData
Secondly, all your objects in console.log are being converted to strings. You can change it to
console.log('bartData:', this.state.bartData);
to be able to see the actual data.
I was unable to get the Bart API to work in a codesandbox, so I had to mock the API... however, the data is still structured the same.
On that note, the API is working as expected, you just need to map over the objects in the this.state.bartData array and deconstruct the properties you want to show.
Working example: https://codesandbox.io/s/031pn7w680
import map from "lodash/map";
import React, { Component, Fragment } from "react";
import { fakeAPI } from "../../api/fakeAPI";
class StationBaseRoutes extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
station: [],
stationAbbv: "ALL",
destination: "",
bartData: []
};
this.getAllStationRoutes = this.getAllStationRoutes.bind(this);
}
componentDidMount() {
this.getAllStationRoutes();
}
async getAllStationRoutes() {
try {
const res = await fakeAPI.get();
const apiData = res.data.root;
const stationData = apiData.station;
this.setState({
isLoading: false,
bartData: stationData
});
} catch (error) {
console.log(error);
}
}
render() {
const { bartData, isLoading } = this.state;
return (
<div className="app-container">
{isLoading ? (
<p className="t-a-c">Loading...</p>
) : (
<Fragment>
<h1 className="t-a-c">Bart Stations</h1>
{map(bartData, ({ name, etd }) => (
<div className="jumbotron station" key={name}>
<h1>Origin: {name}</h1>
{map(etd, ({ destination }) => (
<li key={destination}>Destination: {destination}</li>
))}
</div>
))}
<pre className="preview">
<code>{JSON.stringify(bartData, null, 4)}</code>
</pre>
</Fragment>
)}
</div>
);
}
}
export default StationBaseRoutes;

Why my functions is not returning a component? React

I have a function that render LoginPage if the user is not logged and render the IndexPage if is logged, but It is not rendering none, I tried alerting the user.displayName and It work. See my code.
renderPage = () => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
return <IndexPage />;
} else {
return <LoginPage />;
}
});
};
render() {
return <div>{this.renderPage()}</div>;
}
Why is not working?
You miss a return in the renderPage function, but performing async requests in render is not a good approach in react.
What you should do, is to move the user into the state, then on componentDidMount fetch the user from your async code, and inside your render use the state prop user.
So your code should be something like:
constructor() {
this.state = { user: null };
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
user ? this.setState({ user }) : this.setState({ user: null });
});
}
render() {
const content = this.state.user ? <IndexPage /> : <LoginPage />;
return <div>{content}</div>;
}
Your function inside render method is async function, what you get is undefined.
You should store the user state. Do something like,
class YourComponent extends Component {
constructor(props) {
super(props);
this.state = {
user: null
};
}
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setState({
user
});
}
});
}
render() {
return (
{this.state.user ? <IndexPage /> : <LoginPage />}
);
}
}

Access variable returned from react function within render (in loop)

I have a react class component, where I am passing list of module names and some other attributes. I tried to obtain module code by calling a function getModuleCode() and passing module name as a parameter.
class ModulesListing extends React.Component {
getModuleCode(module){
var moduleCode = 0;
// Do relevant calls and get moduleCode
return moduleCode;
}
render(){
var {modulesList} = this.props;
modulesList.forEach(module => {
//here I need to call getModuleCode as getModuleCode(module.name)
var moduleCode = getModuleCode(module.name)
console.log(moduleCode)
})
return (
//Info to display
);
}
}
If I tried as above mentioned, it prints as undefined
Also tried with setting as state, which is not suitable for looping. Here what I wanted is to get module code wrt certain module.
You could get the codes in componentDidMount and in componentDidUpdate if the modulesList change, and store them in state.
Example
function doCall() {
return new Promise(resolve =>
setTimeout(() => resolve(Math.random().toString()), 1000)
);
}
class ModulesListing extends React.Component {
state = { codes: [] };
componentDidMount() {
this.getModuleCodes();
}
componentDidUpdate(prevProps) {
if (this.props.modulesList !== prevProps.modulesList) {
this.setState({ codes: [] }, this.getModuleCodes);
}
}
getModuleCodes = () => {
Promise.all(this.props.modulesList.map(doCall)).then(codes => {
this.setState({ codes });
});
};
render() {
const { modulesList } = this.props;
const { codes } = this.state;
if (codes.length === 0) {
return null;
}
return (
<div>
{modulesList.map((module, index) => (
<div>
{module}: {codes[index]}
</div>
))}
</div>
);
}
}
ReactDOM.render(
<ModulesListing modulesList={["a", "b", "c"]} />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

React/Redux firing action->render before update a store

A have a simply react/redux app. I Fetch data from API async but component not waiting for data and firing render.
class RestaurantList extends React.Component {
componentWillMount() {
this.props.getRestaurantList();
}
render() {
console.log("render");
let {translation} = store.getState().app;
//------------I NEED DATA ON THIS LET (restaurantList)
let {restaurantList} = this.props.restaurants;
return (
<div>
<TableContainer data={restaurantList}/>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
restaurants: state.restaurants
};
};
const mapDispatchToProps = (dispatch) => {
return {
getRestaurantList() {
dispatch(ACTIONS.getRestaurantList());
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(RestaurantList);
On my action i fetching data using axios :
export function getRestaurantList() {
console.log("action");
return dispatch => {
axios({
method: "GET",
url: URLS.BASE_URL + URLS.URL_RESTAURANT_LIST
}).then((response) => {
console.log(response);
dispatch({
type: CONST.GET_RESTAURANT_LIST,
payload: response.data
})
})
}
}
And my component fired method ComponenWillMount after that render () and next store which update store and set good data to my variable. Maybe u give me advice how to do that to have on my render my fetching data because now on my table I transfer undefined on start. Maybe you give me an example to using another framework like redux-saga or other.
You could try conditionally rendering your TableContainer component so the table will only be rendered once there is data available:
renderTable() {
let { restaurantList } = this.props.restaurants
if (restaurantList) {
return <TableContainer data={ restaurantList } />
} else {
return <div></div>
}
}
render() {
return (
<div>
{ this.renderTable() }
</div>
)
}

Categories