Wait for data to be fetched in child components, then render - javascript

I have a React app that uses multiple fetch calls throughout different components. In Home page component, I have imported smaller components, all of whom have it's own fetch call.
render() {
return (
<React.Fragment>
<Banner/>
<Services />
<About />
</React.Fragment>
)
}
Banner, Services and About have their own fetch calls to different endpoints, now my question is because the response is a little bit on the slower side, how to wait for all of the child components to fetch data, then render the Homepage component. I have tried to put the state of isLoading and add a loader to wait for components to fetch, but I don't know what to wait for to set isLoading to false.

...how to wait for all of the child components to fetch data, then render the Homepage component
You don't. Instead, you move the fetches to the Homepage component's parent, and then have that parent only render the Homepage component when it has all of the necessary information to do so. In React parlance, this is "lifting state up" (e.g., up the hierarchy to the parent).
While you could render the Homepage in a "loading" form, and have it render its child components in a "loading" form, and have the child components call back to the Home page to say they have their information now, that's more complicated than simply lifting the state up to the highest component that actually needs it (so it knows it can render the Homepage).

As #TJCrowder mentioned in his answer, You'll need to lift your state up and keep it in the parent component. Make a network request there and pass the data to your child component as props. You can read more about lifting-state-up here
class YourComponent extends React.Component {
state = {isLoading: true, isError: false, banner: null, services: null, about: null};
async componentDidMount() {
try {
const [banner, services, about] = await Promise.all([
// all calls
]);
this.setState({ isLoading: false, banner, services, about });
} catch (error) {
this.setState({ isError: true, isLoading: false });
}
}
render() {
if (this.state.isLoading) {
return <div>Loading...</div>
}
return (
<React.Fragment>
<Banner data={this.state.banner} />
<Services data={this.state.services} />
<About data={this.state.about} />
</React.Fragment>
)
}
}

using promises in fetch you could, as suggested, have a isLoaded property state determine whether or not a component should render or not.
class ShouldRender extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoaded: false,
}
}
componentDidMount() {
fetch('http://someresource.com/api/resource')
.then(res => res.json())
.then(data => {
this.state({
data,
isLoaded: true,
});
})
}
render() {
const { isLoaded } = this.state;
if (isLoaded) {
return <MyAwesomeReactComponent />
}
return null;
}
}
So once the state is updated it will trigger a rerendering of the component with the new state that will render the if statement true and you're JSX will appear.

Related

Sync local react state with data from redux

I have an App component which contains all the routes
App.jsx
import Editor from './Editor';
function App(props) {
return (
<BrowserRouter>
<Switch>
<Route path="/cms/post/edit/:postId" component={Editor} />
</Switch>
</BrowserRouter>
);
}
export default App;
I have an Editor component where user can edit a post. This component maps the data from redux store to local state as data needs to be manipulated locally.
Editor.jsx
// Usual Imports
class Editor extends Component {
constructor(props) {
super(props);
this.state = {
title: props.post ? props.post.title : '',
content: props.post ? props.post.content : ''
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.props.post ? null : this.fetchPosts(this.props.match.params.postId);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
updatePost(data) {
// function to updatePost
}
fetchPosts(id) {
// function to fetch Posts
}
render() {
return (
<React.Fragment>
<input type="text" name="title" value={this.state.title} onChange={this.handleChange} />
<input type="text" name="content" value={this.state.content} onChange={this.handleChange} />
</React.Fragment>
);
}
}
const mapStateToProps = (state, ownProps) => ({
post: state.posts[ownProps.match.params.postId] || false,
...ownProps
}),
mapDispatchToProps = dispatch => ({
updatePost: data => dispatch(updatePost(data)),
fetchPosts: params => dispatch(fetchPosts(params))
});
export default connect(mapStateToProps, mapDispatchToProps)(Editor);
Now my questions are
Initially, post data is available and fetchPosts is not called. However, if user refreshes the page then post becomes false and fetchPosts is called and redux store is updated.
In which lifecycle method should I update the local react state with data from props?
Possible solutions which I think could be
A. Updating state in componentWillReceiveProps
componentWillReceiveProps(nextProps) {
this.setState({
title: nextProps.post.title,
content: nextProps.post.content
});
}
However, React docs discourages using componentWillReceiveProps as it might be invoked many times in React 16 and so on.
B. Update state in componentDidUpdate
componentDidUpdate(prevProps, prevState) {
if (this.props.post != prevProps.post) {
this.setState({
title: this.props.title,
content: this.props.content
});
}
}
I am not sure about this as I think there might be hidden side effects of this approach.
C. Not setting initial state and providing value to input tags via props i.e. value={this.props.post.title} and updating state via onChange handlers. However, when value is undefined then React will throw an error.
D. Newer lifecycle methods like getDerivedStateFromProps. Not too sure as React docs says it should be used in rare cases when state changes based on props over time.
I need to maintain the state as current component is also used for creating a fresh post also.
Which approach would be the best? And if I am missing out on something then let me know! Thanks!

Call a function in react component, WITHOUT event handlers or props

sorry if this question appeared somewhere else, but it's getting extremely frustrating to find answers where every question involves event handler or child element method calling.
I need to call a function when component is initialized, basically when window loads, or instantly.
On initialization I want to call a getGameMeta() to update Game state, if I'm trying to call it in jsx either I make a loop or get an error saying "Functions are not valid as a React child. This may happen if you return a Component instead of from render...."
class Game extends React.Component{
constructor(props) {
super(props);
this.state = {name: undefined,};
this.getGameMeta = this.getGameMeta.bind(this);
}
getGameMeta(){
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
};
render(){
return (
<div>
{/* {this.getGameMeta()} */} causes loop
{/* {this.getGameMeta} */} causes error
<p>{this.state.name}</p>
</div>
);
};
};
Using the componentDidMount hook is a great way to load data from a remote endpoint when the component is first mounted.
Example
class Game extends React.Component {
constructor(props) {
super(props);
this.state = { name: undefined };
this.getGameMeta = this.getGameMeta.bind(this);
}
componentDidMount() {
this.getGameMeta();
}
getGameMeta() {
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
}
render() {
return (
<div>
<p>{this.state.name}</p>
</div>
);
}
}
You can call it in componentDidMount. It guarantees that it will be called once and right after when component will be mounted. More over from React Docs:
If you need to load data from a remote endpoint, this is a good place
to instantiate the network request.
getGameMeta(){
fetch(Url).then(data => {
console.log(data);
this.setState({
name: data[0].name
});
});
};
componentDidMount(){ this.getGameMeta() }
So seems like this is the way you are looking for
You can simply use useEffect if you are using a functional component.
basically, it loads the data before rendering your UI.
import {useEffect} from "react";
const GameData=()=>{
const [fetchD,setFetchD]=useState("");
useEffect(()=>{
fetch(Url).then(data => {
console.log(data);
setFetchD(data[0].name);
});
});
})
}
export default GameData;
//you can also check react documentation at https://reactjs.org/docs/hooks-effect.html

Rendering component with its state

I'm really confused now about lifecycle hooks. Here's my code:
App.js:
class App extends Component {
constructor(props){
super(props);
this.state = {
arrayOfComponents: []
}
}
componentDidMount(){
//i get the properties from the server which responds with the database's elements
fetch('http://localhost:3001/')
.then(res => res.json())
.then(arrayOfData => this.setState({arrayOfComponents: arrayOfData}))
.catch(err => console.log(err))
}
render() {
console.log(this.state) //first returns empty array, after the mount returns the db's array of elements
return (
<div className="App">
<Component name='First' id={1} componentsComponents={this.state.arrayOfComponents} />
</div>
);
}
}
Component.js:
class Component extends React.Component{
constructor(props){
super(props);
this.state={
componentsComponents: []
}
}
//here i was tried with componentDidMount, componentWillMount to set the
//this.props.componentsComponents to this.state.componentsComponents
//but it didn't work
renderComponents = () => {
if(this.state.componentsComponents.length){
return this.state.componentsComponents.filter(c => c.inhertedFromId === this.props.id).map(c => {
return <Component name={c.name} id={c.id} componentsComponents={this.props.componentsComponents} />
})
}
}
render(){
return(
<div>
{this.renderComponents()}
</div>
)
}
}
So what i want to do is to the components renders themselves, depending on the array they get from the App.js. But how to set the state before the render happens? Can i somehow ask the component to render again if it did mount? Or any other solutions?
You can simply assign this.props.componentsComponents in constructor itself only.
constructor(props){
super(props);
this.state={
componentsComponents: this.props.componentsComponents||[]
}
}
Bring Filter Up To App
Here it appears you are not calling renderComponents, and you are also trying to render a Component inside itself, which is difficult to reason about. Bring the renderComponents function up to App, and render the data using Component inside of App, and simply pass props down to a stateless Component, which may be a simpler solution to represent the data.
If a recursive call is indeed the best way to represent this data, may need to use getDerivedStateFromProps to move props into state on update, if you wish to store that data in state - something like:
static getDerivedStateFromProps(nextProps) {
return {
componentsComponents: nextProps.componentsComponents
}
}
Added to Component.

React and Reloading API Call

I'm working on a project in React that pulls random data from an API and processes it. Currently, on loading the page, that component that actually pulls the API doesn't load by design.
So to keep things simple, I have two Components. I load Component1 and using a state showDiv: false I don't load Component2 with the API call. I have a button that when clicked changes the state to true, and by doing that, loads Component2 inside Component1.
Now what I want to do is have Component2 hidden again, and then brought back, and call a new set of data from the API and process it. I wrongly assumed that once Component2 was pulled off the page, that bringing it back would load it from scratch eg. rerun the componentWillMount() function inside Component2, which is where my API call is located. Now that I see that's not the case, I'm not sure how to accomplish that without reloading the page entirely.
EDIT:
Here's the component to be rendered:
(This has been heavily simplified. The API pulls an array, and that array is processed into an array of objects, and then it is placed in the state. The component's render is then populated by content taken from that processed array from the state)
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
isQuestionLoaded: false,
questions: [],
};
}
componentWillMount() {
fetch(this.props.category)
.then(results => {
return results.json();
}).then(data => {
let questions = data.results.map((question) => {
return question;
})
this.setState({questions: questions, isQuestionLoaded: true});
})
}
Here's the App.js render of this component:
render() {
let questionRender = this.state.showQuestions ?
<Questions
category={ this.state.categoryActive }
questionNumber={ this.state.questionNumber }
onClickCorrect={ this.nextQuestionCorrect }
onClickCorrectLast={ this.lastQuestionCorrect }
onClickIncorrect={ this.nextQuestionIncorrect }
onClickIncorrectLast={ this.lastQuestionIncorrect }
score={ this.state.score }
correct={ this.state.correct }
answerCorrect={ this.state.answerCorrect } />
: null;
Here's the function that takes the component away:
lastQuestionCorrect() {
this.setState({
showQuestions: false,
showFinal: true
});
}
Here's the function that brings the component back:
onClickReplay() {
this.setState({
showQuestions: true,
showFinal: false,
});
}
A new array was being loaded, the problem was one of my states in App.js was not being reset correctly by onClickReplay()

react.js don't render until ajax request finish

I have a pretty simple React.js component which I need to make an isomorphic (rendered on the server). The problem is that component rendered with helpful information only after ajax request completes, like that:
export default React.createClass({
getInitialState() {
return {}
},
componentDidMount() {
fetch("/users/").then(response => {
this.setState(users: response.data)
})
},
render() {
if (this.state.users == undefined) {
return <div />
}
return <div>{this.state.users.map(some_function)}</div>
}
})
The problem is that it's pointless to return empty div to search engines. I want ajax request to be finished (even on the server) and render only after that. How can I achieve that?
As touched on by #Dencker, you want to make the parent component decide when to render the child component, something like this would work:
// Parent
export default React.createClass({
getInitialState() {
return {
usersLoaded: false
}
},
componentDidMount() {
fetch("/users/").then(response => {
this._users = response.users;
this.setState({
usersLoaded: true
});
})
},
render() {
if ( this.state.usersLoaded ) {
return (
<ChildComponent users={this._users} />
)
} else {
return null;
}
}
});
// Child
export default React.createClass({
render() {
return <div>{this.props.users.map(some_function)}</div>
}
});
What I'm doing there is:
Setting an initial state on the parent component which is usersLoaded: false.
In the render function for that component, making sure I only render the child component when the parent's usersLoaded state is true.
parent component's componentDidMount method is where the AJAX call takes place, and note I use a variable on the component to store the users, not the state object (states generally should only be used to store very simple values).
This is then passed down to the child component as a prop.
All of the above makes the child component far simpler as it will only now need a render method and no if/else check.

Categories

Resources