Polaris-react: Modal and Application Communication

Created on 24 Jul 2018  Ā·  19Comments  Ā·  Source: Shopify/polaris-react

How i can access to iframe in modal ?
i can't find it in document
as seem your document very poor.

App Bridge

All 19 comments

I mean how i can access to modal content like that
https://help.shopify.com/en/api/embedded-apps/embedded-app-sdk/features#modal-and-application-communication

Can you explain more what you’re trying to achieve, in terms of your app’s functionality or the user experience?

Hi,
i want i can access to Content in iframe (React js) like https://help.shopify.com/en/api/embedded-apps/embedded-app-sdk/features#modal-and-application-communication

When you’re using Polaris react, you need to provide the Modal component’s src prop with a URL from your own application. Since the URL is a page you control, the Polaris API doesn’t have the same concept of reaching into the modal in the same way as the EASDK.

If you can describe what you’re trying to achieve in more detail, I can probably provide more specific help.

You mean i can't access to iframe when it opened ?
_ShopifyApp.Modal.window().$("body")_

I looked into this some more, and I understand that this is painful right now. Especially with React, it’s not ideal to be reaching into the DOM. For this and other reasons we’ve decided not to expose the iframe content in Polaris the way the EASDK does. This has made things more difficult in the short term, even for our own developers (we use the EASDK in our own apps too). We’re exploring ways to make this better right now.

For the time being, I can outline some of the solutions you can try, most of which we’ve used ourselves. In these points I’ll talk about the modal iframe (which holds the page you point to with the Modal’s src prop) and the embedded iframe (which holds the current page in your embedded app).

  • Localstorage. If you need to communicate between the modal iframe and your embedded app, such as when the modal contains a form, you can use localstorage to store the form values. In your embedded iframe, you can retrieve the values from localstorage when the modal is closed.
  • React portals. You can create a single page in your app that acts as the src for all modals. When this page is loaded, you can postmessage to the embedded iframe to signal the modal iframe’s DOM is loaded. Then, you can use a portal to ā€œtransportā€ some content from your embedded iframe into the modal. This way the embedded iframe and the modal content can share state.
  • Using the normal EASDK along with Polaris. You can choose not to pass your apiKey and shopOrigin to the AppProvider, and instead use the EASDK itself in combination with Polaris React.

None of these suggestions is ideal, I know. Please bear with us as we work to make this better, and let me know if you have any more questions or comments.

Oh, sad ;(
I think that postmessage via iftame is the best of way in this case
Thanks for your ideal

Thanks for the workaround ideas @ry5n.

While I can understand why you mightn't want to reach into DOM from a technical perspective, I eventually came to the conclusion it wasn't really worth the effort to work around the limitations.

So I ended up abandoning the EASDK altogether. Which is fine, I guess it did improve the developer experience a bit.

I just wonder whether it runs counter to the goals of a cohesive design system in the first place if in all likelihood the app I am working on will no longer sit within the Shopify admin.

The technical issues aside, I believe stand-alone apps _can_ still benefit from using the same design language so that merchants feel at home. At the same time, non-embedded apps should take care to differentiate themselves (using color, a logo, or the way the application frame is structured) so that merchants are aware they’re a different product.

Shogun does a good job of this IMO (screenshot).

That’s not to minimize the challenges we’ve discusses in this issue. If you liked the idea of creating an embedded experience, we hate to see you not able to achieve it, and as I said before we’re working on ways to make this much less painful.

Hi Ryan. I totally agree with you, and Shogun is a great example.

I am not abandoning the design language, just the idea that it should be embedded. Not embedding the app provides me with an opportunity to add a bit more brand personality and will make for a possibly nicer experience. Whether that's ideal from Shopify's perspective is a different question though.

No complaints from me in any case, and thanks for sharing the alternative approaches.

Oh, totally. We support both embedded and non-embedded apps, and there are good reasons to go either route, depending on your needs. Branding is one good reason for choosing a stand-alone app.

Whether that's ideal from Shopify's perspective is a different question though

@ptcampbell this is a really great question. The short answer for embedded vs not embedded is whichever you feel is in the best interest of Shopify merchants. If you can deliver a better merchant experience by not embedding I would say go for it.

So I ended up abandoning the EASDK altogether.

This can be okay and other apps do this, but we don’t want embedded apps to rebuild their own app loading state, flash messages, modal, breadcrumbs and other UI features which the EASDK offers. If you need to use any of these UI elements we prefer that is done with the EASDK.

I mean how i can access to modal content

@khiconit back to your original question, you would have to include the app.js in addition to using the Polaris React components to access ShopifyApp.Modal.window() and ShopifyApp.window(). We know this is not ideal and will be working on making all features of the EASDK available via Polaris React.

Here is a workaround as suggested by @ry5n.
I could successfully open a modal from an embedded app using react portals,
A quick guide below, any thoughts and improvements on this solution is highly appreciated:

  • First, define a global variable to contain your app host:
    window.SHOPIFY_APP_HOST = 'xxxxxx.ngrok.io'

  • Then, define a route in your application (in this case "/modal"), this will handle all your modals.

  • Render this component when this route is called, something like <Route exact path="/modal" component={Modal} /> :

import React from 'react';

export default class Modal extends React.Component {
  componentDidMount(){
    window.parent['app-iframe'].postMessage({modalPortal:true},window.SHOPIFY_APP_HOST)
    document.body.style.backgroundColor = "#FFF"
  }
  render(){
    return <div id="ModalPortal_Content"></div>
  }
}
  • Save the following component somewhere in your app, we'll call it "ModalPortal":
import React from 'react';
import ReactDOM from 'react-dom'
import { Modal } from '@shopify/polaris';

export default class ModalPortal extends React.Component {
  constructor(props){
    super(props)
    this.messageHandler = this.messageHandler.bind(this)
    this.onClose = this.onClose.bind(this)
    this.state = {
      portalTarget : null
    }
  }
  messageHandler(event){
    if(!window.SHOPIFY_APP_HOST || event.origin !== window.SHOPIFY_APP_HOST) return;
    if(event.data.hasOwnProperty('modalPortal') && event.data.modalPortal && !this.state.portalTarget && this.props.open){

      const portalTarget = event.source.window.document.getElementById('ModalPortal_Content')
      if(portalTarget){
        this.setState({
          portalTarget : portalTarget
        })
      }  
    }
  }
  componentDidMount(){
    if (window.addEventListener) {
      window.addEventListener("message", this.messageHandler, false);
      } else {
      window.attachEvent("onmessage", this.messageHandler);
    }
  }
  onClose(){
    this.setState({
      portalTarget : null,
    })
    this.props.onClose()
  }
  render(){
    const {onClose,...props} = this.props
    return(
        <React.Fragment>
        <Modal {...props} onClose={this.onClose} src={window.SHOPIFY_APP_HOST+"/modal"}>
        </Modal>
        {(this.props.open && this.state.portalTarget) &&
          <ModalPortalRemote target={this.state.portalTarget}>
            {this.props.children}
          </ModalPortalRemote>
        }
        </React.Fragment>
      )
  }
}

class ModalPortalRemote extends React.Component {
  render(){
      return ReactDOM.createPortal(
        this.props.children,
        this.props.target
      );

  }
}
  • Now, whenever you need a modal, just import "ModalPortal" which you have created in previous step instead of polaris Modal, and pass it all your props and children:
<ModalPortal
          open={active}
          onClose={this.handleChange}
          title="Reach more shoppers with Instagram product tags"
          primaryAction={{
            content: 'Add Instagram',
            onAction: this.handleChange,
          }}
          secondaryActions={[
            {
              content: 'Learn more',
              onAction: this.handleChange,
            },
          ]}
        >
          <Modal.Section>
            <TextContainer>
              <p>
                Use Instagram posts to share your products with millions of
                people. Let shoppers buy from your store without leaving
                Instagram.
              </p>
            </TextContainer>
          </Modal.Section>
</ModalPortal>

@rmtngh stellar work! Will have to give it a try.

@tmlayton I agree with your points and I'm actually experimenting with offering both an embedded vs. standalone experience. Will have to see how that goes. 😃

@ptcampbell I’m very interested to hear what you discover.

Maybe there could be an option to display it within the iframe of the embedded app if the developer doesn't want to use the src attribute on the embedded modal. This way we could use children in the component and not have to worry about a complex work-around.

In my experience stuff that breaks the standard way of doing things can be a real pain to maintain further down the road when you've forgotten how the complex thing that you did as a work-around functions. The way that it works now seems really opinionated and not at all intuitive.

I've seen several related issue reports when trying to research this and it seems a lot of people are running into the same issue repeatedly.

I like that there is a way to completely take over the screen like a native part of Shopify but I just wish there was an option for apps that don't really need that capability.

Personally, I'm going to create the functionality I want, without a nice looking modal, just to make this easier to maintain.

Oh, sad ;(
I think that postmessage via iftame is the best of way in this case
Thanks for your ideal

What you are trying to do is communicate between child and parent component. This is more of a ReactJS question because you want to for example display a form in the modal right? If so then you need to pass a function as a prop. You can use the primaryAction prop that's built into the Modal component, it's meant for this very reason, at least the only real option for an embedded app.

https://github.com/Shopify/polaris-react/issues/876

Thanks everyone for your participation in this issue. Closing for now as the original question has been answered. As of v5.0.0 we will be removing App Bridge from Polaris and moving those components to their own @shopify/app-bridge-react library.

More information will come with docs, etc, but for those immediately curious read https://github.com/Shopify/polaris-react/issues/814#issuecomment-493249756

Was this page helpful?
0 / 5 - 0 ratings