How can I delay rendering until then() of promise completes? - reactjs

I'm using reactjs with redux and this is a part of one of my components.
I want to render postList of the promise which is returned by handleFetchPosts, but the render() function starts rendering before the then() of shouldComponentUpdate is completed.
How can I postpone rendering or rerender after the then is completed?
constructor(props) {
super(props);
this.postList = props.postList;
this.props.handleFetchPosts('/main', 'about', '');
}
shouldComponentUpdate(nextProps) {
if (this.postList !== nextProps.postList) {
nextProps.postList.then(data => {
this.postList = data;
});
return true;
}
else {
return false;
}
}
render() {
return (
<div className="content">
<p>
{this.postList + ""}
{/*content*/}
</p>
</div>
);
}

Apologies, I've totally changed my answer, I bypassed the fact you are using Redux.
The fact that you're using Redux, I'd set up an async action that shouldComponentUpdate calls, which kick-starts the Promise call.
This way you can update a flag in your state after starting the Promise to use in the render method to prevent rendering.
Once the call has finished send the final ACTION with the results and change the flag back.

For manage your component rendering use the state of your component. and update it when you receive your data.
constructor(props) {
super(props);
this.state={
postList:this.props.postList;
}
this.props.handleFetchPosts('/main', 'about', '');
}
shouldComponentUpdate(nextProps) {
if (this.postList !== nextProps.postList) {
nextProps.postList.then(data => {
this.setState({
postList:data
});
});
return true;
}
else {
return false;
}
}
render() {
if(!this.state.postList){
return <div>Loading</div>
}
return (
<div className="content">
<p>
{this.state.postList + ""}
{/*content*/}
</p>
</div>
);
}

Related

child component does not rerender with shouldComponentUpdate

My child component changes its state when its prop is changed. I want to re-render child component ImageSlide so call different string when state changes. The className is well changed and console shows the changed values well. But it does not rerender view.
I tried shouldComponentUpdate, but it did not work.
How can I re-render ImageSlide?
let languages = {
en: require('textEnglish'),
kr: require('textKorean')
}
class ImageSlide extends Component {
constructor(props) {
super(props);
this.state={
lang: this.props.lang,
url: this.props.url
}
}
languageSelect=()=> {
if (this.state.lang === 'kr') {
return 'kr';
} else {
return 'en';
}
}
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.lang !== prevState.lang |
nextProps.url !== prevState.url )
{
return {lang : nextProps.lang, url: nextProps.url} ;
}
}
shouldComponentUpdate(nextProps, nextState){
return true;
//I tried also:
// if (nextProps.url !== this.state.url |
// nextProps.lang !== this.state.lang)
// return true;
}
render() {
const Text=languages[this.languageSelect()];
return (
<div className="test-transition">
{console.log(this.state.lang)}
{console.log(this.state.url)}
{console.log(Text["p1_3_"+String(this.state.url)])}
<div className={`pic${this.state.url}`}>
{Text["p1_3_"+String(this.state.url)]}
</div>
</div>
);
}
}
Check if the new props is changes or not using any of the lifecycle event like componentDidUpdate or shouldComponentUpdate or you can also try componentWillReceiveProps (not recommended since its deprecated)
componentDidUpdate()
{
/*Check for prev props condition*/
this.forceUpdate(); /*this method renders the component*/
}

Why won't child components rerender without using getDerivedStateFromProps?

I cannot figure out why when my selectedEvent successfully changes via setState that my child components do not re-render with the updated state. In the child if I use getDerivedStateFromProps() I can get itto work, but I don't think that should be necessary. I'm missing something.
class App extends Component {
constructor(props) {
super(props);
this.state = {
selectedEvent: '',
eventsData: EventsData,
langData: LangData,
}
this.changeStoryEvent = this.changeStoryEvent.bind(this);
};
changeStoryEvent(event) {
let newEvent = event.target.getAttribute('data-event-key');
if(newEvent != null) {
console.log("new event", newEvent)
this.setState({
selectedEvent: newEvent
})
}
}
render() {
console.log("select", this.state.selectedEvent)
return (
<div>
<div className="header">
<Header />
</div>
<div className="sidebar">
<Sidebar
changeEvent={this.changeStoryEvent}
eventsData={this.state.eventsData}
selectedEvent={this.state.selectedEvent}
/>
</div>
<div className="mainbar">
app event = {this.state.selectedEvent} <br />
<Mainbar
selectedEvent={this.state.selectedEvent}
eventsData={this.state.eventsData}
langData={this.state.langData}
/>
</div>
</div>
)
}
}
class Mainbar extends React.Component {
constructor(props) {
super()
this.state = {
selectedEvent: props.selectedEvent,
changeEventType: props.changeEventType,
eventsData: props.eventsData,
langData: props.langData
}
this.changeEventType = this.changeEventType.bind(this)
}
changeEventType(event) {
}
render() {
console.log('mainstate', this.state)
let eventType = '';
if (this.state.selectedEvent !== '') {
eventType = this.state.eventsData[this.state.selectedEvent].type
}
if (eventType === '') {
eventType = 'action'
}
return (
<div className="mainbar">
mainevent = {this.state.selectedEvent}
<StoryEvent
eventType={eventType}
selectedEvent={this.state.selectedEvent}
changeEventType={this.changeEventType}
eventsData={this.state.eventsData}
langData={this.state.langData}
/>
{this.state.langData.events[this.state.selectedEvent]}
</div>
)
}
}
I see the selectedEvent correctly updated in class App, but not in Mainbar. It apparently does not receive the updated state of selectedEvent.
in you child component, you are copying selectedEvent from props to state, then using this.state.selectedEvent in render.
The constructor is only called once, so you won't update the state of you child later on when the prop change.
Use directly this.props.selectedEvent in the render method

React.js child state not re-rendering even after calling setState?

I have an app with one child component that I would like to re-render when setState updates the bookInput in the parent's state. I am using axios to request info from google's book api. For some reason, even though the state is updating, the child is not re-rendering. Please help if you can! Thank you!
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = {
bookInput: 'ender',
bookSubmitted: 'initial'
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleSubmitEmpty = this.handleSubmitEmpty.bind(this);
}
handleChange(e) {
this.setState({bookInput: e.target.value});
console.log(this.state.bookInput);
//this.setState({bookSubmitted: false});
}
handleSubmit(e) {
e.preventDefault();
//this.setState({bookSubmitted: true})
const name = this.state.bookInput;
this.setState({bookInput: name});
console.log(this.state);
this.setState({bookSubmitted: 'userSub'});
}
handleSubmitEmpty(e) {
alert('please enter an item to search for');
e.preventDefault();
}
render() {
return (
<div className="App">
<header className = "App-header">
<h1>Book Search App</h1>
</header>
<form className = "form-style" onSubmit = {this.state.bookInput ? this.handleSubmit: this.handleSubmitEmpty}>
<label>
<input type="text" className = "input-style"
value = {this.state.bookInput} onChange = {this.handleChange}>
</input>
</label>
<button type="submit">search books</button>
</form>
{/* <Book bookInput = {this.state.bookInput}/> */}
{/*this.state.bookSubmitted && <Book bookInput = {this.state.bookInput}/>*/}
{
(this.state.bookSubmitted === 'initial' || this.state.bookSubmitted === 'userSub') &&
<Book bookInput = {this.state.bookInput}/>
}
</div>
);
}
}
export default App;
class Book extends Component {
constructor(props) {
super(props);
this.state = {
//bookInput2: "ender",
bookTitles: [],
bookExample: '',
isLoading: false
}
this.bookClick = this.bookClick.bind(this);
}
bookClick(book) {
console.log(book);
console.log(book.volumeInfo.infoLink);
const bookURL = book.volumeInfo.infoLink;
window.open(bookURL);
}
componentDidMount() {
//this.setState({ isLoading: true });
this.setState({isLoading: true});
axios.get(`https://www.googleapis.com/books/v1/volumes?q=${this.props.bookInput}`)
.then((response) => {
const bookExample1 = response.data.items;
console.log(bookExample1);
this.setState({bookTitles: bookExample1, isLoading: false});
})
.catch((error) => {
console.error('ERROR!', error);
this.setState({isLoading: false});
});
}
render() {
return (
<div>
{ this.state.bookTitles ? (
<div>
<h2>book list</h2>
{<ul className = 'list-style'>
{this.state.isLoading &&
(<div>
loading book list
</div>)
}
{this.state.bookTitles.map(book => (
<li key={book.id}>
<span className = 'book-details book-title' onClick = {() => this.bookClick(book)}> {book.volumeInfo.title}</span>
<br/>
{book.volumeInfo.imageLinks &&
<img src = {book.volumeInfo.imageLinks.thumbnail}/>
}
{ book.volumeInfo.description &&
<span className = 'book-details'>{book.volumeInfo.description}</span>
}
<br/>
<span className = 'book-details'>Categories {book.volumeInfo.categories}</span>
</li>
))}
</ul>}
</div>) :
(<p>sorry, that search did not return anything</p>)}
</div>
);
}
}
May be you are looking for something similar to this?
https://stackblitz.com/edit/react-snoqkt?file=index.js
The above code can be simplified more and organized but it gives you some idea.
Main changes in the code.
Changed Api call from componentDidMount lifecycle event to a new method named getInitialdata which is called in handleSubmit.
getInitialdata(name){
axios.get(`https://www.googleapis.com/books/v1/volumes?q=${name}`)
.then((response) => {
const bookExample1 = response.data.items;
console.log(bookExample1);
this.setState({bookTitles: bookExample1, isLoading: false, bookSubmitted: 'userSub'});
})
.catch((error) => {
console.error('ERROR!', error);
this.setState({isLoading: false, bookSubmitted: 'userSub'});
});
}
Changed the way how Child component is used.
<Book bookTitles={this.state.bookTitles} isLoading={this.state.isLoading}/>
Issue with your code is you are making an API call in your component's didMount method. This lifecycle event will be invoked only when the component is mounted. Not when it is updated.
When you enter some input in your textbox and click on "Search books", componentDidMount event doesnt fire. And this is the reason why API calls are not happening from the second time.
More on the lifecycle events at https://reactjs.org/docs/react-component.html#componentdidmount
I've taken your code and extrapolated it into this sandbox. Just as you said, your parent component state is updating as it should, but the problem is that the child component doesn't change its state.
A state change will always trigger a re-render in React. The only problem is, your child component is managing it's own state, which isn't directly changing. Instead, it's just receiving new props again and again, but not doing anything with them.
If you look at your code for the <Book /> component, you only modify its state on componentDidMount, which only happens once. If you'd like to programmatically make it update, you can do one of two things.
Remove state from the child component, and make it rely entirely on props, so that it stays in sync with the parent
Use the componentDidUpdate lifecycle method (docs) to choose when to change the state of the child (which will trigger the re-render)

React: Update Component in Array

I have a React component that contains an array of child components. The parent retrieves data from a service and stores it in state. It passes an item from the data array to each child component via props.
The child component includes functionality that updates a value in its data item. When it does this, it fires an event, passing the updated item back to the parent. The parent creates a new state array, including the updated item.
Simplified code below.
This all works fine, and the update array is processed in the parent's render method. However, the child components are never re-rendered, so the updated property remains at its previous value.
How can I get the relevant child component to display the updated status?
class SearchView extends Component {
pageSize = 20;
constructor(props) {
super(props);
this.state = {
searchTerm: this.props.searchTerm,
results: []
};
}
getResults = (page) => {
const from = (page - 1) * this.pageSize;
searchActions.termSearch(this.state.searchTerm, from, this.pageSize).then(response => {
const results = response.SearchResultViews;
this.setState({
results: results
});
});
}
componentDidMount() {
this.getResults(1);
}
refresh(result){
const results = this.state.results.map(r => {
return (r.Id === result.Id) ? result : r;
});
this.setState({
results: results
});
}
render() {
let items = [];
if (this.state.results.length > 0) {
items = this.state.results.map((result, i) => {
return <SearchItem key={i} result={result} onStatusUpdate={(r) => this.refresh(r)}></SearchItem>;
});
}
return (
<div className="r-search-result">
<Row className='clearfix scroller'>
<div className='r-container-row results'>
{ items }
</div>
</Row>
</div>
);
}
}
class SearchItem extends Component {
constructor(props) {
super(props);
}
updateStatus(newValue) {
resourceActions.updateStatus(newValue);
//Bubble an event to the Parent to refresh the result and view
if (props.onStatusUpdate) {
searchActions.get(props.result.Id).then((result) => {
props.onStatusUpdate(result);
});
}
}
render() {
return (
<a href={this.props.result.link}>
<span className="column icon-column">{this.props.result.imageUrl}</span>
<span className="column title-column">{this.props.result.titleLink}</span>
<span className="column status-column">{this.props.result.status}</span>
<span className="column button-column"><button onClick={() => this.UpdateStatus(5)}></button></span>
</a>
);
}
}
Edit
In my actual (non-simplified) app, the child component transforms the props it has been passed in the ComponentDidMount() method, and it sets values in state; the render method binds the markup against state, not props. After putting a breakpoint in the child's Render() method as suggested by #Vishal in the comments, I can see that the updated data is received by the child, but since the state hasn't been updated, the component doesn't display the updated data.
The question then is, how best to update the component state without causing an infinite render loop?
In the end, I solved the problem by transforming the properties into state for the child component's in componentWillUpdate(), as well as the componentDidMount() method. As illustrated in the code below:
componentDidMount() {
if (this.props.result) {
this.prepareRender();
}
}
componentWillUpdate(nextProps, nextState) {
if (nextProps.result.status!== this.props.result.status) {
this.prepareRender();
}
}
prepareRender() {
//simplified
this.setState({
imageUrl: this.props.result.imageUrl,
titleLink: this.props.result.titleLink,
status: this.props.result.status
});
}
render() {
return (
<a href={this.props.result.link}>
<span className="column icon-column">{this.state.imageUrl}</span>
<span className="column title-column">{this.state.titleLink}</span>
<span className="column status-column">{this.state.status}</span>
<span className="column button-column"><button onClick={() => this.UpdateStatus(5)}></button></span>
</a>
);
}
UPDATE
In React 16.3 the componentWillUpdate() method is deprecated. This solution should use the new getDerivedStateFromProps() lifecycle method, as explained here: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html.

ReactJs - Function in Render doesn't Show Up

**
Update: This questions has an answer that worked. It is important to
note that even though you have a return statement in your function
called within render(), it is still important to wrap the entire loop
in a parent "return" in order for it to render properly on state
change. This is a different common issue where state is not updated
properly.
I have the following ClientList component, which shows a list of customers retrieved from database.
Below in the Render() function, i am calling the showList function which will display a list once this.props.clientList Reducer is populated.
Problem is... if I were to call the showList codes directly inside the Render() method, it will show.
IF i were to put it in a showList function, and call {this.showList} it doesn't shows up in the render.
I have the screen shot of the console as well, showing that the list is populated already.
Is this method disallowed? I see many tutorials teaching us to do this but it's not working for me. What are the restrictions to use this method to return the codes for render?
class ClientList extends Component {
constructor(props) {
super(props);
this.state = {
clientId : ''
}
this.getClientList = this.getClientList.bind(this);
this.showList = this.showList.bind(this);
console.log('initializing', this.props);
}
componentDidMount(){
this.getClientList();
}
getClientList() {
if (this.props.actions) {
this.props.actions.getClientList(); //This is an ajax action to retrieve from Api
}
}
showList() {
//If i put all the codes below directly in Render, it will show.
console.log('props from showList', this.props.clientList);
this.props.clientList && Object.keys(this.props.clientList).reverse().map((index,key) => {
return (
<div key={key}>
<div><a onClick={() => this.showProfileBox(this.props.clientList[index].customerId)}>Name: {this.props.clientList[index].firstname} {this.props.clientList[index].lastname}</a><span className="pull-right"><Link to={"/client/" + this.props.clientList[index].customerId}>Edit</Link></span></div>
</div>
);
})
}
render() {
console.log('rendering', this.props);
return (
<div>
<Col xs={12} md={8}>
<h1>Client List</h1>
{ this.showList() } // <= This function doesn't print
</Col>
</div>
)
}
}
function mapStateToProps(state) {
return {
clientList: state.clientList,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(clientActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ClientList);
Your should return the value from showList() method. As of now you were returning the value for map method, but not for the entire showList() method. Thats y it is painting nothing in the page
`
showList() {
return (
//removed unnecessary {}
this.props.clientList && Object.keys(this.props.clientList).reverse().map((index,key) => {
return (
<div key={key}>
<div><a onClick={() => this.showProfileBox(this.props.clientList[index].customerId)}>Name: {this.props.clientList[index].firstname} {this.props.clientList[index].lastname}</a><span className="pull-right"><Link to={"/client/" + this.props.clientList[index].customerId}>Edit</Link></span></div>
</div>
);
})
);
}
`
You don't need to bind showList in the constructor.
Remove it and you should be fine.
Also, as #JayabalajiJ pointed out, you need to return something out of showList otherwise you won't see the final result.
class ClientList extends React.Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
console.log('click')
}
showList() {
return <button onClick={this.handleClick}>From showList</button>
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click-me</button>
{this.showList()}
</div>
)
}
}
ReactDOM.render(
<ClientList />,
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>

Resources