3
votes

I am using the react-modal npm package and wrapping it in my own component so I can re-use some of the behaviour and styling throughout my application.

What I want is to be able to encapsulate all the input modal dialog behaviour in a separate component and just insert it as a React tag in any page I want to use this dialog behaviour.

The problem I have is that I am not sure of the correct way to control the dialog open state. Currently I am maintaining this as a boolean property on the parent control state, and passing it through as a prop to the Dialog component, along with callbacks for close() and ok().

The problem is when the callbacks are executed when the user clicks the ok or close buttons, it then tries to call this.setState({ dialogOpen: true }) (or false) and "this" no longer seems to refer to the parent control, I'm guessing because it was executed from the dialog, so "this" now refers to the input dialog.

Can someone please suggest how I am implementing this incorrectly?

This is the error I get

TypeError: this.setState is not a function ./src/GridContent.tsx.GridContent.handleCloseModal [as closeDialog] C:/src/SignoffGui/src/GridContent.tsx:241 238 | } 239 | 240 | private handleCloseModal () {

241 | this.setState({ dialogOpen: false }); 242 | } 243 | 244 | private getActionButtonClass(expected:boolean, expression:boolean)

My parent control

interface IGridContentState { 
  dialogOpen: boolean
}

class GridContent extends React.Component<{},IGridContentState> {

  constructor(props: any) {
    super(props);    

    this.state = { dialogOpen: false};
    this.handleOpenModal = this.handleOpenModal.bind(this);
  }

  public render() {
    return (          
      <div className="Flex-Subcontainer" style={{margin:10}}>   
        <InputDialog 
          title="Please enter a reason for rejecting"
          dialogOpen={this.state.dialogOpen}
          submitText={this.handleRejectText}
          closeDialog={this.handleCloseModal}
        />     
     <button onClick={(e) => this.doReject()}>Reject</button>             
     </div>
  }

  private doReject()
  {
    if (this.gridApi.getSelectedNodes().length > 0)
    {
      this.handleOpenModal();
    }
  }

  private handleRejectText(enteredText: string)
  {
    this.props.signoffReject(this.getSelectedEntries(), enteredText);
    this.gridApi.refreshCells();
  }

  private handleOpenModal () {
    this.setState({ dialogOpen: true });
  }

  private handleCloseModal () {
    this.setState({ dialogOpen: false });
  }    
}

And my dialog control

import * as React from 'react';
import * as ReactModal from 'react-modal';
import '../App.css';

interface IInputDialogProps 
{
  title: string,
  dialogOpen: boolean,
  submitText : (enteredText:string) => void,
  closeDialog : () => void
}

interface IInputDialogState 
{
  enteredText: string
}

class InputDialog extends React.Component<IInputDialogProps, IInputDialogState> {
  private input:React.RefObject<HTMLInputElement> = React.createRef();

  constructor(props: IInputDialogProps) {
    super(props);    

    this.state = {  enteredText: ""};

    this.handleCloseModal = this.handleCloseModal.bind(this);
  }

  public render() {
    return (
      <ReactModal 
        isOpen={this.props.dialogOpen}
        // contentLabel="Example Modal"
        // className="Modal"
        // tslint:disable
        onAfterOpen={() => this.input.current.focus()}
        overlayClassName="Overlay"
        shouldCloseOnEsc={true}
        shouldReturnFocusAfterClose={true}
        role="dialog"
        onRequestClose={this.handleCloseModal}
        shouldCloseOnOverlayClick={false}
        ariaHideApp={false}
        // tslint:disable
        parentSelector={() => document.body}> 
          <div className="Modal-Container"> 
              <div style={{flex:0.4}}>
                <div className="Panel-header-left">
                  {this.props.title}
                </div>
              </div>
              <div style={{flex:0.6}}>
                <form onSubmit={(e) => this.processOkClicked()}>
                  <input ref={this.input} type="textbox" name="ModalInput" value={this.state.enteredText} onChange={ (e) => this.handleTextChanged(e)} />
                </form>
              </div>
              <div>
                <div className="Panel-header-right">
                  <button className="Action-Button" onClick={(e) => this.handleCloseModal()}>Cancel</button>
                  <button className="Action-Button" onClick={(e) => this.processOkClicked()}>OK</button>
                </div>
              </div>          
          </div>
      </ReactModal>
    );
  }

  private handleCloseModal () {
    this.props.closeDialog();
  }  

  private handleTextChanged(e:React.ChangeEvent<HTMLInputElement>) {
    this.setState({ enteredText: e.target.value});
  }

  private processOkClicked () {
    if (this.state.enteredText === "") return;
    this.props.closeDialog();
    this.props.submitText(this.state.enteredText);
  }
}

export default InputDialog;
1

1 Answers

0
votes

In your parent component, you did bind handleOpenModal but you didn't bind the handleCloseModal function before passing it down. Try this:

public render() {
    return (          
      <div className="Flex-Subcontainer" style={{margin:10}}>   
        <InputDialog 
          title="Please enter a reason for rejecting"
          dialogOpen={this.state.dialogOpen}
          submitText={this.handleRejectText}
          closeDialog={this.handleCloseModal.bind(this)} //<---- here, add a bind() 
        />     
     <button onClick={(e) => this.doReject()}>Reject</button>             
     </div>
  }

or this:

constructor(props: any) {
    super(props);    

    this.state = { dialogOpen: false};
    this.handleOpenModal = this.handleOpenModal.bind(this);
    this.handleCloseModal = this.handleCloseModal.bind(this); //<---- here, add a bind() 
  }

Hoping this will help ;)