Reactjs refresh parent after react modal popup changes data

mo_maat picture mo_maat · Aug 3, 2017 · Viewed 12.8k times · Source

I am using react modal to set up a pop up in on one of my components. I have a component that renders a div wrapped with react modal. My parent component renders the modal component on load with the isOpen set to false. Clicking a link on the parent sets isOpen to true and causes the modal popup to open.

I make changes to my data within the open popup and then save the changes and close the model when the close button is clicked.

I am using a redux set up with actions and reducers handling the data changes and state changes.

Conceptually, how would I update the parent to show the changes made from the popup? Shouldn't changes to my data using an action cause the store to regenerate and hence update the data in my components or do I have to explicitly "refresh" my store after saving? My issue is that right now when the popup closes it is not tirggering any kind of "refresh" on the DataView component.

Components below:

DataView component

import React from 'react';
import DataView from './MonthView.js';
import DataViewPopup from './MonthViewPopup.js';
import { connect } from 'react-redux';
import { getAction } from '../actions/actions.js';
import { getActionAll } from '../actions/actions.js';
import { getPopupData } from '../actions/actions.js';

class DataViewContainer extends React.Component {
  constructor() {
    super();

    this.popupCategory = undefined;
    this.popupMonth = undefined;

    this.state = {
      detailPopup : false,
      refreshView: false
    }

  this.handleAddYear = this.handleAddYear.bind(this);
  this.handleSubtractYear = this.handleSubtractYear.bind(this);
  this.handleGetDetail = this.handleGetDetail.bind(this);

  }

  componentDidMount() {
   this.props.getAction(2016);
   this.props.getActionAll(2016);
  }

  render() {

     return (
        <div className="row">
          <div className="col-sm-8">
              <MonthView transactions={this.props.storeData.storeData} selectedYear={this.props.storeData.selectedYear} onAddYear={this.handleAddYear} onSubtractYear={this.handleSubtractYear} onHandleGetDetail={this.handleGetDetail} />
          </div>
          <div className="col-sm-4">
              <MonthViewPopup modalActive={this.state.detailPopup} transactions={this.props.storePopupData.getPopupData} selectedYear={this.props.storeTransactions.selectedYear} category={this.popupCategory} month={this.popupMonth}/>
          </div>
        </div>
      ) 

  }

  handleGetDetail(category,month) {

    console.log("props inside handleGetDetail:  ", this.props);

    this.popupCategory = category;
    this.popupMonth = month;
    let popupYear = this.props.storeTransactions.selectedYear

    this.props.getPopupData(popupYear, month, category);

    this.setState({ detailPopup: true}, function () {});

  }

}


const mapStateToProps = (state) => ({
  storeData: state.storeData,
  storePopupData: state.storePopupData,
  storeDataAll: state.storeDataAll
});

export default connect(mapStateToProps, {getAction,getActionAll,getPopupData})(DataViewContainer);

DataViewPopup component

import React from 'react';
import Modal from 'react-modal';
import { connect } from 'react-redux';

import { saveAction } from '../actions/actions.js';
import { getActionAll } from '../actions/actions.js';

class DataViewPopup extends React.Component {

  constructor (props) {
    super(props)

    this.editedData = new Map(); 

    this.state = {
      modalIsOpen: false
    };

    this.openModal = this.openModal.bind(this);
    this.afterOpenModal = this.afterOpenModal.bind(this);
    this.closeModal = this.closeModal.bind(this);

    this.renderFilteredTransactions = this.renderFilteredTransactions.bind(this);

  }

 openModal() {
    this.setState({modalIsOpen: true});
  }

  afterOpenModal () {
    console.log("inside afterOpenModal");
  }

  closeModal () {

    this.props.saveTransactions(this.editedData);

    this.editedData = new Map();

    this.setState({  modalIsOpen: false }, function () {});

  }
  componentWillUnmount() {

    this.props.getDataAll(); // i tried this but it does not work because the component (modal popup) is still mounted, but just not visible

    //this.props.refreshParent();

  }


   componentWillReceiveProps(nextProps){

    if (nextProps.modalActive === true) {
         this.openModal();
         return;
      }

    }

    render () {
        return  <div>
            <Modal
            isOpen={this.state.modalIsOpen}
            //isOpen={this.modalActive}//not needed as is currently setup
            onAfterOpen={this.afterOpenModal}
            onRequestClose={this.closeModal}
            //style={customStyles}
            contentLabel="Example Modal"
            >

            <button onClick={this.closeModal}>close</button>

            {this.renderFilteredTransactions(this.props.data,this.props.category,this.props.month)};
            </Modal>
        </div>
    } 

    renderFilteredTransactions(trans,category,month){
        return <div>
            <table>
                <tbody className="mo-tblBody">
                    {trans && trans.map((data,index) =>
                        <tr key={data.transid}>
                            <td>{data.transid}</td>
                            <td>
                              {this.renderCategory(data.category,index,data.transid)}
                            </td>
                        </tr>
                    )}
                </tbody>
            </table>
        </div>

    }

    handleCategoryChange(value,transIndex,transId){

      let trans = JSON.parse(JSON.stringify(this.props.transactions));

      //add or updated transaction to this.editedTransactions based on whether or not the transid already exists
      trans.filter(item => item.transid === transId)
      .map(item => this.editedData.set(transId, 
                  Object.assign({}, item, { category: value })));

    }

}

const mapStateToProps = (state) => {
  return {storeDataAll: state.storeDataAll,
    storeSaveData: state.storeSaveData
  }
};

export default connect(mapStateToProps, {getDataAll,saveData})(DataViewPopup);

Answer

markerikson picture markerikson · Aug 3, 2017

I covered this kind of question in my recent post Practical Redux, Part 10: Managing Modals and Context Menus.

The basic approaches are:

  • Pass a callback function as a prop to the modal component (which will work, but if you're driving the modal display from Redux state, putting functions into the Redux state is not encouraged)
  • Create a "pre-built" action, pass that as a prop to the modal, and have the modal dispatch that action as a "return value" when it closes
  • Use a middleware like redux-promising-modals to track actions for opening and closing modals, and use the promises it returns to handle "return values" when modals are closed.