golden-layout: Usage with @connect breaks in various ways.

Rendering any React components that use react-redux and the HOC @connect causes golden-layout to fail to render with an error along the lines of: "Uncaught Invariant Violation: Could not find “store” in either the context or props of “Connect(DropdownMenu)”. Either wrap the root component in a <Provider>, or explicitly pass “store” as a prop to “Connect(DropdownMenu)”.

Our extremely kludgy “fix” is to manually copy the store across to context like so:

   static contextTypes : React.ValidationMap<any> = {
        store: React.PropTypes.shape({
            subscribe: React.PropTypes.func.isRequired,
            dispatch: React.PropTypes.func.isRequired,
            getState: React.PropTypes.func.isRequired
        })
    }

    content.props = {store: (this.context as any).store};

and then later in our component to be rendered:

   static childContextTypes : React.ValidationMap<any> = {
        store: React.PropTypes.shape({
            subscribe: React.PropTypes.func.isRequired,
            dispatch: React.PropTypes.func.isRequired,
            getState: React.PropTypes.func.isRequired
        })
    }

    getChildContext() {
        return {store: this.props.store}
    }

While this solves the nominal case, as soon as I try to pop out a component, I am back in a world without my store:

:8060/dist/vendor.js:3324 Warning: Failed Context Types: Required child context `store.subscribe` was not specified in `TestItem`.warning @ :8060/dist/vendor.js:3324
:8060/dist/vendor.js:3324 Warning: Failed Context Types: Required context `store.subscribe` was not specified in `Connect(DropdownMenu)`. Check the render method of `QueryViewToolbar`.warning @ :8060/dist/vendor.js:3324
:8060/dist/vendor.js:37303 Uncaught TypeError: _this.store.getState is not a function

Halp!

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 22 (3 by maintainers)

Most upvoted comments

I have a fix for this problem. It originates in the usage of ReactDOM.render in the render method of ReactComponentHandler…

First I had to add an overload of the LayoutManager to accept a 3rd argument which would be a React component context…

lm.LayoutManager = function( config, container, reactContainer ) {
.....
	this.container = container;
	this.reactContainer = reactContainer
....

Next I had to change this method to not use ReactDOM.render and instead use ReactDOM.unstable_renderSubtreeIntoContainer…

		//this._reactComponent = ReactDOM.render( this._getReactComponent(), this._container.getElement()[ 0 ]);
		var reactContainer = this._container.layoutManager.reactContainer;
		this._reactComponent =  reactContainer ?  ReactDOM.unstable_renderSubtreeIntoContainer(reactContainer, this._getReactComponent(), this._container.getElement()[ 0 ]) : ReactDOM.render( this._getReactComponent(), this._container.getElement()[ 0 ]);
		this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function(){};
		this._reactComponent.componentWillUpdate = this._onUpdate.bind( this );
		if( this._container.getState() ) {
			this._reactComponent.setState( this._container.getState() );
		}
	},

I use a react Component Wrapper to handle bootstrapping of GL… usage is something like…

  componentDidMount() {
    .....
    var myLayout = new GoldenLayout( config, this.container, this);
      myLayout.registerComponent('dashboard', Dashboard)
      myLayout.init();
    }
    shouldComponentUpdate() {
       return false // we need to control this...
    }
    render() {
      return (
        <div  ref={c => this.container = c} style={{ height: '100%'}} />
      )
     }
  }

Since I am always using a Wrapper component to handle displaying GL. my example might be different than other use cases. Please try to incorporate this change since this will allow people to use Redux/ or anything that deals with context. Of course you guys can incorporate logic to check if there is a “reactContainer” and if so use the unstable_renderSubtreeIntoContainer or regular ReactDOM.render method…

Best, Marc

Still struggling to get this working. Anybody have a minimal CodePen blending redux, react-router, and golden-layout? @marc314 what’s the intended usage of “c” in your snippet?

can we flush this into a full redux and react-redux example? I’ve opened an issue for redux documentation: #280 . @evansb could you expand upon this idea:

My recommended approach would be to initialise golden layout and do the component registration after you prepare the redux store, and place the layout instance in a transient reducer.

I create my redux store in my index.jsx and then initialize goldenlayout & register components in a GoldenLayoutWrapper (React class) that gets passed the store, so I believe I am already doing the first part; what do you mean by placing the layout instance in a transient reducer?

@axelnormand where did you get GoldenLayoutPlugin from…I can’t find any reference to it in the source code using grep -nr "Plugin". Was this just a mistype? A codepen would be hugely appreciated.

@davidmcooper My intended use of “c” is how you are recommended to use ref’s since they deprecated string refs…

<MyComponent  ref="myRef" />
// or now
<MyComponent2 ref={c=> this.myRef = c } />

Usage now is this.myRef rather than this.refs.myRef…

One thing to add to whatever you are trying to do… Since you want to have complete control over the component which will handle the golden layout, you CANNOT have react update it…

shouldComponentUpdate() { return false; }

If I have some time I can try to whip up an example however my code example above I had to actually edit the code of golden-layout react to deal with the issue of it not getting React context which is what you are going to need to use Redux. I suggested they added my overloaded method into their code but I don’t think they have yet.

You can use higher order component to generalise geschaefteConstructor so that it wraps on any existing react component.

I think you will need connect as well.

Snippets from my project

function wrapComponent (Component: Component, store) {
  class Wrapped extends React.Component {
    render () {
      return (
        <Provider store={store}>
          <Component {...this.props} />
        </Provider>
      )
    }
  }
  return Wrapped
}

  R.forEach((component) => {
    const view = connect(component.stateToProps)(component.component)
    layout.registerComponent(component.name, wrapComponent(view, store))
  }, components)