on b8a50ee help sign in
214 lines 6.9 kB history raw
1+++
2title = "7. React app setup"
3weight = 7
4template = "doc.html"
5+++
7Now that we have a basic idea of how Eyre works, we can begin working on our
8React app front-end.
10## Create React app
12Node.js must be installed, and can be downloaded from their
13[website](https://nodejs.org/en/download). With that installed, we'll have the
14`npm` package manager available. The first thing we'll do is globally install
15the `create-react-app` package with the following command:
17```sh
18npm install -g create-react-app
19```
21Once installed, we can use it to create a new `journal-ui` directory and setup a
22new React app in it with the following command:
24```sh
25create-react-app journal-ui
26```
28We can then open our new directory:
30```sh
31cd journal-ui
32```
34Its contents should look something like this:
36```
37journal-ui
38├── node_modules
39├── package.json
40├── package-lock.json
41├── public
42├── README.md
43└── src
44```
46## Install `http-api`
48Inside our React app directory, let's install the `@urbit/http-api` NPM package:
50```sh
51npm i @urbit/http-api
52```
54We also install a handful of other packages for the UI components (`bootstrap react-bootstrap react-textarea-autosize date-fns react-bottom-scroll-listener react-day-picker`), but that's not important to our purposes here.
56## Additional tweaks
58Our front-end will be served directly from the ship by the `%docket` app, where
59a user will open it by clicking on its homescreen tile. Docket serves such
60front-ends with a base URL path of `/apps/[desk]/`, so in our case it will be
61`/apps/journal`. In order for our app to be built with correct resource paths,
62we must add the following line to `package.json`:
64```json
65"homepage": "/apps/journal/",
66```
68Our app also needs to know the name of the ship it's being served from in order
69to talk with it. The `%docket` agent serves a small file for this purpose at
70`[host]/session.js`. This file is very simple and just contains:
72```js
73window.ship = "sampel-palnet";
74```
76`sampel-palnet` will of course be replaced by the actual name of the ship. We
77include this script by adding the following line to the `<head>` section of
78`public/index.html`:
80```
81<script src="/session.js"></script>
82```
84## Basic API setup
86With everything now setup, we can begin work on the app itself. In this case
87we'll just edit the existing `App.js` file in the `/src` directory. The first thing is to import the `Urbit` class from `@urbit/http-api`:
89```js
90import Urbit from "@urbit/http-api";
91```
93We also need to import a few other things, mostly relating to UI components (but
94these aren't important for our purposes here):
96```js
97import React, { Component } from "react";
98import "bootstrap/dist/css/bootstrap.min.css";
99import "react-day-picker/lib/style.css";
100import TextareaAutosize from "react-textarea-autosize";
101import Button from "react-bootstrap/Button";
102import Card from "react-bootstrap/Card";
103import Stack from "react-bootstrap/Stack";
104import Tab from "react-bootstrap/Tab";
105import Tabs from "react-bootstrap/Tabs";
106import ToastContainer from "react-bootstrap/ToastContainer";
107import Toast from "react-bootstrap/Toast";
108import Spinner from "react-bootstrap/Spinner";
109import CloseButton from "react-bootstrap/CloseButton";
110import Modal from "react-bootstrap/Modal";
111import DayPickerInput from "react-day-picker/DayPickerInput";
112import endOfDay from "date-fns/endOfDay";
113import startOfDay from "date-fns/startOfDay";
114import { BottomScrollListener } from "react-bottom-scroll-listener";
115```
117Inside the existing `App` class:
119```js
120class App extends Component {
121```
123...we'll clear out the existing demo code and start adding ours. The first thing
124is to define our app's state. We'll look at most of the state entries in the
125next section. For now, we'll just consider `status`.
127```js
128state = {
129 // .....
130 status: null,
131 // .....
132};
133```
135Next, we'll setup the `Urbit` API object in `componentDidMount`. We could do
136this outside the `App` class since we're adding it to `window`, but we'll do it
137this way so it's all in one place:
139```js
140componentDidMount() {
141 window.urbit = new Urbit("");
142 window.urbit.ship = window.ship;
143 window.urbit.onOpen = () => this.setState({status: "con"});
144 window.urbit.onRetry = () => this.setState({status: "try"});
145 window.urbit.onError = (err) => this.setState({status: "err"});
146 this.init();
147};
148```
150The first thing we do is create a new instance of the `Urbit` class we imported
151from `@urbit/http-api`, and save it to `window.urbit`. The `Urbit` class
152constructor takes three arguments: `url`, `desk` and `code`, of which only `url`
153is mandatory.
155- `url` is the URL of the ship we want to talk to. Since our React app will be
156 served by the ship, we can just leave it as an empty `""` string and let
157 `Urbit` use root-relative paths.
158- `desk` is only necessary if we want to run threads through Eyre, and since
159 we're not going to do that, we can exclude it.
160- `code` is the web login code for authentication, but since the user will
161 already have logged in, we can also exclude that.
163Therefore, we call the class contructor with just the empty `url` string:
165```js
166window.urbit = new Urbit("");
167```
169Next, we need to set the ship name in our `Urbit` instance. Eyre requires the
170ship name be specified in all requests, so if we don't set it, Eyre will reject
171all the messages we send. We previously included `session.js` which sets
172`window.ship` to the ship name, so we just set `window.urbit.ship` as that:
174```js
175window.urbit.ship = window.ship;
176```
178Next, we set three callbacks: `onOpen`, `onRetry`, and `onError`. These
179callbacks are fired when the state of our channel connection changes:
181- `onOpen` is called when a connection is established.
182- `onRetry` is called when a channel connection has been interrupted (such as by
183 network issues) and the `Urbit` object is trying to reconnect. Reconnection
184 will be attempted up to three times: immediately, after 750ms, and after
185 3000ms.
186- `onError` is called with an `Error` message once all retries have failed, or
187 otherwise when a fatal error occurs.
189We'll look at how we handle these cases in the next section. For now, we'll just
190set the `status` entry in the state to either `"con"`, `"try"`, or `"err"` as
191the case may be. Note that it's not mandatory to set these callbacks, but
192leaving connection problems unhandled is usually a bad idea.
194The last thing we do is call:
196```js
197this.init();
198```
200This function will fetch initial entries and subscribe for updates. We'll look
201at it in the next section.
203## Resources
205- [HTTP API Guide](/docs/userspace/http-api-guide) - Reference documentation for
206 `@urbit/http-api`.
208- [React app source
209 code](https://github.com/urbit/docs-examples/tree/main/journal-app/ui) - The
210 source code for the Journal app UI.
212- [`@urbit/http-api` source
213 code](https://github.com/urbit/urbit/tree/master/pkg/npm/http-api) - The
214 source code for the `@urbit/http-api` NPM package.