Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React component to update child component

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.

like image 329
HuLu ViCa Avatar asked May 12 '26 05:05

HuLu ViCa


2 Answers

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>
    );
  }
like image 145
Juorder Gonzalez Avatar answered May 14 '26 19:05

Juorder Gonzalez


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

like image 28
Piotr Szlagura Avatar answered May 14 '26 20:05

Piotr Szlagura