Polaris-react: this.context.easdk is undefined

Created on 25 May 2017  ·  19Comments  ·  Source: Shopify/polaris-react

Issue summary

I'm trying to run the example from https://github.com/Shopify/polaris/blob/master/documentation/Embedded%20apps.md to Access to further EASDK APIs

Actual behavior

this.context.easdk is undefined, I get the error "Uncaught TypeError: Cannot read property 'startLoading' of undefined"

Steps to Reproduce the Problem

  1. create app using create-react-app
  2. copy code from https://github.com/Shopify/polaris/blob/master/documentation/Embedded%20apps.md
  3. add apikey and test store url to code
  4. install dependencies
  5. build and deploy react app so that it runs on an iframe in teststore.myshopify.com

Specifications

  • Polaris version: "@shopify/polaris": "^1.0.3"
  • React version: "react": "^15.5.4",
  • Browser: Chrome
  • Operating System: macOS

Most helpful comment

Thank you for your reply.

However, I found the issue and it was my fault, sorry :smile:

I had set index.js as entry point of my app but both index.js and App.js had a render() function so both were rendering and that's why the console.log was being called twice.

All 19 comments

Thanks for the issue, @sebnun. You're definitely right, those docs are completely incorrect. I've added an issue for us to fix this internally ASAP. There are two big mistakes there:

  1. Instead of rendering an EmbeddedApp inside your component, you would render your component within an EmbeddedApp component (so, some component higher up does <EmbeddedApp><MyComponent /></EmbeddedApp>). This would make it so that this.context.easdk is actually defined inside MyComponent

  2. Instead of calling methods during render (<Button onClick={this.context.easdk.startLoading()}>Start loading</Button>), you would call those in a function you pass to onClick: <Button onClick={() => this.context.easdk.startLoading()}>Start loading</Button>

I'll update this issue once we've fixed those errors.

Note that React.PropTypes is deprecated as of React v15.5. To define the context in a child component and not have it error you'll have to include the prop-types library and do something like this:

import React from 'react';
import PropTypes from 'prop-types';

...

class MyComponent extends React.Component {
    static contextTypes = {
        easdk: PropTypes.object
    };

    ...

    componentWillMount() {
        console.log(this.context.easdk) // This should be defined now
    }
}

Question of my own: will the missing EASDK features be added soon? For example, what should we do about error messages? I can open a new issue if that would be better.

@dansundy what features do you feel are missing? I'll open an issue for the flash error, we must have missed that one.

@lemonmade primaryAction and secondaryActions in Page component inside EmbeddedApp not read prop "target: app". All actions generate with "target: new" in ShopifyBar if I use url prop

I see that transformers.ts don't pass this prop to EASDKButton:

return { label: action.content, href: action.url, message: action.onAction, style, } as EASDKButton;

@lemonmade Thanks. I think .flashError is the only one I need. I'll cross reference and report back if there are any others.

@lemonmade @hallenmaia I just want to add that we have the same problem with Page breadcrumbs (ie. no target: 'app').

@EvilLooney I have changed from url to onAction () in primary and secondary actions. Breadcrumbs does not have this option so I temporarily disabled them.

You can now pass {error: true} as the second argument to the flash message method to present the flash as an error.

Hi,

Regarding accessing the Embedded SDK object, I'm trying to display a flash error message in an action creator (so outside the component) if an API call failed in some rather unexpected way. Is there any way to get access to it globally?

There isn't really a way to do this, no. I'll open an issue for us to discuss the ability to pass in an instance of EASDK to pass in (which would let you use that for whatever else as well). For now, I would recommend having a component that grabs the easdk from context and assigns it somewhere global before it is needed as part of the action creator.

Hi @lemonmade

Was this issue fixed? I ran into a similar problem with my app, this is my code:

import React from 'react';
import PropTypes from 'prop-types';
import {render} from 'react-dom';
import {Button} from '@shopify/polaris';
import {EmbeddedApp} from '@shopify/polaris/embedded';

class MyApp extends React.Component {
  render() {
    console.log(this.context);
    return (
      <div>
        <Button onClick={() => this.context.easdk.stopLoading()}>TEST</Button>
      </div>
    );
  }
}

MyApp.contextTypes = {
  easdk: PropTypes.object,
};

render(
  <EmbeddedApp
    apiKey="MY_API_KEY"
    shopOrigin="https://MY_STORE_NAME.myshopify.com"
  >
    <MyApp />
  </EmbeddedApp>,
  document.querySelector('#root')
);

export default MyApp;

Something weird is happening, the console.log(this.context); fires twice: the first time with the EASDK object and the second time with {easdk: undefined}. So obviously the button will not work because this.context.easdk is undefined.

Is there something wrong with my code or with how I understood the example? Sorry if this is a non-issue, I'm new to both react and polaris.

Hey @martinsileno, what version of @shopify/polaris are you currently using? Your code looks great, I'm thinking the problem is with the use of the EmbeddedApp component which has been deprecated in v2.

If you are using the latest beta version (@shopify/polaris v2.0.0-beta.17), we've replaced EmbeddedApp with the new AppProvider component. To initialize your app, you'll pass the shopOrigin and apiKey props to this component instead of EmbeddedApp. Then you should be able to access the easdk context just as you have in your code snippet.

Using AppProvider

import React from 'react';
import PropTypes from 'prop-types';
import {render} from 'react-dom';
import {AppProvider, Button, Card, Page} from '@shopify/polaris';

class MyApp extends React.Component {
  static contextTypes = {
    easdk: PropTypes.object,
  };

  render() {
    console.log(this.context);

    return (
      <Page title="Using AppProvider">
        <Card>
          <Card.Section>
            <Button onClick={() => this.context.easdk.stopLoading()}>TEST</Button>
          </Card.Section>
        </Card>
      </Page>
    );
  }
}

render(
  <AppProvider
    apiKey="MY_API_KEY"
    shopOrigin="https://MY_STORE_NAME.myshopify.com"
  >
    <MyApp />
  </AppProvider>
  , document.querySelector('#root')
);

Thank you for your reply.

However, I found the issue and it was my fault, sorry :smile:

I had set index.js as entry point of my app but both index.js and App.js had a render() function so both were rendering and that's why the console.log was being called twice.

I've tried so many things and couldn't get this to work. Then I realised, the AppProvider actually has to be connected to your app to work (with valid shopOrigin and apiKey properties). This was an issue because when developing locally I often comment out those so that I can see things like page title and not get errors.

Also as it uses the legacy context API, this method also seems kind of outdated. Are there plans to make this work with the new context api?

Hey @Hughp135, what exactly was it that didn't work? Can you post a code snippet? The app provider does not require the EASDK props (shopOrigin and apiKey). Unless you're testing your app in the admin, you should be able to develop locally with no props passed to the app provider and it will render.

@chloerice The issue isn't with using AppProvider in general without those keys (it works fine for most things), it's that the PropTypes for easdk is not set without them, meaning you have to do checks like this to avoid errors at runtime:

if (this.context.easdk) {
      this.context.easdk.startLoading();
    }

(Without apiKey / shopOrigin, this.context.easdk is undefined).

Ahh yes, if you're trying to do anything with the easdk (either with the new this.props.polaris.easdk or backward compatible this.context.easdk) you won't have the prop or the context without providing the shopOrigin and apiKey.

To answer your question about using the new context API––we will be able to use it once Enzyme supports testing it (see https://github.com/airbnb/enzyme/pull/1513).

How can we use this.context.easdk of root component MyApp in children components?

In Home component this.context.easdk is undefined here.

import React from 'react';
import PropTypes from 'prop-types';
import {render} from 'react-dom';
import {AppProvider, Button, Card, Page} from '@shopify/polaris';
import {BrowserRouter, Route} from 'react-router-dom';

class Home extends React.Component {
  render() {
    return (
      <Page title="Using AppProvider">
        <Card>
          <Card.Section>
            <Button onClick={() => this.context.easdk.stopLoading()}>TEST</Button>
          </Card.Section>
        </Card>
      </Page>
    );
  }
}

class MyApp extends React.Component {
  static contextTypes = {
    easdk: PropTypes.object,
  };

  render() {
    return (
      <BrowserRouter>
        <div>
          <Route path="/" render={() => (
            <Home />
          )} />
        </div>
      </BrowserRouter>
    );
  }
}

render(
  <AppProvider
    apiKey="MY_API_KEY"
    shopOrigin="https://MY_STORE_NAME.myshopify.com"
  >
    <MyApp />
  </AppProvider>
  , document.querySelector('#root')
);

I'm using polaris 2.10.0

@montalvomiguelo each component that needs access to the easdk context would have to define that they want access to it off of the context.

class Home extends React.Component {
  static contextTypes = {
    easdk: PropTypes.object,
  };

  render() {
    return (
      <Page title="Using AppProvider">
        <Card>
          <Card.Section>
            <Button onClick={() => this.context.easdk.stopLoading()}>TEST</Button>
          </Card.Section>
        </Card>
      </Page>
    );
  }
}
Was this page helpful?
0 / 5 - 0 ratings