UNPKG

8.16 kBMarkdownView Raw
1# `@shopify/app-bridge-host`
2
3App Bridge Host contains components and middleware to be consumed by the app's host, as well as the host itself. The middleware and `Frame` component are responsible for facilitating communication between the client and host, and used to act on actions sent from the [App Bridge client](../app-bridge/). This package is used by Shopify's web admin.
4
5[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md)
6
7## App Bridge Host architecture
8
9The App Bridge host uses a cross-platform, modular architecture. The host has several responsibilities. First, the host brokers communication between contexts (ie between the app and Shopify Admin). Second, the host maintains a central store representing the current state of all App Bridge features, which is exposed to the client app. Third, the host provides functionality to the client app.
10
11Functionality is exposed to the client app via App Bridge actions. When an action is dispatched using App Bridge, the host evaluates the action against the relevant reducers, which make changes to the central store of app state. The state is then passed to UI components, which render functionality based on the state.
12
13Features and UI components are treated separately in App Bridge. A feature consists of an action set and reducers, and the associated UI component consumes the resulting state. Most UI components have an associated feature, but this is not required.
14
15The `<HostProvider>` is not responsible for rendering the client app, by Iframe or other means.
16
17## Building an App Bridge host
18
19You can create your own App Bridge host using the `<HostProvider>` component.
20
21`<HostProvider>` requires three types of data: app configuration, functionality to provide to the client app, and an initial state for the store.
22
23### App configuration
24
25The `<HostProvider>` requires configuration information about the client app to be loaded:
26
27```js
28const config = {
29 apiKey: 'API key from Shopify Partner Dashboard',
30 appId: 'app id from GraphQL',
31 handle: 'my-app-handle',
32 shopId: 'shop id from GraphQL',
33 url: 'app url from Shopify Partner Dashboard',
34 name: 'app name',
35};
36```
37
38Note that we'll be referring to this sample config throughout the examples below.
39
40#### Providing functionality
41
42The `<HostProvider>` does not load any components by default. In order to provide a feature to an app, you must load the necessary component(s).
43
44You can find pre-defined host UI components inside the `@shopify/app-bridge-host/components` directory. You can also write your own components.
45
46#### Initial state
47
48In App Bridge, features are gated using a permission model, which lives in the store. All feature permissions default to `false`. To provide a feature, you must also set the relevant permissions. If you don’t, the client app will not be permitted to use the feature, even if the component is available. Most components are associated with a single feature, but this is not a requirement.
49
50The `<HostProvider>` accepts an initial state for the store. This allows a host to pre-populate the store with information the app can immediately access, such as feature permissions.
51
52The `setFeaturesAvailable` utility can be used to build the `initialState.features` object. The following example shows a host with several components, and the corresponding feature availability set in `initialState`:
53
54```tsx
55import {HostProvider} from '@shopify/app-bridge-host';
56import {Loading} from '@shopify/app-bridge-host/components/Loading';
57import {Modal} from '@shopify/app-bridge-host/components/Modal';
58import {Navigation} from '@shopify/app-bridge-host/components/Navigation';
59
60import {Group} from '@shopify/app-bridge/actions';
61import {setFeaturesAvailable} from '@shopify/app-bridge-host/store';
62
63const initialState = {
64 features: setFeaturesAvailable(Group.Loading, Group.Modal, Group.Navigation),
65};
66
67function Host() {
68 return (
69 <HostProvider
70 config={config}
71 components={[Loading, Modal, Navigation]}
72 initialState={initialState}
73 />
74 );
75}
76```
77
78### Custom components
79
80`HostProvider` can render any type of React component; it’s not limited to the components in this package.
81
82To connect a component to the App Bridge host, wrap it using the `withFeature` decorator. This decorator provides the component with access to the `store` and `actions` for a specified App Bridge feature (remember to set the corresponding feature permissions in `initialState`).
83
84Here is an example of creating a custom component that utilizes the App Bridge `Toast` feature, rendering the `Toast` component from Polaris.
85
86```tsx
87import {HostProvider, ComponentProps, withFeature} from '@shopify/app-bridge-host';
88import {feature as toastFeature, WithFeature} from '@shopify/app-bridge-host/store/reducers/embeddedApp/toast';
89import {Toast} from '@shopify/polaris';
90import compose from '@shopify/react-compose';
91
92function CustomToastComponent(props: WithFeature) {
93 const {
94 actions,
95 store: {content},
96 } = props;
97
98 if (!content) {
99 return null;
100 }
101
102 const {duration, error, id, message} = content;
103 return (
104 <Toast
105 error={error}
106 duration={duration}
107 onDismiss={() => actions.clear({id: id})}
108 content={message}
109 />
110 );
111}
112
113const Toast = compose<ComponentProps>(withFeature(toastFeature))(CustomToastComponent);
114
115function Host() {
116 return <HostProvider config={config} components={[Toast]} />;
117}
118```
119
120### Asynchronous components
121
122You can load host components asynchronously, ie using [@shopify/react-async](https://github.com/Shopify/quilt/tree/master/packages/react-async). The `<HostProvider>` handles adding the feature's reducer to the Redux store. Actions that are dispatched by the app before the feature is available is automatically queued and resolved once the feature's component is loaded.
123
124```tsx
125import {createAsyncComponent, DeferTiming} from '@shopify/react-async';
126import {HostProvider} from '@shopify/app-bridge-host';
127
128const Loading = createAsyncComponent<ComponentProps>({
129 load: () =>
130 import(
131 /* webpackChunkName: 'AppBridgeLoading' */ '@shopify/app-bridge-host/components/Loading'
132 ),
133 defer: DeferTiming.Idle,
134 displayName: 'AppBridgeLoading',
135});
136
137function Host() {
138 return <HostProvider config={config} components={[Loading]} router={router} />;
139}
140```
141
142### Rendering the client app, with navigation
143
144Since `<HostProvider>` is not responsible for rendering the client app, one of the `components` must handle this task. The Apps section in Shopify Admin uses the `MainFrame` component, which additionally requires a router context to provide navigation to the client app.
145
146If you want to provide navigation capabilities to your app, you will need to include the `Navigation` component and provide a router to the `<HostProvider>`. The router should keep the app’s current location in sync with the host page’s current location, and manage updating the location when the route changes.
147
148The following example shows a simple router being passed into `<HostProvider>`, along with the `MainFrame` and `Navigation` components:
149
150```ts
151import {HostProvider} from '@shopify/app-bridge-host';
152import {MainFrame} from '@shopify/app-bridge-host/components/MainFrame';
153import {Navigation} from '@shopify/app-bridge-host/components/Navigation';
154
155const router = {
156 location: {
157 pathname: window.location.pathname,
158 search: window.location.search,
159 },
160 history: {
161 push(path: string) {
162 window.history.pushState('', null, path);
163 },
164 replace(path: string) {
165 window.history.replaceState('', null, path);
166 },
167 },
168};
169
170const initialState = {
171 features: setFeaturesAvailable(Group.Navigation),
172};
173
174
175function Host() {
176 return (
177 <HostProvider
178 config={config}
179 components={[MainFrame, Navigation]}
180 initialState={initialState}
181 router={router}
182 />
183 );
184}
185
186```
187
188Note that since `MainFrame` only renders the app itself and does not provide features to the app, there is no related `initialState`. `Navigation`, however, provides a feature to the app. To allow the app to use that feature, it is made made available in `initialState`.