Tuesday, November 7, 2017

Setting a component state outside of the component results in error

Leave a Comment

Building a modal component that opens up a bootstrap modal from any part of the app then sets custom states for that component outside of it. It works fine but i always just get this error once i open the modal and I cant seem to figure out why:

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.` Doesnt really break anything but error keeps showing up.

My code:

layout.js

import React from "react"; import {Link} from 'react-router'; import NotificationSystem from 'react-notification-system';  import AppHeader from "#/ui/header/AppHeader"; import AppFooter from "#/ui/footer/AppFooter";  import Modal from "#/ui/modals/modal/Modal";  import "@/main.scss"; import './layout.scss';   export default class Layout extends React.Component {     constructor(props) {         super(props);     }      componentDidMount() {         app.notify.clear = this.refs.notificationSystem.clearNotifications;         app.notify = this.refs.notificationSystem.addNotification;         app.modal = this.refs.modal.updateProps;     }      render() {         return (             <div class="app">                 <div class="header">                     <AppHeader page={this.props.location.pathname.replace('/', '')}/>                 </div>                 <div class="body">                     {this.props.children}                 </div>                 <div class="footer">                     <AppFooter />                 </div>                  <NotificationSystem ref="notificationSystem" style={false} />                 <Modal ref="modal" />             </div>          );     }; } 

Modal.js

import React from "react"; import ReactDOM from 'react-dom';  import SVGInline from "react-svg-inline"; import {closeSvg} from '#/utils/Svg';  export default class Modal extends React.Component {     constructor(props) {         super(props);          this.state = {             showHeader: true,             showFooter: false,             title: "",             size: '',             className: '',             id: '',             footerContent: null,             showSubmitBtn: true,             showCancelBtn: true,             cancelBtnText: "Cancel",             successBtnText: "Save Changes",             onModalClose: () => {},             showModal: false,             html: () => {}         }          this.updateProps = this.updateProps.bind(this);         this.hideModal = this.hideModal.bind(this);     }      componentWillMount() {         var self = this;          var $modal = $(ReactDOM.findDOMNode(this));     }      componentDidUpdate(prevProps, prevState) {         if(this.state.showModal) {             $('body').addClass('modal-open');         } else {             $('body').removeClass('modal-open');         }     }      componentWillUnmount() {         // $('body').removeClass("modal-open");     }      componentWillReceiveProps(nextProps) {         console.log(nextProps);     }      updateProps(args) {         let merged = {...this.state, ...args};         this.setState(merged);     }      hideModal() {         this.setState({             showModal: false         });          this.state.onModalClose();     }      buildFooter() {         if(this.props.footerContent) {             return (                 <div class="content">                     {this.props.footerContent}                 </div>             )         } else if(this.props.showCancelBtn && this.props.showSubmitBtn) {             return (                 <div class="buttons">                     <button type="button" class="btn btn-default" data-dismiss="modal" onClick={this.props.onModalClose}>{this.props.cancelBtnText}</button>                     <button type="button" class="btn btn-success">{this.props.successBtnText}</button>                 </div>             );         } else if(this.props.showCancelBtn) {             return (<button type="button" class="btn btn-default" data-dismiss="modal" onClick={this.props.onModalClose}>Close</button>);         } else if(this.props.showSubmitBtn) {             return (<button type="button" class="btn btn-success">Save changes</button>);         }     }      render() {         let {             id,             className,             onModalClose,             size,             showHeader,             title,             children,             showFooter,             showModal,             html         } = this.state;          return (             <div class={`modal-wrapper`} >                 {                     showModal ?                         <div class={`modal fade in ${className}`} role="dialog">                             <div class="bg" ></div>                             <div class={`modal-dialog ${size}`}>                                 <div class="modal-content">                                      { showHeader ?                                         <div class="modal-header">                                             <button type="button" class="close" data-dismiss="modal">                                                 <SVGInline svg={closeSvg} />                                             </button>                                             <h4 class="modal-title">{ title }</h4>                                         </div> : '' }                                       <div class="modal-body" >                                         {html()}                                     </div>                                      {  showFooter ?                                         <div class="modal-footer">                                             { this.buildFooter() }                                         </div> : ''                                     }                                  </div>                             </div>                         </div>                     : ''                 }             </div>         );     } } 

SelectDefaultImage.js

import React from "react"; import sass from "./selectdefaultimage.scss"; import FullScreenImageModal from "#/ui/modals/fullscreenimagemodal/FullScreenImageModal";  export default class SelectDefaultImage extends React.Component {     constructor() {         super();          this.state = {             showModal: false,             imgUrl: false,         }     }      showImageModal(image) {         this.setState({             showModal: true,             imgUrl: image         });     }      hideImageModal() {         this.setState({             showModal: false,             imgUrl: false         })     }      onSelectImageClick(e, image) {         $('.select-image-widget .active').removeClass('active');         $(e.target).parent().addClass('active');          // this.props.selectedImage(image)     }      render() {         let {listingManager, images, selectedImage} = this.props;         let {imgUrl} = this.state;          return (             <div class="content">                 <div class="row">                     <div class="col-sm-12">                         <label class="control-label" for="description">Select an Image</label>                     </div>                 </div>                  <div class="row">                     <div class="col-sm-12">                         <div class="select-image-widget">                             {                                 images.map((image, idx) => {                                     return (                                         <div class="selecter" key={idx}>                                             <div class="img" style={{backgroundImage: `url(${listingManager.LISTINGS_PATH + image})` }} onClick={(e) => { this.onSelectImageClick(e, image) }}></div>                                             <i class="fa fa-search-plus" aria-hidden="true" onClick={()=> {this.showImageModal(image)}}></i>                                         </div>                                     )                                 })                             }                         </div>                     </div>                 </div>                 {                     this.state.showModal ?                         app.modal({                             showModal: true,                             className: "fullscreen-image-modal",                             size: "modal-lg",                             html: () => {                                 return (<img src={listingManager.LISTINGS_PATH + imgUrl} />);                             }                         })                     : ''                 }             </div>         )     } } 

1 Answers

Answers 1

The reason for the error is most likely that in SelectDefaultImage, you call app.modal from within the render method, and app.modal is this.refs.modal.updateProps, which does a setState. If you put the app.modal call in showImageModal, I expect the error to go away. However, setting the state of a another component by means of refs and globals is a bit of a React antipattern, so I would recommend to do some refactoring and use props to pass the data.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment