I have a React component (Albums) which render() method returns one of two possible components, depending on its state. One of these components (AlbumsTable) is called with two props parameters, classes and albums. The second props parameter is an array obtained by an ajax using Axios in a method (getData) that updates Albums state causing its re-rendering.
Due to Ajax asynchronic nature, Albums renders twice, the first time with this.albums = [], and the second one (caused by getData() state update) with this.albums equal to the result of the ajax.
The problem is that I can trace that AlbumsTable.constructor() is called only once (the first time Albums renders), when this.albums equals []. So the second time Albums renders, when this.albums equals the result of the ajax, this content is not sent as props to AlbumsTable, causing the result of the ajax not to be displayed never.
This is the code of my components:
import React, { Component } from 'react';
import Cookies from 'js-cookie';
import axios from 'axios';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
const styles = theme => ({
root: {
width: '100%',
marginTop: theme.spacing.unit * 3,
overflowX: 'auto',
},
table: {
minWidth: 700,
},
});
class AlbumsTable extends Component {
constructor(props) {
super(props);
this.state = {
classes: props.classes,
}
if(props.albums === undefined){
this.albums = [];
} else {
this.albums = props.albums;
}
}
render() {
return (
<Paper className={this.state.classes.root}>
<Table className={this.state.classes.table}>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell align="right">Name</TableCell>
<TableCell align="right">Photos</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.albums.map(album => {
return (
<TableRow key={album.id}>
<TableCell component="th" scope="row">
{album.name}
</TableCell>
<TableCell align="right">{"photos"}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Paper>
);
}
}
class AlbumDetail extends Component {
render() {
return(<p>Album Detail Comming Soon...</p>);
}
}
class Albums extends Component {
constructor(props) {
super(props);
this.state = {
classes: props.classes,
mode: 'table',
albums: [],
};
this.albums = [];
this.getData();
}
getData() {
const axios = require('axios');
var userToken = Cookies.get('token');
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
axios.get('http://127.0.0.1:8000/es/api/albums/',
{
headers: {
Authorization: userToken,
}
}
)
.then(function (response) {
console.log(response);
this.albums = response.data.results;
this.setState({albums: this.state.albums + 1});
}.bind(this))
.catch(function (error) {
console.log(error);
return(null);
})
}
setData(albums) {
this.setState({albums: albums});
}
render() {
if(this.state.mode === 'table'){
return (<AlbumsTable classes={this.state.classes} albums={this.albums} />);
} else {
return (<AlbumDetail />);
}
}
}
Albums.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Albums);
I guess I am missing or missunderstanding something about components rendering.
if you think your problem better, you can realize that you don’t need to save the albums array as a variable into the class component, would be better to check if your albums property is defined else you should return an empty array, in that way the component will work as expected.
render(){
const albums = this.props.albums || [];
return (
<Paper className={this.state.classes.root}>
<Table className={this.state.classes.table}>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell align="right">Name</TableCell>
<TableCell align="right">Photos</TableCell>
</TableRow>
</TableHead>
<TableBody>
{albums.map(album => {
return (
<TableRow key={album.id}>
<TableCell component="th" scope="row">
{album.name}
</TableCell>
<TableCell align="right">{"photos"}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Paper>
);
}
Re-rendering component won't cause it to be created once again. It will be updated instead, so constructor will not fire twice. This is a standard behaviour. You should use static getDerivedStateFromProps method to update state in response to props change. You can find more informations here: https://reactjs.org/docs/react-component.html#the-component-lifecycle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With