0

I have a table of accounts that contains an edit option. When the edit option is clicked, an account edit modal dialog is displayed. The user has the option to close or cancel the edit by clicking the "X" in the top right corner or clicking a Close button. When the modal closes there are state properties that i would like to clear, both dialog properties and parent component properties. The dialog properties are updated without any issues but i receive this error when the parent properties are attempted to be updated:

Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.

This is the parent component:

export class Accounts extends Component {

    constructor(props, context) {
        super(props);
        this.state = {
            loading: true,
            showEditUserModal: false,                
            userId: null,
            redraw: false,
        }
        this.handleAccountEdit = this.handleAccountEdit.bind(this);
    }

    handleAccountEdit(cell, row, rowIndex) {
        this.setState({ userId: cell },
            this.setState({ showEditUserModal: true }));
    }
    
    //This is parent function being called from dialog close Error occurs here
    handleAccountEditClose() {
        this.setState({ userId: null, showEditUserModal: false, redraw: true });
    }

    render() {
        return (
            <div className='container-fluid'>
                {this.state.showEditUserModal ?
                    <AccountEditModal userId={this.state.userId}
                        parentCloseAccountEditModal={() => this.handleAccountEditClose()}></AccountEditModal>
            </div>
            )
        }

AccountEditModal:

export default class AccountEditModal extends React.Component {
    constructor(props, context) {
        super(props);
        this.state = {
            uId: null,
            userName: '',
            showModal: true,
        }        
    }

    handleClose = (e) => {
        this.setState({ showModal: false, uId: null, userName: '' });
    }
    
    render() {        
        return (            
                <div >
                    <Modal show={this.state.showModal} onHide={() => { this.handleClose(); this.props.parentCloseAccountEditModal() }} centered >
                        <Modal.Header closeButton>
                            <Modal.Title>Account Edit</Modal.Title>
                        </Modal.Header>
                        <Modal.Body>
                            <div className="row">                                    
                                <div className="row pad-top float-right">
                                    <div className='col-md-2 my-auto'>
                                        <span id='btnCloseAccountEdit' onClick={() => { this.handleClose(); this.props.parentCloseAccountEditModal() }}
                                            className='btn btn-success'>Close</span>
                                    </div>
                                </div>
                            </div>
                        </Modal.Body>
                    </Modal>
                </div>
        )
    }

How can i update the parent component properties without getting this error?

The suggested solution does not call a parent component function. I changed the handleClose to a lambda in the AccountEditModal but still receive the same error.

user2370664
  • 381
  • 5
  • 8
  • 30
  • Does this answer your question? [ReactJS: Warning: setState(...): Cannot update during an existing state transition](https://stackoverflow.com/questions/37387351/reactjs-warning-setstate-cannot-update-during-an-existing-state-transiti). If not, there are plenty of other questions/answers on Stack Overflow. – emerson.marini Sep 29 '20 at 14:43
  • How are implementing the Modal component? – lissettdm Oct 05 '20 at 12:44
  • your code is quite broken, what are you trying to do here? `this.setState({ userId: cell }, this.setState({ showEditUserModal: true }));` please update your question with a correct version of your code – diedu Oct 05 '20 at 16:39
  • Can you try batching handleAccountEdit setState in one call like `this.setState({ userId: cell, showEditUserModal: true })`. – noumanniazi Oct 12 '20 at 10:25

2 Answers2

2

I am nit sure what you are doing different than I am but based on your code and explanation I created this demo application and it is working as expected without any errors. May be you might want to compare it to your application and check what is different in your app.

Find working codesandbox here.

Accounts.js

import React, { Component } from "react";
import { AccountEditModal } from "./AccountEditModal";

export class Accounts extends Component {
  constructor(props, context) {
    super(props);
    this.state = {
      loading: true,
      showEditUserModal: false,
      userId: null,
      redraw: false
    };
    this.handleAccountEdit = this.handleAccountEdit.bind(this);
  }

  handleAccountEdit(cell) {
    this.setState({ userId: cell }, this.setState({ showEditUserModal: true }));
  }

  //This is parent function being called from dialog close Error occurs here
  handleAccountEditClose() {
    this.setState({ userId: null, showEditUserModal: false, redraw: true });
  }

  render() {
    return (
      <div className="container-fluid">
        {this.state.showEditUserModal ? (
          <AccountEditModal
            userId={this.state.userId}
            parentCloseAccountEditModal={() => this.handleAccountEditClose()}
          ></AccountEditModal>
        ) : (
          <table>
            {this.props.users.map((uId) => {
              return (
                <tr>
                  <td>
                    <button onClick={() => this.handleAccountEdit(uId)}>
                      {uId}
                    </button>
                  </td>
                </tr>
              );
            })}
          </table>
        )}
      </div>
    );
  }
}

AccountEditModal.js

import React, { Component } from "react";
import { Modal } from "react-bootstrap";

export class AccountEditModal extends Component {
  constructor(props, context) {
    super(props);
    this.state = {
      uId: null,
      userName: "",
      showModal: true
    };
  }

  handleClose = (e) => {
    this.setState({ showModal: false, uId: null, userName: "" });
  };

  render() {
    return (
      <div>
        <Modal
          show={this.state.showModal}
          onHide={() => {
            this.handleClose();
            this.props.parentCloseAccountEditModal();
          }}
          centered
        >
          <Modal.Header closeButton>
            <Modal.Title>Account Edit: {this.props.userId}</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <div className="row">
              <div className="row pad-top float-right">
                <div className="col-md-2 my-auto">
                  <span
                    id="btnCloseAccountEdit"
                    onClick={() => {
                      this.handleClose();
                      this.props.parentCloseAccountEditModal();
                    }}
                    className="btn btn-success"
                  >
                    Close
                  </span>
                </div>
              </div>
            </div>
          </Modal.Body>
        </Modal>
      </div>
    );
  }
}

I have added dummy application data in App.js:

import React, { useState } from "react";
import { Accounts } from "./Accounts";

import "./styles.css";

export default function App() {
  const [users] = useState([
    "User1",
    "User2",
    "User3",
    "User4",
    "User5",
    "User6",
    "User7",
    "User8",
    "User9",
    "User10"
  ]);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <Accounts users={users}></Accounts>
    </div>
  );
}
Dipen Shah
  • 25,562
  • 1
  • 32
  • 58
-1

One easy way to reset your component is to manage it with a key. See: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key

More precisely:

When a key changes, React will create a new component instance rather than update the current one.

So following this, you could for example use a timecode so that every time you call this modal window a new instance is created with default values. Like this:

<AccountEditModal 
    key={Date.now()} 
    userId={this.state.userId}
    parentCloseAccountEditModal={() => this.handleAccountEditClose()}> 
</AccountEditModal>

Then you don't need this function and this call: this.handleClose(); Simply call this one: this.props.parentCloseAccountEditModal() and next time you call the modal, you're going to have a new instance.

For normal components, it may introduce non wanted behavior, but for a modal window it's usually exactly the intent: each time you close/open it, you want a reset state, and you're not going to change the props except by opening it - since it's modal and so prevents interactions with the parent.

Julien Grégoire
  • 16,864
  • 4
  • 32
  • 57