2. NEAR Components

The /src folder isn’t so different from any other React app. The main difference is the use of NEAR components, which you’ll import from the NEAR blockchain or from your local testing environment.

In either case, you’ll import each of your components through a component called Widget.

import { Widget } from 'near-social-vm'

The Widget component has several optional attributes, but for now you only need to know about three: src, props, and code.

The src attribute is the path to your component’s JavaScript code, stored onchain.

<Widget src='influencer.testnet/widget/Greeter' />

The props attribute will be JSON data passed into a component. For example, this will return a component reading "3 cheers for Anna!"

<Widget src='influencer.testnet/widget/Greeter' props={{name: 'Anna', amount: 3}} />

The code attribute can accept stringified code and render it to the DOM. One highlight of our create-near-app fork is that we use this as an alternative to src to render local components from /build/data.json, which simulates onchain data. This makes it trivial to write and test local components which are ready to copy-paste onto the blockchain.

Writing local components

The /components folder is where you’ll write new components. NEAR components mostly look like React components, but not wrapped in an exported class or function like MyComponent(). Out of the box, BOS supports styled components; you don’t have to use them, but you might see them if you look at other NEAR components.

Here’s a simple example of a NEAR component.

const userName = props.userName || "Anna";
const [count, setCount] = useState(1);
return (
<div>
<p> {count} cheers for {userName}! </p>
<button onClick={() => setCount(count + 1)}>Cheers!</button>
</div>
);

The only requirement for a NEAR component is a return statement. Usually these return JSX like a React component, but you can return any JavaScript you want.

Testing components

When you’ve finished writing your components, you can make sure they build with this script.

> npm run component-build

This creates a file /build/data.json which encodes all your gateway’s components in one JSON file, simulating the component data you’d find onchain.

You can see those components being referenced in Widgets in /pages/ExamplePage.js, which is the homepage of the example gateway.

Now in your Widgets, you can import these local components by passing the JSON into the code attribute.

import React from 'react'
import { Widget } from 'near-social-vm'
import localComponents from '../../build/data.json'
function ExamplePage({ api }) {
const localUrbitHeader = localComponents['local.components/widget/components.header']
const localPokeUrbit = localComponents['local.components/widget/components.pokeUrbit']
const localScryUrbit = localComponents['local.components/widget/components.scryUrbit']
return (
<div>
<Widget
code={localUrbitHeader.code}
/>
<div style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
height: '500px',
width: '75vw',
margin: 'auto'
}}>
<div>
<Widget
// src={'urbitlabs.near/widget/pokeUrbit'}
code={localPokeUrbit.code}
props={{ api: api }}
/>
</div>
<div>
<Widget
// src={'urbitlabs.near/widget/scryUrbit'}
code={localScryUrbit.code}
props={{ api: api }}
/>
</div>
</div>
</div>
)
}
export default ExamplePage

When you want to see your components in action, you can run a dev server with npm run dev. (This script includes npm run build-component, so you’ll always be working with the latest version of your code.)

Urbit-aware NEAR components

This gateway uses a fork of the NEAR Social VM that includes an Urbit object, and that object has three methods you can use to interact with your ship. The main ones are Urbit.poke() and Urbit.scry().

poke(app, mark, json, OnSuccess, OnError)

Send a poke (like a POST request) to the local ship.

This method takes the app you’d like to send a poke to, the mark that tells the app what kind of poke it is, and a json object with the data you’d like to send. (Note: as in the example below, the json argument can also be a string.)

The OnSuccess and OnError parameters are optional. These allow you to pass in callback functions which will run after the poke has succeeded or failed on the Urbit server.

Urbit.poke('hood', 'helm-hi', 'hello urbit!')
.then(res => {
// code
})

An app’s marks are defined by the developer, so you’ll have to read the app’s source code or documentation to find those, as well as the keys the json object should have.

Take a look at this example Urbit app from the App School course to see marks in context. With JSON keys, the key count in your JSON object should correspond to something like count.action or count.act in the app. If you want to understand more about the code in that example, it’s never been easier to learn Hoon, Urbit’s native functional programming language.

scry(app, path)

Send a scry (like a GET request) to the local ship.

This method takes the app you’d like to scry, and the path of the endpoint in that app. You can find the endpoints for a given app in the on-peek section of the file at /zod/<desk-name>/app/<app-name>.hoon.

As far as frontend developers are concerned a tuple like [%x %charges ~] corresponds to a scry path like /charges. A tuple like [%x %foo %bar ~] corresponds to a scry path like /foo/bar, etc.

Urbit.scry('docket', '/charges')
.then(res => {
// code
})

(Note that in both methods, the app argument corresponds to the name of a “Gall agent” and not a desk. An agent is a combined state machine and API that serves as the backend for an app, and that helps make Urbit software composable by default. An agent could be a small app on its own, or it could be one of several state machines that run a larger app. In any case a desk is a package containing an app. A desk’s agents are the files in its /app folder.)

setApi(api)

Treat this as boilerplate. When you write an Urbit-aware NEAR component, paste in the following two lines at the top and add destructured props to the first line as needed.

let { api } = props
Urbit.setApi(api)

With the api prop, your component can use api.ship and api.url to access the user’s Urbit ID and the host machine’s URL respectively. These are useful for UI text and navigation.