Cannot resolve an issue with Meteor + React + Semantic UI

Hello everyone - i have been trying to understand what the issue is for past couple of days but now i give up - i need some expert help please.

My code is very simple - i have a modal box that i want to trigger using a button and i want to show “Loading…” when the subscription is being loaded and show “Wow” when it has loaded inside the modal box. I am using Semantic UI.

Please find my code below:

Booking = React.createClass({
	
	componentDidMount: function() {
		// Localize the selector
		var rootNode = ReactDOM.findDOMNode(this);

		// Payment Modal
		let paymentModal = $('.basic.app_payment_methods_modal.modal').modal();
		
		$('.app_review_button').click(function(e) {
			paymentModal.modal('show');
		});
		
	},


	render() {
		return (
			<div>
				<button className="app_review_button">Launch Modal</button>
				<PaymentMethods />
			</div>
		);
	}
	
});


PaymentMethods = React.createClass({
	
	mixins: [ReactMeteorData],

	getMeteorData() {
		var handle = Meteor.subscribe("userCreditCards", Meteor.userId() );

		return {
			dataReady: handle.ready(),
		};
	},

	componentDidMount: function() {
	},

	
	render() {
		
		return (
			<div className="ui basic modal app_payment_methods_modal">
				{this.data.dataReady ? <div>WOW</div> : <div>Loading...</div>}
			</div>
		);
	}
});

I receive this error in console:
Exception from Tracker recompute function: debug.js:41 Error: Invariant Violation: findComponentRoot(..., .0.0.0.0.1.0): Unable to find element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a <tbody> when using tables, nesting tags like <form>, <p>, or <a>, or using non-SVG elements in an <svg> parent. Try inspecting the child nodes of the element with React ID ``. at invariant (invariant.js:39) at Object.ReactMount.findComponentRoot (ReactMount.js:764) at Object.ReactMount.findReactNodeByID (ReactMount.js:679) at Object.getNode (ReactMount.js:162) at Object.ReactDOMIDOperations.dangerouslyProcessChildrenUpdates (ReactDOMIDOperations.js:83) at Object.wrapper [as processChildrenUpdates] (ReactPerf.js:66) at processQueue (ReactMultiChild.js:160) at ReactDOMComponent.ReactMultiChild.Mixin.updateChildren (ReactMultiChild.js:334) at ReactDOMComponent.Mixin._updateDOMChildren (ReactDOMComponent.js:871) at ReactDOMComponent.Mixin.updateComponent (ReactDOMComponent.js:700)

So I’m just getting into bringing semantic into Meteor, but check out the modal documentation. I think react is probably having an issue with how semantic is manipulating the dom.

Yes, semantic moves the Dom element that you initialize the modal on to a different part of the tree. React does not like that. You need to include the detatchable: false option when you initialize the modal to prevent this behavior.

Thanks Jlevy! - It actually does get rid of the error that way - however now the modal box is completely empty :neutral_face:

Also why does the error only appear for modal boxes that have Reactive data source associated with them (collections/sessions etc). ?

I dont get that error with static data.

I found a hacky fix to get over the problem (in addition to detachable: false) - it turns out i have to set the .pushable DIV “transform” css property to “inherit” and pusher DIV’s “position” to “static” (other wise the content doesn’t appear in the modal [but in the background])

body.undetached.dimmed {
	.ui.pushable {
		-webkit-transform: inherit;
		transform: inherit;
		& > .pusher {
			position: static;
		}
	}
}

I’m not sure exactly what’s causing this issue, but your code written in a way that I find a little bit difficult to reason about. It seems like you are using React, but haven’t quite internalized The React Way™.

  1. jQuery is something you generally want to avoid in React. With Semantic we can’t avoid it completely, but what I would suggest is making a reusable component that encapsulates the messy jQuery to make the modal work and presents a nice React-like API for the rest of your app to use it via props only. For example, your button handler is added directly to the DOM element. There is no need to do this. The React way is to pass a function as an onClick prop to your button component.

  2. If the modal is working with static data but not with dynamic data, that tells me there is some logic code and presentational code which needs to be separated. If you haven’t read this, I highly recommend it.

Here is what I use for my modals:

import React from 'react';

SemanticUIModal = React.createClass({
  propTypes: {
    //Whether or not the modal is open
    show: React.PropTypes.bool,

    //Callback functions that will be passed to the onApprove/onDeny events in the modal
    //These callback functions should: 1) Take care of any logic (saving results, resetting state), 2) close the modal,
    //and 3) return false to prevent the modal from closing itself.
    onSave: React.PropTypes.func,
    onCancel: React.PropTypes.func
  },

  //generate a uniqueId in so we open/close the right one if there are more than 1 modal in the DOM
  getInitialState(){
    return {
      modalId: _.uniqueId('modal-')
      };
  },

  //Default handlers for the modal OnApprove and OnDeny events.
  getDefaultProps(){
    return {
      onSave: () => {return false},
      onCancel: () => {return false}
    };
  },

  render(){
    return(
        <div className='ui modal' id={this.state.modalId}>
          {this.props.children}
        </div>
    );
  },


  componentDidMount(){
    //Initialize the modal with options. The modal should never be allowed to close itself.
    // The only thing that should close the modal is props.show changing to false
    $('#'+this.state.modalId).modal({
      detachable: false,
      closable: false,
      onApprove: this.props.onSave,
      onDeny: this.props.onCancel,
    });

    //If the initial render was with props.show = true, then show the modal
    if(this.props.show) {
      $('#'+this.state.modalId).modal('show');
    }
  },

  //if props.show changes, open/close the modal
  componentWillReceiveProps(nextProps){
    if(this.props.show != nextProps.show){
      if(nextProps.show){
        $('#'+this.state.modalId).modal('show');
      }else{
        $('#'+this.state.modalId).modal('hide');
      }
    }
  },

});

Then you just need to have a state element that controls if the modal is open and pass it down to the modal via ths show prop. You’ll write an onClick event handler that changes this state to true to open the modal, and pass that as an onClick prop to your button. The content of the modal is specified as a child elements of the modal. If you want to use Semantic’s onApprove or onDeny events, then you need to write event handlers for those and pass them down as props too. Remember to return false from these to prevent Semantic from closing the modal for you. You want to remain in control of the modal open/close state via props.

I hope that helps!

1 Like

Ah - Thanks for such elaborate example. I will try to do it your way :slightly_smiling:

One more question Jlevy if you would be kind enough to help me out - How do you chain modal boxes together (with your SemanticUIModal) ?

Like clicking one button to initiate the modal - then adding buttons inside of the ```{this.props.children}` to navigate to different modal boxes (kinda like steps with Next / Previous buttons)

Again, you need to try to change your mindset from the old ways :slightly_smiling:

I wouldn’t think of it as chaining different modal boxes together, but rather displaying multiple pages of content within the modal. Assuming you don’t want the modal to animate closed and then open again, I would implement that something like this:

  1. Instead of including DOM elements as children of the SemanticUIModal, create a single component that will be the child of the SemanticUIModal. This component’s render method will emit DOM. I’ll call this component MyModalContent.
  2. In MyModalContent, use state to store which page of the modal should be displayed. Use getInitialState() to specify the initial value when the modal is first mounted.
  3. In the MyModalContent render() method, use a switch statement or something similar to output the correct page of the modal based on the state value.
  4. Write button handlers which use setState to switch to the appropriate page.
  5. Use these handlers as onClick events for next/previous buttons.

You will also need a way to close the modal when you are done. For this, you will need to write the event handler in the component which opened the Modal (the component with the state element that controls whether the modal is open/closed). You can pass this function to MyModalContent as a prop so you can call it when the modal needs to close.

Thanks Jlevy,

I am still pretty new to “the React way” :slightly_smiling:

I actually did want to show the Modal box animations between each step (but for now i can live without it).

I am not fully sure how you recommended i should control the ‘state’ variable to maintain the steps. For now i am using a hacky method mentioned here http://stackoverflow.com/questions/31856712/update-component-state-from-outside-react-on-server-response

I have a parent component called ‘Booking’ in which i have a modal box (using your SementicUIModal component). As a child to the Modal box i have another component called ‘MyModalContent’ which has a switch that controls the layout. This switch uses the prop passed to it via the Root parent component (Booking) and then from the switching components i change the Booking state variables. Is this how you recommended it ?

Its working fine but i wanted to double check if its the recommended way.

Also i have three questions i tried searching on the internet but couldn’t find solution to:

  1. Is there a way return multiple DOM elements in render function - (i know the answer to this is No [according to github issues for react]) - but then i wanted to know how to use SementicUI properly because its CSS fails in many places where it expects a direct child element (instead of a wrapping element)?
  2. Is there a way to add transitions after when the state is changed (like perhaps fading content in and out) ?
  3. Is there a way to show open/close transitions with your SementicUI Modal component?

Thank you very much for your continued help!

I think there is a better way to control the state of which page is displayed, but let’s put that aside for a minute. I wasn’t thinking of the issue with SemanticUI and multiple children. I think that will require a little refactoring. Here is what I would suggest.

  • Instead of a MyModalContent component which is a child of SemanticUIModal, create a component MyModal to wrap the SemanticUIModal.
  • So now MyModal is a child of Booking, which passes a show prop to MyModal.
  • Booking defines a function to close the modal (by calling this.setState({showModal: false});. This function also needs to be passed to MyModal as a prop so that code within the modal can close the modal.
  • The state element that controls which page of the modal is displayed lives in MyModal.
  • MyModal's render method contains SemanticUIModal, with a child that looks like this {this.renderModalContent()}.
  • The renderModalContent() method contains the switch statement and returns the modal content.
  • Your buttons can have onClick handlers which can call setState() to change the page.

That’s the way I usually use the SemanticUIModal.

As for your questions:

  1. This is addressed above
  2. I’m not the best person to ask. I’ve done some very simple tasks with React Motion and I think that would work for you, but it might be overkill.
  3. The animations should work - at least they do for me without any additional effort.