0

I have a YearFilter component:

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Dropdown } from 'semantic-ui-react';
import {
  changeYear
} from '../../actions/menuFiltersActions';
import makeDropdownOptions from '../../lib/makeDropdownOptions';

export class YearFilter extends React.Component {
  constructor(props) {
    super(props);
    this.handleChangeYear = this.handleChangeYear.bind(this)
  }

  handleChangeYear(year) {
    console.log(year)
    this.props.changeYear(year)
    this.props.getClientActuals()
    // this.props.testSelectedYear()

    console.log(this.props.years.selectedYear)
  }

  render() {
    return (
      <div id="YearFilter">
        <span>Year</span>
        <Dropdown
          // fluid
          selection
          loading={!this.props.years.data.length}
          options={makeDropdownOptions(this.props.years.data)}
          value={this.props.years.selectedYear}
          onChange={(e, { value }) => this.handleChangeYear(value)}
        />
      </div>
    );
  }
}

YearFilter.propTypes = {
  years: PropTypes.exact({
    data: PropTypes.array,
    selectedYear: PropTypes.number
  }).isRequired,
  changeYear: PropTypes.func.isRequired,
  getClientActuals: PropTypes.func.isRequired,

};

export default connect(
  store => ({
    years: store.menuFilters.years
  }),
  {
    changeYear
  }
)(YearFilter);

I am using this to update the menuFilters.years.selectedYear property in the Redux store whenever the user selects a year from the dropdown.

The parent component is the following:

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Header, Button, Icon, Loader} from 'semantic-ui-react';
import {
  changeYear
} from '../../actions/menuFiltersActions';
import {
  saveSalesHouseForecastSpendData
} from '../../actions/forecastSpendDataActions';
import './TabDataEntry.css';
import DataEntryFiltersSidebar from '../DataEntryFiltersSidebar/DataEntryFiltersSidebar';
import YearFilter from '../YearFilter/YearFilter';
import PivotTable from '../PivotTable/PivotTable';
import PivotTableAPI from '../../api/PivotTableAPI';
import processForecastSpendData from '../../lib/processForecastSpendData';
import { SALESHOUSE_LEVEL, MONTHS } from '../../constants';


export class TabDataEntry extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isSavingChanges: false,
      showNotification: false,
      showPivotTable: false
    }

    this.getClientActuals = this.getClientActuals.bind(this);
    this.getClientActualsFilters = this.getClientActualsFilters.bind(this);
    this.saveForecasts = this.saveForecasts.bind(this);
    this.collectChangedForecasts = this.collectChangedForecasts.bind(this);
    this.testSelectedYear = this.testSelectedYear.bind(this);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.menuFilters.years.data !== this.props.menuFilters.years.data){
      this.props.changeYear(this.props.menuFilters.years.data[0]);
    }
  }

  getClientActualsFilters(extraParams) {
    const filters = {
      year: this.props.menuFilters.years.selectedYear,
      client_id: this.props.menuFilters.brands.selectedBrand,
      ...extraParams
    };
    // console.log(filters)

    if (this.props.menuFilters.media.selectedMedia) {
      filters.media_id = this.props.menuFilters.media.selectedMedia;
    }

    if (this.props.menuFilters.media.selectedBooking) {
      filters.booking_id = this.props.menuFilters.media.selectedBooking;
    }

    return filters;
  }

  testSelectedYear(){
    console.log(this.props.menuFilters.years.selectedYear)
  }

  async getClientActuals() {
    try {
      const clientActuals = await PivotTableAPI.getClientActuals(this.getClientActualsFilters());
      this.props.saveSalesHouseForecastSpendData(
        processForecastSpendData(clientActuals, { level: SALESHOUSE_LEVEL })
      );
      this.setState({showPivotTable: true})
    } catch (e) {console.log(e)}
  }


  async saveForecasts() {
    this.setState({ isSavingChanges: true })
    let forecast = this.collectChangedForecasts(this.props.forecastSpendData);

    try {
      const response = await PivotTableAPI.saveForecasts({
        media_id: this.props.menuFilters.media.selectedMedia,
        booking_id: this.props.menuFilters.media.selectedBooking,
        advertiser_id: this.props.menuFilters.brands.selectedBrand,
        year: this.props.menuFilters.years.selectedYear,
        forecast_period: [],
        forecast
      });

      this.notify(response ? 'positive' : 'negative');
    } catch (e) { console.log(e) }
    this.setState({ isSavingChanges: false })
  }


  collectChangedForecasts(data) {
    const childrenKeys = {
      Saleshouse: 'folios',
      Folio: 'suppliers'
    };

    let changedForecasts = Object.values(data).reduce((changedForecasts, saleshouse) => {
      for (let month in MONTHS) {
        // check if forecast has changed and if this element is the level at which data was entered
        if (saleshouse[MONTHS[month]]
        && saleshouse[MONTHS[month]].changed
        && saleshouse[MONTHS[month]].forecastLevel === saleshouse.level) {
          changedForecasts.push({
            month: parseInt(month) + 1,
            sales_house_id: saleshouse.SaleshouseId,
            folio_id: saleshouse.FolioId,
            supplier_id: saleshouse.SupplierId,
            forecast_level: saleshouse[MONTHS[month]].forecastLevel,
            metric: saleshouse[MONTHS[month]].forecast
          });
        }
      }

      let children = saleshouse[ childrenKeys[saleshouse.level] ]
      if (children && Object.values(children).length) {
        changedForecasts.push(...this.collectChangedForecasts(children))
      }

      return changedForecasts;
    }, [])

    return changedForecasts
  }

  notify(state) {
    this.setState({ showNotification: state })
    setTimeout(() => this.setState({ showNotification: false }), 5000)
  }


  render() {
    return (
        <div id="TabDataEntry">
          <DataEntryFiltersSidebar
            getClientActuals={this.getClientActuals}
          />


        <div id="main-container">
          <div className="header-section">
            <Header as="h2">Investment forecasts</Header>

            <p>
              Select a Client / Advertiser to view and confirm the current share deal spends.
              <Loader as="span" active={this.state.isSavingChanges} inline size="mini" inverted />
              { this.state.showNotification &&
                <span className={`notification ${this.state.showNotification}`}><Icon name="checkmark"/>Saved</span>
              }
            </p>
          </div>



          {this.state.showPivotTable && 
            <>
              <YearFilter
                getClientActuals={this.getClientActuals}
                testSelectedYear={this.testSelectedYear}
              />
              <div id="overflowing-container">
                  <PivotTable
                    getClientActualsFilters={this.getClientActualsFilters}
                  />

                  <div id="bottom-buttons">
                    <div>
                      <Button
                        primary
                        onClick={this.saveForecasts}
                        disabled={!Object.keys(this.props.forecastSpendData).length}
                      >
                        <Loader active={this.state.isSavingChanges} inline size="mini" inverted /> Save Changes
                      </Button>
                      <Button
                        basic
                        onClick={this.getClientActuals}
                        disabled={!Object.keys(this.props.forecastSpendData).length}
                      >
                        Cancel
                      </Button>
                    </div>
                    <Button basic disabled><Icon name='balance scale'/>Rebalance Calculator</Button>
                    <Button basic disabled><Icon name='file excel outline'/>Export as XLS</Button>
                  </div>
              </div>
            </>
          }
        </div>
      </div>
    );
  }
}

TabDataEntry.propTypes = {
  menuFilters: PropTypes.objectOf(PropTypes.object).isRequired,
  forecastSpendData: PropTypes.objectOf(PropTypes.object).isRequired,
  saveSalesHouseForecastSpendData: PropTypes.func.isRequired,
  changeYear: PropTypes.func.isRequired
};

export default connect(
  store => ({
    menuFilters: store.menuFilters,
    forecastSpendData: store.forecastSpendData
  }),
  { saveSalesHouseForecastSpendData, changeYear }
)(TabDataEntry);

The problem I am having is that when YearFilter updates the menuFilters.years.selectedYear in the Redux store, the parent component (TabDataEntry) still retrieves the previously set value.

What am I doing wrong? Appreciate any help.

Mr B
  • 3,980
  • 9
  • 48
  • 74

2 Answers2

0

You are trying to access

this.props.changeYear(year)

in the YearFilter component, but I don't see it passed from the parent component:

  <YearFilter
    getClientActuals={this.getClientActuals}
    testSelectedYear={this.testSelectedYear}
  />

If changeYear is an action creator, just use:

changeYear(year);

in the method handleChangeYear of component YearFilter.

html_programmer
  • 18,126
  • 18
  • 85
  • 158
0

I realised that the cause of my issue is that I am assuming the Redux store is immediately updated when I invoke the changeYear action. However this is not the case, the Redux store is not updated instantaneously.

In YearFilter component, I am calling this.props.changeYear(year) immediately followed by this.props.getClientActuals(). When getClientActuals is fired it is still working with Redux store that has not been updated hence my issue.

I realised that I need to fire a callback function upon updating the Redux store in order to ensure that the getClientActuals() is working with the latest Redux store.

Using the answer to this question I adopted the life cyle method componentDidUpdate() in the YearFilter and implemented it like so:

if (this.props.years.selectedYear !== prevProps.years.selectedYear) {
  this.props.getClientActuals()
}
Mr B
  • 3,980
  • 9
  • 48
  • 74