Fluentui: Layer: Preserve React context from parent to projected layer

Created on 10 Oct 2016  路  6Comments  路  Source: microsoft/fluentui

Scenario:
React application uses some library like react-router
The library initializes useful objects in the React Context for using in child controls.

I want to implement Dialog control and put some buttons in the dialog footer. The buttons will use Context to make redirection logic:

   onClick: function () {
        ...
        this.context.router.push({ pathname: to, query: query, hash: hash, state: state });
    },

Problem:

Unfortunately, the controls placed inside of Dialog control did not received this.context property from parent control. In this case, the this.context.router is undefined.

My temporary workaround is passing Context as property:

                    <DialogFooter>
                        <SaveButton {...this.props} Form={x} context={this.context}/>
                        <CancelButton {...this.props} Form={x} context={this.context}/>
                    </DialogFooter>

and calling the router through this.props.context.router.push({ pathname: to, query: query, hash: hash, state: state });

Probably, I did something wrong?

Type

Most helpful comment

I think this very well is a bug. The Layer component is not propagating the context to the projected layer component. So say that you have a <SmartPeoplePicker /> that internally accesses context. If you render it without Layer, you can access context. If you wrap it in a Layer (like what Dialog does), it suddenly can't access the context.

You can work around it by wrapping the Dialog (outside of the Layer), accessing context, and passing it into the smart child via props. Not great though.

All 6 comments

You need to define the context types on the components that require them, i.e.

class SaveButton extends React.Component {
  constructor(props, context) {
    this.router = context.router
    this.onClick = this.onClick.bind(this)
  }

  onClick() {
    this.router.push(/* ... */)
  }

  render() {
    // ...
  }
};
SaveButton.contextTypes = {
  router: React.PropTypes.object.isRequried
};

This has nothing to do with the components being placed "inside" the Dialog component (or any other component for that matter). Also make sure that the this inside your onClick handler is actually the component instance and not the button that was clicked (by binding it to this)

I think this very well is a bug. The Layer component is not propagating the context to the projected layer component. So say that you have a <SmartPeoplePicker /> that internally accesses context. If you render it without Layer, you can access context. If you wrap it in a Layer (like what Dialog does), it suddenly can't access the context.

You can work around it by wrapping the Dialog (outside of the Layer), accessing context, and passing it into the smart child via props. Not great though.

I had the same problem trying to use a Redux Form component (which needs a Redux store object from the context) inside a Dialog. In this case you can't pass the store as a prop, because Redux Form expects that it is on the context.

My workaround was to get the store from the context explicitly in a custom component (using the static contextTypes property of the component), inside the render method wrapping the Redux Form with a Provider component (part of the React Redux bindings) passing the store as a prop, and using the wrapped form inside the Dialog, that is returned from the render method of the custom component. Not great, but at least the workaround is concentrated in a single custom component.

@Solwo just curious since you're using react-router, are you using it together with the Nav component? Is there any guidance on how to use the Nav together with react-router? I'm trying to use the two together but I get full page reloads. I also can't use IndexRoute, etc.

CC/ @dzearing @kmees

I _think_ this is the same issue when using Yahoo's react-intl library: Uncaught Error: [React Intl] Could not find required 'intl' object. <IntlProvider> needs to exist in the component ancestry.

Will implement the suggested context work-around for now.

edit: also happens with react-router - both components work fine in normal use, but when implemented inside a Layer-based component (like a Panel) they fail in this way. Having inside a panel is a pretty common use-case. If this can't be resolved easily, perhaps some documentation to this effect could be produced?

Resolved, published later tonight.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

luisrudge picture luisrudge  路  3Comments

luisrudge picture luisrudge  路  3Comments

nekoya picture nekoya  路  3Comments

justinwilaby picture justinwilaby  路  3Comments

rickyp-ms picture rickyp-ms  路  3Comments