0

I'm extremely new to ReactJS. Currently I was ask to handle a project that my previous colleague did, which he left and no one knows how exactly it work and I can't ask anyone.
Anyway, I was asked to add a confirm dialog when user attempt to redirect but (s)he changed some values on the form.
The structure of the page is kinda complicated. AFAIK, it is an parent with different children and each children has their own form. Parent itself doesn't have one. I managed to find out each children used a varaible 'pristine'(PropType.bool.isRequired) to determine if the form has been changed, and I also managed to retrieve the value of 'pristine' back to parent.
My Current Approach: Whenever a child's form has changed, it will call a function from parent, which will use setState to save the value of 'pristine'. When the user redirects, I noticed that it will trigger componentWillUnmount, so I planned to do the confirm dialog here. If the value in state is true, it will prompt the confirm dialog.
My Problem: The web browser's console keeps giving a warning:

Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.

Plus, the value in state is always one step behind.

From what I have been "scavenging" for the past two day, Since the child is in 'render', doing setState in 'render' will cause inifinite loop of 'render', which is not a good approach.

Here is the code related:
index.jsx

class PageEditProperty extends React.PureComponent {

constructor(props){
    super(props);

    this.SetCredentialChanged = this.SetCredentialChanged.bind(this);
    this.onUnload = this.onUnload.bind(this);
}
state = {
    deleteModalOpen: false,
    credentialsChanged: false,
  };

openDeleteModal = () => {
this.setState({ deleteModalOpen: true });
};

closeDeleteModal = () => {
this.setState({ deleteModalOpen: false });
};

SetCredentialChanged = (pristine) =>{
    this.setState({credentialsChanged: pristine},()=>{console.log(this.state.credentialsChanged);});
};

onUnload(){
  //console.log("Redirect");
  if(this.state.credentialsChanged){
    confirm("Hello");
  }
}

componentDidMount(){
  window.addEventListener("beforeunload", this.onUnload);
  //this.setState({credentialsChanged: false});
  console.log("Mount");
}

componentWillUnmount(){
    window.removeEventListener("beforeunload", this.onUnload);
    console.log("Unmount");
    console.log(this.state.credentialsChanged);
 }

render() {
const childProps = {
  propertyId: this.props.propertyId,
  propertyExists: this.props.propertyExists,
  clientPropertyId: this.props.clientPropertyId,
  beforeSubmit: this.props.createPropertyIfNeeded,
  setPendingRequest: this.props.setPendingRequest,
};
return (
  <div className="edit-property container">
    <div className="mdc-layout-grid">
      <div className="mdc-layout-grid__inner">
        <div className="mdc-layout-grid__cell--span-12">
          <PageHeader {...childProps} onDeleteClick={this.openDeleteModal} />
        </div>
        <div className="mdc-layout-grid__cell--span-6 mdc-layout-grid__cell--span-6-phone mdc-layout-grid__cell--span-8-tablet">
          <div className="mdc-elevation--z1 mdc-theme--background">
            <ThumbnailUploader {...childProps} />
          </div>
        </div>
        <div className="mdc-layout-grid__cell--span-6 mdc-layout-grid__cell--span-8-tablet">
          <div className="mdc-elevation--z1 mdc-theme--background">
            <PropertyInfoEdit {...childProps} onChangedCredentials={this.SetCredentialChanged}/>
          </div>
        </div>
        <div className="mdc-layout-grid__cell--span-4 mdc-layout-grid__cell--span-8-tablet">
          <div className="mdc-elevation--z1 mdc-theme--background">
            <TripodUploader {...childProps} />
          </div>
        </div>
        <div className="mdc-layout-grid__cell--span-8 mdc-layout-grid__cell--span-8-tablet">
          <div className="mdc-elevation--z1 mdc-theme--background">
            <PropertyLocationEdit {...childProps} />
          </div>
        </div>
        <div className="mdc-layout-grid__cell--span-12 mdc-layout-grid__cell--span-8-tablet">
          <div className="mdc-elevation--z1 mdc-theme--background">
            <ScenesListEdit {...childProps} />
          </div>
        </div>
      </div>
    </div>
    <Dialog
      open={this.state.deleteModalOpen}
      acceptLabel={this.props.t('Delete this content')}
      cancelLabel={this.props.t('Cancel')}
      acceptClassname="danger-bg"
      onAccept={this.props.deleteProperty}
      onClose={this.closeDeleteModal}
      title={this.props.t('Delete this content ?')}
    >
      {this.props.t('property-delete-text')}
    </Dialog>
  </div>
    );
  }
}

PropertyInfoEdit.jsx:

PropertyInfoEdit.propTypes = {
  pristine: PropTypes.bool.isRequired,
  submitting: PropTypes.bool.isRequired,
  valid: PropTypes.bool.isRequired,
  reset: PropTypes.func.isRequired,
  handleSubmit: PropTypes.func.isRequired,
    onChangedCredentials: PropTypes.func,
};
PropertyInfoEdit.defaultTypes = {
  pending: false,
};
function PropertyInfoEdit({
  onSubmit,
  handleSubmit,
  pristine,
  reset,
  submitting,
  valid,
  pending,
  categories,
  t,
  onChangedCredentials,
}) {

    function ChangeCredentials(e){
        {onChangedCredentials(e)};
    }

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="edit-property__form" onChange={onChangedCredentials(pristine)}>
      <div className="mdc-card__primary form-group">
        <Field name="nameEn" label={t('English name')} component={renderTextField} type="text"/>
        <Field name="nameCh" label={t('Chinese name')} component={renderTextField} type="text"/>
        <Field
          name="categoryId"
          label={t('Category')}
          component={renderSelectField}
          fullWidth
          metaText={false}
          options={categories.map(c => ({
            value: c.id,
            name:
              t('_locale') === 'zh-CN'
                ? c.nameZhCN
                : t('_locale') === 'zh-TW' ? c.nameZhTW : c.nameEn,
          }))}
        />
      </div>

      <div className="mdc-card__actions" style={{ justifyContent: 'center' }}>
        <div className="inline-button-group">
          <RaisedButton
            type="button"
            onClick={reset}
            disabled={pristine || submitting}
            className="margin-auto mdc-button--secondary"
          >
            {t('Reset')}
          </RaisedButton>
          <RaisedButton
            disabled={pristine || submitting || !valid}
            type="submit"
            className="margin-auto mdc-button--primary"
          >
            {(pending && <i className="material-icons spin2s">cached</i>) || t('Submit')}
          </RaisedButton>
        </div>
      </div>
    </form>
  );
}

// region HOC Declarations

/**
 * Injects the property basic information data in the component props
 * @type {ComponentDecorator}
 */
const injectInfoData = injectQuery(
  gql`
    query PropertyInfoQuery($id: String) {
      property(id: $id) {
        id
        nameCh
        nameEn
        categoryId
      }
    }
  `,
  {
    options: ({ propertyId, propertyExists }) => ({
      variables: { id: propertyId },
      fetchPolicy: propertyExists ? 'network-only' : 'cache-only',
    }),
    props: ({ data }) => ({
      property: data.property,
    }),
  },
);

/**
 * Injects mutation handler to change the basic informations data
 */
const PropertyInfoUpdateMutation = gql`
  mutation PropertyInfoUpdate($id: String!, $property: PropertyUpdate) {
    updateProperty(id: $id, property: $property, autoPublish: false) {
      id
      nameCh
      nameEn
      categoryId
      published
      isValid
    }
  }
`;

const injectInfoMutation = injectQuery(PropertyInfoUpdateMutation, {
  props: ({ ownProps, mutate }) => ({
    updateDBProperty: property =>
      mutate({
        variables: { id: ownProps.propertyId, property },
      }),
  }),
});

/**
 * Injects the categories
 * @type {ComponentDecorator}
 */
const injectCategories = injectQuery(
  gql`
    query PagePropertyEditCategoryQuery {
      categories(noFilter: true) {
        id
        nameEn
        nameZhCN
        nameZhTW
      }
    }
  `,
  {
    props: ({ data }) => ({
      categories: data.categories || [],
    }),
  },
);

const injectSubmitHandler = withBoundHandlers({
  onSubmit(data) {
    return this.props
      .beforeSubmit()
      .then(() => this.props.propertyExistsPromise)
      .then(() => this.props.updateDBProperty(data));
  },
});

/**
 * Create and wraps a reduxForm around our component
 * @type {function}
 */
const wrapsWithReduxForm = compose(
  withProps(props => ({
    form: `PropertyInfoForm#${props.clientPropertyId}`,
    initialValues: props.property
      ? {
          nameEn: props.property.nameEn || '',
          nameCh: props.property.nameCh || '',
          categoryId: props.property.categoryId,
        }
      : undefined,
  })),
  reduxForm({
    enableReinitialize: true,
    keepDirtyOnReinitialize: true,
    destroyOnUnmount: false,
  }),
);

// endregion HOC Declarations

// @type {React.Component}
const DecoratedPropertyInfoEdit = compose(
  injectInfoData,
  injectCategories,
  injectInfoMutation,
  wrapsWithReduxForm,
  makePromiseFor(
    // 1) the property must exist on the server and
    // 2) it must have loaded in this component (ex we have its id)
    props => props.propertyExists && props.property && props.property.id,
    promise => ({ propertyExistsPromise: promise }),
  ),
  injectSubmitHandler,
  translate(),
)(PropertyInfoEdit);
DecoratedPropertyInfoEdit.propTypes = {
  propertyId: PropTypes.string,
  clientPropertyId: PropTypes.string.isRequired,
  propertyExists: PropTypes.bool,
  beforeSubmit: PropTypes.func.isRequired,
  onChangedCredentials: PropTypes.func,
};

export default DecoratedPropertyInfoEdit;

I understand what I did wrong in here, but I have no idea how to fix it. So if there is anyone that can lend me a hand, that will be great because I have almost 0 knowledge with React/Redux.

Thank you so much for your help.

Dienamight
  • 21
  • 3
  • Have you tried this solution: https://stackoverflow.com/questions/37387351/reactjs-warning-setstate-cannot-update-during-an-existing-state-transiti#37387846 – blaz Sep 14 '17 at 07:23
  • I did, the result is that "pristine" returns Proxy instead of boolean – Dienamight Sep 14 '17 at 08:45

0 Answers0