Reputation: 659
My own answer to the question works, Pedrag's comment which is included in my answer also works
I am doing a http request call and don't want to display the page until the http response came back (the request call is getRoadmaps). I have a redux state called "loading" to determine if the response came back yet? Any insights on how to implement that in my code? My other redux states are roadmap, which contains an object also called roadmap (sorry for the confusion).
P.S. I saw a few answers that said to do (psuedo code): if not loaded display loading screen else load the normal screen in render. But my problem doesn't exists in render, but in componentWillMount() where I do
this.props.getRoadmaps()
and then 3 lines later where I do
console.log(this.displayRoadmap[0].roadmap[0])
which should successfully log the roadmap if getRoadmaps() finished, it seems like getRoadmaps() is called but then the program continues without getRoadmaps() completely finishes, which causes my displayRoadmap to be undefined. This also leads to some weird phenomenon like if I go from one screen into this component it always works but if I refresh the page it doesn't work
import React, { Component } from "react";
import Tree from "./Tree";
import TreeSidebar from "./TreeSidebar";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { getRoadmaps } from "../actions/roadmapAction";
class OneRoadmap extends Component {
constructor(props) {
super(props);
this.state = {
loading: this.props.roadmap.loading,
roadmapName: "click a white circle to show more!",
roadmapImg:
"https://cdn3.iconfinder.com/data/icons/harmonicons-06/64/plus-circle-512.png",
roadmapDetail: [{ text: "Click on something" }, { text: "hi" }],
treeData: //deleted,just the default big json object
};
}
componentWillMount() {
this.props.getRoadmaps();
var location1 = this.props.location.pathname;
var n = location1.slice(9);
var current_roadmaps = this.props.roadmap.roadmaps;
this.displayRoadmap = current_roadmaps.filter(
eachRoadmap => eachRoadmap._id == n
);
// now we have a roadmap
console.log(this.displayRoadmap[0].roadmap[0]);
this.setState({ treeData: this.displayRoadmap[0].roadmap[0] });
console.log("is good");
console.log(this.props.loading);
}
componentDidMount() {}
handle_on_click_change = d => {
this.setState({ roadmapName: d.data.name });
this.setState({ roadmapImg: d.data.img });
if (d.data.details == undefined) {
this.setState({
roadmapDetail: [{ text: "The author did not put anything here" }]
});
} else {
this.setState({ roadmapDetail: d.data.details });
}
};
render() {
return (
<div>
{console.log(this.state.loading)}
<ul>
<li styles={{ float: "left" }}>
<div>
{console.log(this.state.treeData)}
<Tree
on_click_change={this.handle_on_click_change}
roadmapData={this.state.treeData}
/>
</div>
</li>
<li styles={{ float: "right" }}>
<div>
<TreeSidebar
displayName={this.state.roadmapName}
displayImg={this.state.roadmapImg}
displayDetail={this.state.roadmapDetail}
/>
</div>
</li>
</ul>
</div>
);
}
}
const mapStateToProps = state => ({
roadmap: state.roadmap
});
export default connect(
mapStateToProps,
{ getRoadmaps }
)(OneRoadmap);
Upvotes: 1
Views: 19536
Reputation: 630
Personally, how I do this I use CSS styles I don't know if this if the best approach to do this but it works for me, I will do something like this
this state = {
loaderStyle: 'block',
contentStyle: 'none'
}
componentDidMount() {
If(this.displayRoadmap[0].length > 0) {
this.setsState({loaderStyle: 'none', contentStyle:
'block:
} else /// do something
};
In my render function, I will do this
<Loader style={{display: this.state.loaderStyle}}/>
<Content style={{display: this.state.contentStyle}}>.
</Content>
Upvotes: 4
Reputation: 659
I checked elraphty's answer because he does answer part of my question and he also wanted me to check his answer. Anyways, if you have trouble fetching data and waiting for the data to finish fetching, here's a complete example of how I do it. Predrag Beocanin's comment works:
fetch('some_url', { method: 'GET' }).then(res = res.json()).then(data => { // some parse here; return parsed}
Here's how I do it (using axios instead of metch, sending request to another completely different file /api/roadmap, and then using Redux to update the store.
In OneRoadmap.js, which is the component I need data fetching in, this is what I call to fetch the data:
async componentDidMount() {
await this.props.getRoadmaps();
.....
}
This makes it so that we will run this.props.getRoadmaps() and wait until the line to complete before going to the next line. If you don't put async/await, this.props.getRoadmaps() will trigger but the code in componentDidMount() will immediately continue without this.props.getRoadmaps() to finish data fetching and then updating the state. Now, ofcourse, Predrag's answer should definitely solve this problem, but just in case you like to place data fetching and state updating with redux in different files (like me), keep reading.
this is what getRoadmaps() does, which is in a file called RoadmapActions.js:
export const getRoadmaps = () => dispatch => {
dispatch(setRoadmapsLoading());
return axios
.get("/api/roadmaps")
.then(res => dispatch({ type: GET_ROADMAPS, payload: res.data }));
};
This says that we will send a get request to "/api/roadmaps", then take the return data from the get request, and then send a dispatch to the redux store, which will update the store state and we will ultimately use it in OneRoadmap.js. This is what /api/roadmaps get request returns:
router.get("/", (req, res) => {
Roadmap.find()
.sort({ date: -1 })
.then(roadmaps => res.json(roadmaps));
});
Roadmap.find() goes into our mongo Database and retrieves all the roadmap items, then we return that to whereever it was called from.
With that returned data, we send a dispatch to the reducer with that data, this is what GET_ROADMAPS() does, GET_ROADMAPS locates in roadmapReducer.js:
const initialState = {
roadmaps: [],
loading: false,
current_roadmap: ""
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_ROADMAPS:
return {
...state,
roadmaps: action.payload,
loading: false
};
}
Then, that's it, your redux store state is updated and since I connected OneRoadmap.js with state.roadmap, I can use it freely in the code.
Upvotes: 0
Reputation: 3483
This can easily achieved by React Higher Order Components as follows.
withLoadingScreen.js
import * as React from "react";
const withLoadingScreen = WrappedComponent => {
return class LoadingScreen extends React.Component {
render() {
if (this.props.loading) return <div className="pre-loader" > Loading... </div>
return <WrappedComponent {...this.props} />;
}
};
};
export default withLoadingScreen;
Use HOC as follows with your component
export default compose(
connect(mapStateToProps, mapActionCreators),
withLoadingScreen
)(OneRoadmap)
You can pass custom parameters too to the HOC. For more example please follow links below. Make changes as you want on your code by referring them.
https://medium.com/@Farzad_YZ/handle-loadings-in-react-by-using-higher-order-components-2ee8de9c3deb
Upvotes: 0
Reputation: 1400
First and most important, don't use componentWillMount
, rather, use componentDidMount
. UNSAFE_componentWillMount()
is legacy and will be removed in version 17. Now, onto the next issue, you want to start your page off by setting the loading
state to true.
this.state = {
loading: true,
roadmapName: "click a white circle to show more!",
roadmapImg:
"https://cdn3.iconfinder.com/data/icons/harmonicons-06/64/plus-circle-512.png",
roadmapDetail: [{ text: "Click on something" }, { text: "hi" }],
treeData: //deleted,just the default big json object
};
After that, you want to tweak your render method a bit to support conditional rendering:
render() {
if (this.state.loading) {
return <LoadingBar />;
}
return (
<div>
<ul>
<li styles={{ float: 'left' }}>
<div>
{console.log(this.state.treeData)}
<Tree
on_click_change={this.handle_on_click_change}
roadmapData={this.state.treeData}
/>
</div>
</li>
<li styles={{ float: 'right' }}>
<div>
<TreeSidebar
displayName={this.state.roadmapName}
displayImg={this.state.roadmapImg}
displayDetail={this.state.roadmapDetail}
/>
</div>
</li>
</ul>
</div>
);
}
Now, whenever your data is ready, you can simply do this.setState({ loading: false })
, and the render method will return whatever is not the loading bar. So in your specific case:
this.setState({ treeData: this.displayRoadmap[0].roadmap[0], loading: false });
Upvotes: 1