├── .flowconfig
├── .gitignore
├── README.md
├── flow-typed
└── globals.js
├── package.json
├── preview.png
├── src
├── main.js
├── pages
│ ├── home.js
│ ├── pageNotFound.js
│ └── search.js
├── redux
│ ├── concerts.js
│ └── index.js
├── root.js
└── rpc
│ └── index.js
└── yarn.lock
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | .*/node_modules/.*[^(package)]\.json$
3 |
4 | [include]
5 | ./src/
6 |
7 | [libs]
8 | ./node_modules/fusion-plugin-rpc-redux-react/flow-typed/npm/redux_v4.x.x.js
9 | ./node_modules/fusion-plugin-rpc-redux-react/flow-typed/redux-reactors_v1.x.x.js
10 | ./node_modules/fusion-plugin-react-redux/flow-typed/npm/redux_v3.x.x.js
11 | ./node_modules/fusion-plugin-redux-action-emitter-enhancer/flow-typed/npm/redux_v3.x.x.js
12 | ./node_modules/fusion-core/flow-typed/npm/koa_v2.x.x.js
13 | ./node_modules/fusion-core/flow-typed/tape-cup_v4.x.x.js
14 |
15 | [lints]
16 |
17 | [options]
18 |
19 | [strict]
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | *.log
3 | .fusion/
4 | coverage*/
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fusion.js + Base UI Example App
2 |
3 | ## What are we going to build?
4 |
5 | 
6 |
7 | If you want to immediately get the whole application
8 |
9 | ```
10 | git clone git@github.com:tajo/fusion-baseui.git
11 | cd fusion-baseui
12 | yarn
13 | yarn dev
14 | ```
15 |
16 | Or you can follow the tutorial bellow with detailed description.
17 |
18 | # Step-by-step tutorial
19 |
20 | ## Assumptions
21 |
22 | - Your environment has Node.js 8.11 and the latest Yarn
23 | - Advanced knowledge of JavaScript
24 | - Intermediate knowledge of React and Redux
25 |
26 | ## Learning objectives
27 |
28 | - Bootstrap a basic Fusion.js app
29 | - Fetch data from a public REST API and store it in Redux
30 | - Pre-render the page on the server
31 | - Rehydrate the redux store on the client
32 | - Build a simple UI using Base UI components
33 | - Handle errors
34 |
35 | ## Fusion.js setup
36 |
37 | ```
38 | yarn create fusion-app fusion-baseui
39 | cd fusion-baseui
40 | yarn dev
41 | ```
42 |
43 | That should open [https://localhost:3000](https://localhost:3000) in your browser with "Fusion.js - Let's get started" message.
44 |
45 | Open `src/pages/home.js`, change the `Get Started` message to something else and save it. You should immediately see it in the browser because of hot reloading.
46 |
47 | ## Base UI setup
48 |
49 | Base UI is a component library based on React. We will use it to put together our user interface. Add it to your project via
50 |
51 | ```
52 | yarn add baseui
53 | ```
54 |
55 | Now, replace the content of `src/pages/home.js` with
56 |
57 | ```jsx
58 | // @flow
59 | import * as React from "react";
60 |
61 | // Base UI components
62 | import { Card } from "baseui/card";
63 | import { Block } from "baseui/block";
64 |
65 | const CONCERTS = [
66 | {
67 | eventDateName: "Jón Jónsson og Friðrik Dór - fjölskyldutónleikar",
68 | name: "Tónleikar",
69 | dateOfShow: "2018-12-15T14:00:00",
70 | eventHallName: "Bæjarbíó (Hafnarfirði)",
71 | imageSource:
72 | "https://d30qys758zh01z.cloudfront.net/images/medium/1.10700.jpg"
73 | },
74 | {
75 | eventDateName: "Jón Jónsson og Friðrik Dór - fjölskyldutónleikar",
76 | name: "Tónleikar-UPPSELT",
77 | dateOfShow: "2018-12-15T16:00:00",
78 | eventHallName: "Bæjarbíó (Hafnarfirði)",
79 | imageSource:
80 | "https://d30qys758zh01z.cloudfront.net/images/medium/1.10700.jpg"
81 | },
82 | {
83 | eventDateName: "Hera Björk - Ilmur af jólum - Í borg og bæ",
84 | name: "Hólmavík",
85 | dateOfShow: "2018-12-15T17:00:00",
86 | eventHallName: "Hólmavíkurkirkja",
87 | imageSource:
88 | "https://d30qys758zh01z.cloudfront.net/images/medium/1.10648.jpg"
89 | },
90 | {
91 | eventDateName: "Hátíðartónleikar Eyþórs Inga",
92 | name: "Víðistaðakirkja",
93 | dateOfShow: "2018-12-15T20:00:00",
94 | eventHallName: "Víðistaðakirkja (Hafnarfirði)",
95 | imageSource:
96 | "https://d30qys758zh01z.cloudfront.net/images/medium/1.10630.jpg"
97 | },
98 | {
99 | eventDateName: "Jólin til þín",
100 | name: "Höfn",
101 | dateOfShow: "2018-12-15T20:00:00",
102 | eventHallName: "Íþróttahúsið á Höfn",
103 | imageSource:
104 | "https://d30qys758zh01z.cloudfront.net/images/medium/1.10647.jpg"
105 | },
106 | {
107 | eventDateName: "Jólalögin þeirra",
108 | name: "Tónleikar",
109 | dateOfShow: "2018-12-15T21:00:00",
110 | eventHallName: "Hendur í Höfn",
111 | imageSource:
112 | "https://d30qys758zh01z.cloudfront.net/images/medium/1.10687.jpg"
113 | }
114 | ];
115 |
116 | class Home extends React.Component {
117 | render() {
118 | return (
119 |
120 |
127 | {CONCERTS.map(concert => (
128 |
138 | 📅 {concert.dateOfShow}
139 |
140 | 📍 {concert.eventHallName}
141 |
142 | ))}
143 |
144 |
145 | );
146 | }
147 | }
148 |
149 | export default Home;
150 | ```
151 |
152 | The Home component now renders a list of (hard-coded) concerts. Every concerts has a few properties:
153 |
154 | - `eventDateName: string` - the name of event
155 | - `name: string` - the name of artist
156 | - `dateOfShow: string` - the date of event
157 | - `eventHallName: string` - where the event takes place
158 | - `imageSource: string` - poster for the event
159 |
160 | We use two Base UI components:
161 |
162 | - `` - basic building block for layouts. In our example, we utilize CSS grid properties to build a responsive grid layout.
163 | - `` - to display the information about a single event. Note that we need to specify an unique `key` prop because it's React's requirement for array of components. Also, we use `overrides` to customize the styles of the root Card element (positioning and maximum width).
164 |
165 | ## Date formatting
166 |
167 | As you might notice, `2018-12-15T20:00:00` is not very human readable. We can use 3rd party library to make the formatting better
168 |
169 | ```
170 | yarn add date-fns
171 | ```
172 |
173 | Now import it into `home.js`
174 |
175 | ```jsx
176 | import { format } from "date-fns";
177 | ```
178 |
179 | and replace
180 |
181 | ```
182 | concert.dateOfShow
183 | ```
184 |
185 | with
186 |
187 | ```
188 | format(concert.dateOfShow, "MM/DD/YYYY hh:mm A")
189 | ```
190 |
191 | **Note:** You can often see usage of other library `Moment.js` We try to avoid it since it dramatically increases the size of the application. `date-fns` is much smaller, modular and tree-shakeable.
192 |
193 | ## Search
194 |
195 | First, we will create a search icon component that will be part of our search input. Create a new file `src/pages/search.js`:
196 |
197 | ```jsx
198 | // @flow
199 | import * as React from "react";
200 | import { styled } from "fusion-plugin-styletron-react";
201 | import SearchIcon from "baseui/icon/search";
202 |
203 | const Icon = styled("div", {
204 | display: "flex",
205 | alignItems: "center",
206 | justifyContent: "center",
207 | height: "100%",
208 | marginRight: "1em"
209 | });
210 |
211 | const SearchComponent = () => (
212 |
213 |
214 |
215 | );
216 |
217 | export default SearchComponent;
218 | ```
219 |
220 | Now go back to `home.js` and add the imports
221 |
222 | ```jsx
223 | import { HeaderNavigation } from "baseui/header-navigation";
224 | import { StatefulInput } from "baseui/input";
225 | import Search from "./search";
226 | ```
227 |
228 | Our search is client-side only (API doesn't have a search parameter). We need to add a local search state
229 |
230 | ```jsx
231 | class Home extends React.Component<{}, { search: string }> {
232 | state = {
233 | search: ""
234 | };
235 | // the rest of Home component....
236 | ```
237 |
238 | Let's add a header that will contain the search input
239 |
240 | ```jsx
241 |
242 |
243 | this.setState({ search: e.target.value })}
247 | />
248 |
249 | {/* the rest of render method... */}
250 | ```
251 |
252 | Now you should see the page header rendered. The last step is to filter concerts accordingly to `this.state.search`
253 |
254 | ```jsx
255 | CONCERTS.filter(concert =>
256 | concert.name.toLowerCase().includes(this.state.search.toLowerCase())
257 | ).map(/* ... */);
258 | ```
259 |
260 | Our main UI is finished!
261 |
262 | ## Redux and fetching the data
263 |
264 | Redux is a popular state container for JavaScript apps. Fusion.js team maintains multiple plugins that make the integration easy. Let's add them and all other necessary dependencies
265 |
266 | ```
267 | yarn add fusion-plugin-react-redux fusion-plugin-rpc-redux-react fusion-plugin-universal-events react-redux@5 redux isomorphic-fetch
268 | ```
269 |
270 | `fusion-plugin-universal-events` is commonly required by other Fusion.js plugins and is used as an event emitter for data such as statistics and analytics. It's necessary for other redux plugins.
271 |
272 | `fusion-plugin-react-redux` adds basic integration of React-Redux into your Fusion.js application. It handles creating your store, wrapping your element tree in a provider, and serializing/deserializing your store between server and client.
273 |
274 | `fusion-plugin-rpc-redux-react` RPC is a natural way of expressing that a server-side function should be run in response to a client-side function call. It's an alternative to REST. This plugin provides a higher order component that connects RPC methods to Redux as well as React component props. It also helps to cut the typical redux boilerplate when creating action creators and reducers.
275 |
276 | But first things first, let's create a reducer in `src/redux/concerts.js`
277 |
278 | ```jsx
279 | // @flow
280 | import { createRPCReducer } from "fusion-plugin-rpc-redux-react";
281 |
282 | export type ConcertT = {
283 | +name: string,
284 | +imageSource: string,
285 | +eventDateName: string,
286 | +dateOfShow: string,
287 | +eventHallName: string
288 | };
289 |
290 | const initialState = {
291 | loading: false,
292 | data: [],
293 | error: null
294 | };
295 | export default createRPCReducer<
296 | { loading: boolean, data: ConcertT[], error: ?string },
297 | { payload: any, type: string }
298 | >(
299 | "getConcerts",
300 | {
301 | start: (state, action) => ({ ...state, loading: true }),
302 | success: (state, action) => ({
303 | ...state,
304 | loading: false,
305 | data: action.payload
306 | }),
307 | failure: (state, action) => {
308 | return {
309 | ...state,
310 | loading: false,
311 | error: action.payload.message
312 | };
313 | }
314 | },
315 | initialState
316 | );
317 | ```
318 |
319 | And `src/redux/index.js` where we combine/re-export existing reducers so we can add even more reducers in the future
320 |
321 | ```jsx
322 | // @flow
323 | import { combineReducers } from "redux";
324 | import concerts from "./concerts.js";
325 |
326 | export default combineReducers({
327 | concerts
328 | });
329 | ```
330 |
331 | Now we need to create an RPC handler. It's a function (endpoint) that will handle the data fetching of our concerts. It will be used by server-side rendering and it can be also called by the client. Create `src/rpc/index.js`
332 |
333 | ```jsx
334 | // @flow
335 | import { ResponseError } from "fusion-plugin-rpc-redux-react";
336 |
337 | export default {
338 | getConcerts: async () => {
339 | try {
340 | const response = await fetch("https://apis.is/concerts");
341 | if (response.status == 200) {
342 | const json = await response.json();
343 | return json.results;
344 | }
345 | throw response.statusText;
346 | } catch (e) {
347 | throw new ResponseError(e);
348 | }
349 | }
350 | };
351 | ```
352 |
353 | The next step is to put it all together in `src/main.js`
354 |
355 | ```jsx
356 | import Redux, { ReduxToken, ReducerToken } from "fusion-plugin-react-redux";
357 | import RPC, { RPCToken, RPCHandlersToken } from "fusion-plugin-rpc-redux-react";
358 | import UniversalEvents, {
359 | UniversalEventsToken
360 | } from "fusion-plugin-universal-events";
361 | import { FetchToken } from "fusion-tokens";
362 | import reducer from "./redux/index.js";
363 | import handlers from "./rpc/index.js";
364 | import fetch from "isomorphic-fetch";
365 |
366 | export default () => {
367 | /* ... */
368 | app.register(RPCToken, RPC);
369 | app.register(UniversalEventsToken, UniversalEvents);
370 | __NODE__
371 | ? app.register(RPCHandlersToken, handlers)
372 | : app.register(FetchToken, fetch);
373 | app.register(ReduxToken, Redux);
374 | app.register(ReducerToken, reducer);
375 | /* ... */
376 | return app;
377 | };
378 | ```
379 |
380 | Finally, **let's remove the hardcoded concerts** and connect the home component to the redux (`getConcerts` store).
381 |
382 | Add these imports into `src/pages/home.js`
383 |
384 | ```jsx
385 | // redux and fusion helpers
386 | import { compose } from "redux";
387 | import { connect } from "react-redux";
388 | import { prepared } from "fusion-react";
389 | import { withRPCRedux } from "fusion-plugin-rpc-redux-react";
390 |
391 | // types
392 | import type { ConcertT } from "../redux/concerts";
393 | ```
394 |
395 | And this will create an HOC that connects our page to the store and it also triggers `getConcerts` fetch when doing server-side rendering
396 |
397 | ```jsx
398 | const hoc = compose(
399 | // generates Redux actions and
400 | // a React prop for the `getConcerts` RPC call
401 | withRPCRedux("getConcerts"),
402 | // expose the Redux state to React props
403 | connect(({ concerts }) => ({ concerts })),
404 | // invokes the passed in method on component hydration
405 | prepared(props => {
406 | if (props.concerts.loading || props.concerts.data.length) {
407 | return Promise.resolve();
408 | }
409 | return props.getConcerts();
410 | })
411 | );
412 |
413 | export default hoc(Home);
414 | ```
415 |
416 | Let's update our flow types
417 |
418 | ```jsx
419 | class Home extends React.Component<
420 | {
421 | getConcerts: () => void,
422 | concerts: { data: ConcertT[], error: ?string }
423 | },
424 | { search: string }
425 | > {
426 | /* ... */
427 | }
428 | ```
429 |
430 | And finally, replace `CONCERTS` with `this.props.concerts.data`. Now you should see the list of events again 🎉🎉🎉. However, this time our application fetches them from the public API!
431 |
432 | Note the client doesn't **NOT** make an XHR call to `https://apis.is/concerts` - it's all done on the server and browser already receives all events (rendered HTML elements and also as serialized redux state). **That's it!**
433 |
434 | The Home component has also an access to `this.props.getConcerts()`, so for example, you could re-fetch the data like this
435 |
436 | ```jsx
437 | componentDidMount() {
438 | // optional re-fetch on the client
439 | this.props.getConcerts();
440 | }
441 | ```
442 |
443 | For the full example you can clone and explore this repository. It has some extra code to gracefully handle fetch errors and display a nice user notification.
444 |
445 | ## More resources
446 |
447 | For detailed documentation please visit:
448 |
449 | - https://fusionjs.com
450 | - https://baseui.design/
451 |
--------------------------------------------------------------------------------
/flow-typed/globals.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | declare var __NODE__: boolean;
3 | declare var __BROWSER__: boolean;
4 | declare var __DEV__: boolean;
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fusion-baseui-app",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "dependencies": {
6 | "baseui": "^9.24.0",
7 | "date-fns": "^2.8.1",
8 | "flow-bin": "0.110.0",
9 | "fusion-cli": "^2.5.0",
10 | "fusion-core": "^2.0.8",
11 | "fusion-plugin-i18n": "^2.3.5",
12 | "fusion-plugin-react-helmet-async": "^2.1.4",
13 | "fusion-plugin-react-redux": "^2.0.8",
14 | "fusion-plugin-react-router": "^2.1.2",
15 | "fusion-plugin-rpc": "^3.3.3",
16 | "fusion-plugin-rpc-redux-react": "^4.0.8",
17 | "fusion-plugin-styletron-react": "^3.0.11",
18 | "fusion-plugin-universal-events": "^2.0.9",
19 | "fusion-react": "^3.1.8",
20 | "fusion-tokens": "^2.0.8",
21 | "isomorphic-fetch": "^2.2.1",
22 | "prop-types": "^15.6.2",
23 | "react": "^16.12.0",
24 | "react-dom": "^16.12.0",
25 | "react-redux": "^7.1.3",
26 | "redux": "^4.0.4",
27 | "styletron-react": "^5.2.6"
28 | },
29 | "scripts": {
30 | "dev": "fusion dev",
31 | "flow": "flow",
32 | "test": "fusion test",
33 | "build": "fusion build",
34 | "build-production": "fusion build --production",
35 | "start": "fusion start"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tajo/fusion-baseui/0a17ac4ca0afd677337e287842978f926a05931c/preview.png
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import App from "fusion-react";
3 | import Router from "fusion-plugin-react-router";
4 | import Styletron from "fusion-plugin-styletron-react";
5 | import UniversalEvents, {
6 | UniversalEventsToken
7 | } from "fusion-plugin-universal-events";
8 | import Redux, { ReduxToken, ReducerToken } from "fusion-plugin-react-redux";
9 | import RPC, { RPCToken, RPCHandlersToken } from "fusion-plugin-rpc-redux-react";
10 | import { FetchToken } from "fusion-tokens";
11 | import reducer from "./redux/index.js";
12 | import handlers from "./rpc/index.js";
13 | import fetch from "isomorphic-fetch";
14 |
15 | import root from "./root.js";
16 |
17 | export default () => {
18 | const app = new App(root);
19 | app.register(Styletron);
20 | app.register(Router);
21 | app.register(RPCToken, RPC);
22 | app.register(UniversalEventsToken, UniversalEvents);
23 | __NODE__
24 | ? app.register(RPCHandlersToken, handlers)
25 | : app.register(FetchToken, fetch);
26 | app.register(ReduxToken, Redux);
27 | app.register(ReducerToken, reducer);
28 | return app;
29 | };
30 |
--------------------------------------------------------------------------------
/src/pages/home.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from "react";
3 | import { format, parseISO } from "date-fns";
4 |
5 | // Base UI components
6 | import { Card } from "baseui/card";
7 | import { Block } from "baseui/block";
8 | import { HeaderNavigation } from "baseui/header-navigation";
9 | import { StatefulInput } from "baseui/input";
10 | import { Notification, KIND } from "baseui/notification";
11 | import Search from "./search";
12 |
13 | // redux and fusion helpers
14 | import { compose } from "redux";
15 | import { connect } from "react-redux";
16 | import { prepared } from "fusion-react";
17 | import { withRPCRedux } from "fusion-plugin-rpc-redux-react";
18 |
19 | // types
20 | import type { ConcertT } from "../redux/concerts";
21 |
22 | const formatDate = (dateOfShow: string) => {
23 | return format(parseISO(dateOfShow), "mm/dd/yyyy hh:mm")
24 | }
25 |
26 | class Home extends React.Component<
27 | {
28 | getConcerts: () => void,
29 | concerts: { data: ConcertT[], error: ?string }
30 | },
31 | { search: string }
32 | > {
33 | state = {
34 | search: ""
35 | };
36 | componentDidMount() {
37 | // optional re-fetch on the client
38 | this.props.getConcerts();
39 | }
40 | render() {
41 | const { concerts } = this.props;
42 | if (concerts.error) {
43 | return (
44 |
45 | {concerts.error}
46 |
47 | );
48 | }
49 | return (
50 |
51 |
52 | this.setState({ search: e.target.value })}
56 | />
57 |
58 |
65 | {concerts.data &&
66 | concerts.data
67 | .filter(concert =>
68 | concert.name
69 | .toLowerCase()
70 | .includes(this.state.search.toLowerCase())
71 | )
72 | .map(concert => (
73 |
83 | 📅 {formatDate(concert.dateOfShow)}
84 |
85 | 📍 {concert.eventHallName}
86 |
87 | ))}
88 |
89 |
90 | );
91 | }
92 | }
93 |
94 | const hoc = compose(
95 | // generates Redux actions and
96 | // a React prop for the `getConcerts` RPC call
97 | withRPCRedux("getConcerts"),
98 | // expose the Redux state to React props
99 | connect(({ concerts }) => ({ concerts })),
100 | // invokes the passed in method on component hydration
101 | prepared(props => {
102 | if (props.concerts.loading || props.concerts.data.length) {
103 | return Promise.resolve();
104 | }
105 | return props.getConcerts();
106 | })
107 | );
108 |
109 | export default hoc(Home);
110 |
--------------------------------------------------------------------------------
/src/pages/pageNotFound.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import {NotFound} from 'fusion-plugin-react-router';
4 |
5 | const PageNotFound = () => (
6 |
7 | 404
8 |
9 | );
10 |
11 | export default PageNotFound;
12 |
--------------------------------------------------------------------------------
/src/pages/search.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from "react";
3 | import { styled } from "fusion-plugin-styletron-react";
4 | import SearchIcon from "baseui/icon/search";
5 |
6 | const Icon = styled("div", {
7 | display: "flex",
8 | alignItems: "center",
9 | justifyContent: "center",
10 | height: "100%",
11 | marginRight: "1em"
12 | });
13 |
14 | const SearchComponent = () => (
15 |
16 |
17 |
18 | );
19 |
20 | export default SearchComponent;
21 |
--------------------------------------------------------------------------------
/src/redux/concerts.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { createRPCReducer } from "fusion-plugin-rpc-redux-react";
3 |
4 | export type ConcertT = {
5 | +name: string,
6 | +imageSource: string,
7 | +eventDateName: string,
8 | +dateOfShow: string,
9 | +eventHallName: string
10 | };
11 |
12 | const initialState = {
13 | loading: false,
14 | data: [],
15 | error: null
16 | };
17 | export default createRPCReducer<
18 | { loading: boolean, data: ConcertT[], error: ?string },
19 | { payload: any, type: string }
20 | >(
21 | "getConcerts",
22 | {
23 | start: (state, action) => ({ ...state, loading: true }),
24 | success: (state, action) => ({
25 | ...state,
26 | loading: false,
27 | data: action.payload
28 | }),
29 | failure: (state, action) => {
30 | return {
31 | ...state,
32 | loading: false,
33 | error: action.payload.message
34 | };
35 | }
36 | },
37 | initialState
38 | );
39 |
--------------------------------------------------------------------------------
/src/redux/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { combineReducers } from "redux";
3 | import concerts from "./concerts.js";
4 |
5 | export default combineReducers({
6 | concerts
7 | });
8 |
--------------------------------------------------------------------------------
/src/root.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from "react";
3 | import { Route, Switch } from "fusion-plugin-react-router";
4 |
5 | import Home from "./pages/home.js";
6 | import PageNotFound from "./pages/pageNotFound.js";
7 |
8 | const root = (
9 |
10 |
11 |
12 |
13 | );
14 |
15 | export default root;
16 |
--------------------------------------------------------------------------------
/src/rpc/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { ResponseError } from "fusion-plugin-rpc-redux-react";
3 |
4 | export default {
5 | getConcerts: async () => {
6 | try {
7 | const response = await fetch("https://apis.is/concerts");
8 |
9 | // TEST Error: Network
10 | // const response = await fetch("http://exampleofnetworkerror.com");
11 |
12 | // TEST Error: Status code is not 200
13 | //const response = await fetch("http://httpstat.us/500");
14 |
15 | if (response.status == 200) {
16 | const json = await response.json();
17 | return json.results;
18 | }
19 | throw response.statusText;
20 | } catch (e) {
21 | throw new ResponseError(e);
22 | }
23 | }
24 | };
25 |
--------------------------------------------------------------------------------