Tomasz Waszczyk
Tomasz Waszczyk

Reputation: 3139

Get data from React props

How to get data from props and be sure that data arrived properly. My issue is that in my class component I receive alarms from props and then I want to render table with the alarms, props sets asynchronously and I want to be sure that the data arrived and that I will be able to set a state. Below is my not nice try to solve it, but does not work (both console.log shows empty array):

  componentDidMount() {
    this.setState({
      startDate: moment()
        .subtract(30, 'days')
        .startOf('day'),
      endDate: moment().endOf('day'),
      selectedSeverity: null,
      isChecked: true,
    });

    if(this.state.tableAlerts.length === 0){
      console.log('tableAlerts', this.state.tableAlerts)
      console.log('householdAlerts', this.props.householdAlerts)
      this.setState({ tableAlerts: this.props.householdAlerts })
    }
  }

The whole component:

import React, { useState } from 'react';
import T from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { moment } from 'react-moment';
import { t } from 'i18next';
import _ from 'lodash';
import { TimezoneLabel } from 'cloud-modules-user/build/TimezoneLabel';
import { Table, Radio } from 'semantic-ui-react';
import { RangeDatePicker } from 'cloud-modules-user/build/RangeDatePicker';
import { SeverityFilter } from 'cloud-modules-user/build/Alerts';
import { fetchHouseholdAlerts } from '../Redux/Household.actions';

const alertSeverityText = ({ severity }) =>
  severity < 3 ? 'error' : 'warning';

const alertsBySeverity = alerts => {
  const groupedAndCounted = _.countBy(alerts, alertSeverityText);
  return severity => groupedAndCounted[severity] || 0;
};

const alertSeverityColor = {
  error: '#e05567',
  warning: '#f9b233',
};

export class HouseholdAlertsPure extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      tableAlerts: props.householdAlerts
    }
  }

  static propTypes = {
    householdAlerts: T.arrayOf(T.object).isRequired,
  };

  componentDidMount(prevProps) {
    this.setState({
      startDate: moment()
        .subtract(30, 'days')
        .startOf('day'),
      endDate: moment().endOf('day'),
      selectedSeverity: null,
      isChecked: true,
    });

    console.log('prevprops', prevProps)

    // if(this.props.householdAlerts !== prevProps.householdAlerts){

    //   this.setState({ tableAlerts: this.props.householdAlerts })

    // }


    // if(this.state.tableAlerts.length === 0){
    //   console.log('tableAlerts', this.state.tableAlerts)
    //   console.log('householdAlerts', this.props.householdAlerts)
    //   this.setState({ tableAlerts: this.props.householdAlerts })
    // }
  }

  onChangeDate = e => {
    const startDate = moment(e.startDate).startOf('day');
    const endDate = moment(e.endDate).endOf('day');
    this.setState({ startDate, endDate });
    const {
      // eslint-disable-next-line no-shadow
      fetchHouseholdAlerts,
      match: { params },
    } = this.props;
    fetchHouseholdAlerts(params.deviceGroupId, startDate, endDate);
  };

  selectOngoingAlerts = (e, data) => {
    const { isChecked } = this.state;
    this.setState( {isChecked : !isChecked} );

    const { householdAlerts } = this.props;
    // check whether alarm is ongoing
    if(isChecked){
      // filter ongoing alerts
      let filteredHouseholdAlerts = householdAlerts.filter((alert) => alert.setTimestamp < alert.clearTimestamp)
      this.setState( {tableAlerts: filteredHouseholdAlerts} )
    }else{
      // show all alerts, without any filter
      this.setState({tableAlerts: householdAlerts});
    }
  }

  handleOnChangeSeverity = (e, selectedOption ) => {

    console.log('e', e)
    this.setState( {selectedSeverity: e.option} )
    console.log('selectedSeverity', this.state.selectedSeverity)

  };

  setAlertForTable = () => {
    // if(this.state.alertsInitialised == true) return;
    console.log('setAlertForTable')
    // this.setState ( {alertsInitialised: true} )    
  }

  render() {

    console.log('test', this.props.householdAlerts)
    const { startDate, endDate } = this.state;
    const datesInitialized = startDate && endDate;
    // The dates are not set until the component is mounted.
    if (!datesInitialized) return null;

    const { householdAlerts } = this.props;
    const labels = {
      from: t('householdAlertsTimeRangeFrom'),
      to: t('householdAlertsTimeRangeTo'),
    };
    const numberOfAlert = alertsBySeverity(householdAlerts);



    return (
      <div className="alert-table">
        <section className="alerts-buttons">
          <div className="ongoing-box">
            <Radio toggle className="ongoing-checkbox"
              label="Ongoing alerts"
              value={ !this.state.isChecked }
              onChange={this.selectOngoingAlerts}
            ></Radio>
          </div>
          <SeverityFilter className="severity--button"
            onChange={this.handleOnChangeSeverity}
            value={this.state.selectedSeverity}
            option={this.state.selectedSeverity || 'all'}
          ></SeverityFilter>

          { this.setAlertForTable() }

          <div className="time-range">{t('householdAlertsTimeRange')}</div>
          <RangeDatePicker
            id="rangeDateSelector"
            labels={labels}
            onChange={e => this.onChangeDate(e)}
            selectedDateRange={[startDate, endDate]}
            filterDate={date => moment() > date}
          />
        </section>
          <div className="alert-table--statuses">
            <div aria-label="error">
              <div
                className="alert-table--statuses-item"
                style={{ backgroundColor: alertSeverityColor.error }}
              />
              <span className="alert-table--statuses-item-number">
                {numberOfAlert('error')}
              </span>
            </div>
            <div aria-label="warning">
              <div
                className="alert-table--statuses-item"
                style={{ backgroundColor: alertSeverityColor.warning }}
              />
              <span className="alert-table--statuses-item-number">
                {numberOfAlert('warning')}
              </span>
            </div>
          </div>

        <Table sortable>
          <Table.Header>
            <Table.Row>
              <Table.HeaderCell style={{ width: '116px' }}>
                {t('householdAlertsSeverity')}
              </Table.HeaderCell>
              <Table.HeaderCell textAlign="left">
                {t('householdAlertsDevice')}
              </Table.HeaderCell>
              <Table.HeaderCell textAlign="left">
                {t('householdAlertsAlertName')}
              </Table.HeaderCell>
              <Table.HeaderCell textAlign="left">
                {t('householdAlertsAlertsMessage')}
              </Table.HeaderCell>
              <Table.HeaderCell textAlign="right">
                {t('householdAlertsTimestamp')}
              </Table.HeaderCell>
            </Table.Row>
          </Table.Header>
          <Table.Body>
            {householdAlerts && householdAlerts.length !== 0 ? (
              _.map(
                householdAlerts,
                ({
                  id,
                  severity,
                  deviceName,
                  name,
                  setMessage,
                  setTimestamp,
                }) => (
                  <Table.Row key={id}>
                    <Table.Cell
                      className="alert-table--column-severity"
                      textAlign="right"
                    >
                      <div
                        className="alert-table--column-severity__status"
                        style={{
                          backgroundColor:
                            alertSeverityColor[alertSeverityText({ severity })],
                        }}
                      />
                    </Table.Cell>
                    <Table.Cell
                      className="alert-table--column-device"
                      textAlign="left"
                    >
                      {deviceName}
                    </Table.Cell>
                    <Table.Cell
                      className="alert-table--column-alert"
                      textAlign="left"
                    >
                      {name}
                    </Table.Cell>
                    <Table.Cell
                      className="alert-table--column-message"
                      textAlign="left"
                    >
                      {setMessage}
                    </Table.Cell>
                    <Table.Cell
                      className="alert-table--column-timestamp"
                      textAlign="right"
                    >
                      {moment(setTimestamp).format('DD-MM-YYYY HH:mm')}
                    </Table.Cell>
                  </Table.Row>
                ),
              )
            ) : (
              <Table.Row>
                <Table.Cell colSpan={7}>
                  {t('householdAlertsMessageNoAlerts')}
                </Table.Cell>
              </Table.Row>
            )}
          </Table.Body>
        </Table>
        <section className="timezone-wrapper">
          <TimezoneLabel />
        </section>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  householdAlerts: state.HouseholdReducer.householdAlerts,
});

const mapDispatchToProps = { fetchHouseholdAlerts };

export default withRouter(
  connect(mapStateToProps, mapDispatchToProps)(HouseholdAlertsPure),
);

Upvotes: 0

Views: 1197

Answers (2)

Danko
Danko

Reputation: 1864

You should probably use componentDidUpdate. It runs on each state and prop change.

This is an example of how it can be used:

componentDidUpdate(prevProps) { 
  if(this.props.householdAlerts !== prevProps.householdAlerts) { 
    this.setState({ tableAlerts: this.props.householdAlerts }) 
  } 
}

Upvotes: 1

Hagai Harari
Hagai Harari

Reputation: 2877

It is bad practice to set initial values to state at the constructor, therefore use lifeCycles as componentDidMount or componentDidUpdate instead to manipulate the state.


 export class HouseholdAlertsPure extends React.Component {

 constructor(props) {
  super(props);
  // initial values for state
  this.state = {
  tableAlerts: [],
  startDate: null,
  endDate: null,
  selectedSeverity: null,
  isChecked: false      
 }
}

componentDidMount() {
 // values declared for state at component first load
 this.setState({
  startDate: moment()
    .subtract(30, 'days')
    .startOf('day'),
  endDate: moment().endOf('day'),
  selectedSeverity: null,
  isChecked: true,
  tableAlerts: this.props.householdAlerts
});

componentDidUpdate() {
// called whenever prop value changed
 if(this.props.householdAlerts !== this.state.tableAlerts) 
      this.setState({ tableAlerts: this.props.householdAlerts })
}

selectOngoingAlerts = (e, data) => {
 const { isChecked, tableAlerts } = this.state;
 this.setState( {isChecked : !isChecked} );
 // right now, { isChecked } still refer to construct abouve methond and prev value... 
// for more consist aproach, consider do the filtering logic direct at render without manipulate state
 if(!isChecked) { 
  this.setState( { tableAlerts: tableAlerts.filter((alert) => alert.setTimestamp < alert.clearTimestamp)} )
 } else { this.setState({tableAlerts}) }
}
 ...rest class...
}

Now at render, { this.state.tableAlerts } should contain the correct information

Upvotes: 1

Related Questions