Arch tutorial

As an introduction to Arch, we’ll build a simple application showing a list of items. We pick a listing because it’s a very common use case and the simplest example that demonstrates all the major concepts behind Arch.

Create an Arch application

The easiest way to start is using the Arch CLI. You can install it from npm with

npm install -g arch-cli

To create an Arch application create a new directory and run arch-cli init inside

mkdir demo && cd demo
arch-cli init

This will generate an application skeleton. You can start the application by running

arch-cli s -w

and access the application on http://localhost:3000. The options -w makes the server watch the source files and restart one one has changed so you can live experiment.

Add a route

As part of the example application, you’ll get a route to handle the / URL. In most cases, you want your application’s possible general states to be linkable, i.e. serialised in and reconstructible from the URL.

You can specify routes in the app.ls file in the app directory. This file is responsible for defining your application. You create an Arch application by calling

arch.application.create

and passing in an object with a handful of functions.

To define routes you specify a routes method, which uses a arch.routes.define call to declare different routes in your application. Let’s add a page listing some data.

Add a line at the end of app/app.ls saying

page '/listing', listing

This means the ‘/listing’ URL is handled by a ‘listing’ route component.

To make this line work, you need to create the component itself. To do that create a file in the ‘app/routes’ directory, called ‘listing.ls’ with the following code:

require! {
  './base-route': BaseRoute
  arch
}

# Utility methods from Prelude LS
{map, filter} = require 'prelude-ls'

d = arch.DOM

things =
  * "Hovercraft full of eels"
  * "Ex-parrot"
  * "Eggs, beans, bacon and spam"
  * "Flying circus"

module.exports = class Listing extends BaseRoute
  render: ->
    d.div do
      d.h1 "A list of useful things"
      d.ul do
        things |> map ->
          d.li it

then change the top of app.ls to say

# module dependencies
require! <[ arch ]>
global import require 'prelude-ls'

# route components
require! <[
  ./routes/welcome
  ./routes/listing
  ./routes/not-found
]>

so that we’re able to reference the component and use prelude-ls implementation of map. You can now go to http://localhost:3000/listing and see your page rendered.

Pages (route handlers) in Arch are React components. They all share the same props format, specifically, they all get the application state as their single prop, called app-state. To learn more about routing, read the routing guide.

LiveScript as template language

The listing component code deserves a more detailed explanation. First, we don’t use JSX to define the DOM structure we’re rendering, we use pure LiveScript. Second, we use a thin wrapping layer provided by Arch to make LiveScript a very nice markup language: Each component is a simple function taking its children as arguments - either separately or in an array. Optionally, the first argument is an object with props for the component. Behind the scenes Arch uses React.createElement like JSX would.

The result is an almost haml/slim like template language, that is pure LiveScript. In the example route component we render a div containing a h1 and a p with some text. You can read more about the advantages of the language in the LiveScript section

Adding interaction

What we built so far is, in essence, a static page. To add some interaction, we need our application to have state. Arch handles UI state in its most basic form the same way React itself does - using component’s state. Let’s make our list searchable. In listing.ls add a simple form component:

render: ->
  d.div do
    d.h1 "A list of useful things"
    d.form do
      d.input do
        type: 'search'
        placeholder: 'Search things'
    d.ul do
      things |> map ->
        d.li it

Now we need to add some state handling to make it interactive

matches = (query, item) -->
  item.to-lower-case!.index-of(query.to-lower-case!) > -1

module.exports = class Listing extends BaseRoute
  ->
    @state = query: ''

  render: ->
    d.div do
      d.h1 "A list of useful things"
      d.form do
        d.input do
          type: 'search'
          placeholder: 'Search things'
          value: @state.query
          on-change: ~> @set-state query: it.target.value
      d.ul do
        things
        |> filter matches @state.query
        |> map ->
          d.li it

Notice the use of currying in the definition of the matches function.

React doesn’t go much further than that. In real-world applications however, state rarely spans just a single component. The existence of the Flux architecture is good evidence of the fact.

Centralised state instead of Flux

Arch takes a different approach to state, which is very similar to the Om framework for ClojureScript. In Arch, all shared UI state is kept in a single place, the app-state - application state.

The application state is a “cursor” - a focused view of a part of a larger data structure that can be mutated in a controlled fashion. There is a larger discussion of the application structure in the Arch Architecture section.

Let’s add a list of recent searches into our little demo. Since it will be another listing, we should keep our code DRY and extract the list rendering into a separate component.

# components/list.ls

require! <[ arch ]>
d = arch.DOM

module.exports = class List extends React.Component
  render: ->
    d.ul do
      @props.items |> map ->
        d.li it

Then we can use it in our listing route component

list = arch.DOM require '../components/list.ls'

...

render: ->
  d.div do
    d.h1 "A list of useful things"
    d.form do
      d.input do
        type: 'search'
        placeholder: 'Search things'
        value: @state.query
        on-change: ~> @set-state query: it.target.value
    list do
      items: (things |> filter matches @state.query)

Actually, the filtering functionality seems very common, lets include that in the component too. Move the filtering UI from listing.ls to list.ls. The filtering logic itself is very use-case specific though, so it should be external.

This is now our listing component

# routes/listing.ls
render: ->
  d.div do
    d.h1 "A list of useful things"
    # no more form here
    list do
      query: @state.query
      items: (things |> filter matches @state.query)

and the filterable list

# components/list.ls
render: ->
  d.div do
    d.form do
      d.input do
        type: 'search'
        placeholder: 'Search things'
        value: @props.query
        on-change: ~> # now what?
    d.ul do
      @props.items |> map -> d.li it

This made the route component much simpler, but we now face a new problem - how do we notify whoever is interested that the user changed the query?

The solution is easy in Arch: shared state belongs to the app state. Let’s put both the query and the items there as an initial value in app.ls. Initially we want the query to be empty and we put our list of things in as well.

intial-state =
  query: ''
  items:
    * "Hovercraft full of eels"
    * "Ex-parrot"
    * "Eggs, beans, bacon and spam"
    * "Flying circus"

module.exports = arch.application.create do
  get-initial-state: ->
    initial-state

Then we need to use that list in our listing route. In listing.ls:

  render: ->
    query = @props.app-state.get \state.query
    items = @props.app-state.get \state.items

    d.div do
      d.h1 "A list of useful things"
      list do
        query: query
        items: (items.deref! |> filter matches query.deref!)

As you can see, there is a bit of ceremony going on when using the app-state cursor. That’s because a cursor is an explicit wrapper that acts as a reference into the application state.

Notice we first get the query and items from the app state. The get call returns a cursor backed by the same data the original was. Then we pass the query cursor down to the list component and compute the list of items it should show. To do that, we need the actual values the two new cursors are referring to, which means we need to dereference them. That’s what the deref! call does - it gives back the actual value behind the reference.

There is one more interesting detail in the previous code snippet - the state. prefix in the query and items paths. The app-state cursor in arch reserves the top level keys for use by the framework. At the moment, there are two keys state and route. The value for the state key is the user-defined application state, the value for the route key is the currently matched route, including the matched segment values. This way, the app-state contains all of the application state, including routing information.

You might be thinking “so now we’ve made a couple things much more complicated and got nothing in return”. But now, we can solve our trouble of what to do in our filterable list component.

  render: ->
    d.div do
      d.form do
        d.input do
          type: 'search'
          placeholder: 'Search things'
          value: @props.query.deref!
          on-change: (e) ~>
            @props.query.update -> e.target.value
      d.ul do
        @props.items |> map -> d.li it

Everything works exactly as it did before, except our state is now central, which has countless benefits (see Application as Data for examples). Every time the state gets updated, the whole UI gets automatically re-rendered so we can see our changes (which isn’t nearly as expensive as it sounds partly through the magic of React, partly through optimisations Arch itself can do [and soon will do] thanks to the immutable data structures backing the app-state).

When the user types into the field, we update the query value to the value of the event. The update method actually takes a callback, instead of just taking a new value.

In Arch, the new state behind the cursor is a function of the state before the update. This lets you do in-place updates based on the previous value in a single call. (Arguably this is much less important in a single threaded application, but still has some benefits). You can learn more about how the Arch cursor works in Cursors over Immutable Data.

Let’s finally add the list of recent queries. First we need to keep track of them. In components/list.ls:

module.exports = class List extends React.Component
  ->
    @state = query: ''

  render: ->
    d.div do
      if @props.query
        d.form do
          on-submit: (e) ~>
            e.prevent-default!

            @props.query.update ~> @state.query
            @props.queries.update ~> [@state.query] ++ it
          d.input do
            type: 'search'
            placeholder: 'Search things'
            value: @state.query
            on-change: (e) ~>
              @set-state query: e.target.value
      d.ul do
        @props.items |> map -> d.li it

The list component now takes an additional prop - the list of queries to push into. Since we probably don’t want to track every single character as a new query, we’ll change the behaviour to only submit when the user presses the Enter key submitting the form. Notice the component gained some internal state again to support this behaviour. We also made a small change hiding all the filtering UI if we don’t get any query, this will be useful in a second.

The initial state needs to contain an empty list of recent queries to have somewhere to push queries in. In app.ls let’s add queries array to hold this list:

initial-state =
  query: ''
  items:
    * "Hovercraft full of eels"
      * "Ex-parrot"
      * "Eggs, beans, bacon and spam"
      * "Flying circus"
  queries: []

module.exports = arch.application.create do
  get-initial-state: ->
    initial-state

Rendering the recent queries is as simple as adding another list component to our listing page now.

{map, filter, take} = require 'prelude-ls'

...

  render: ->
    query = @props.app-state.get \state.query
    items = @props.app-state.get \state.items
    queries = @props.app-state.get \state.queries

    d.div do
      d.h1 "A list of useful things"
      list do
        query: query
        items: (items.deref! |> filter matches query.deref!)
        queries: queries

      d.h2 "Recent searches"
      list do
        items: queries.deref! |> take 5

Notice how our route component breaks the app state down and distributes it to its children. This is a very common pattern in Arch and the main way the applications stay modular and components stay decoupled.

You can imagine you can easily make the recent queries clickable to run them again. You just need to pass the query cursor into the second list and implement the interactivity there (at that point, it is probably becoming a different component - one that updates a state key-path with an item from a list.

Connect to a backend API

In the previous state we grew the “state update loop” from component local to application wide. But let’s say we want to make the search actually fetch results from a backend - say Github’s user search.

First let’s think about what this means. We want to respond to the query change by issuing a request to Github API and when we get a results back (asynchronously), update the list of items. This is quite obviously not a job for a React component. The key feature of Arch’s cursor that enables the behaviour is that cursors are observable.

Let’s create a separate module that does what we need.

# observers/github-search.ls
require! {'isomorphic-fetch': 'fetch'}

{map} = require 'prelude-ls'

module.exports = (query, results) ->
  query.on-change ->
    fetch "https://api.github.com/search/users?q=#{it}"
    .then (res) ->
      throw new Error(res.status-text) unless res.status in [200 til 300]
      res
    .then (res) -> res.json!
    .then (body) ->
      results.update ->
        body.items |> map (.login)
    .catch ->

The module exports a function, which observers the query cursor. Whenever it changes, the module starts an API request. Upon getting the results, it updates the items in the app-state, which in turn re-renders the UI.

To support this module, we need an isomorphic-fetch module from npm. Install it with

$ npm install --save isomorphic-fetch

We still need to hook this into the app-state. We do that in the application configuration file.

...

# route components
require! <[
  ./routes/welcome
  ./routes/listing
  ./routes/not-found

  ./observers/github-search
]>

initial-state =
  query: ''
  items: []
  queries: []

module.exports = arch.application.create do
  get-initial-state: ->
    initial-state

  start: (app-state) ->
    query = app-state.get \state.query
    items = app-state.get \state.items

    github-search query, items

...

Notice the search initialisation is again independent of the structure of the app-state itself. It only requires a query cursor to observe and an items cursor to update.

Finally, we need to make a slight change to our listing route:

render: ->
  query = @props.app-state.get \state.query
  items = @props.app-state.get \state.items
  queries = @props.app-state.get \state.queries

  d.div do
    d.h1 "A list of useful things"
    list do
      query: query
      items: items.deref!
      queries: queries

    d.h2 "Recent searches"
    list do
      items: queries.deref! |> take 5

We also no longer need the matches function at the top. This is coincidentally very good for decoupling the UI from the business logic - if we decide to change the search to something completely different, we just switch the search provider in app.ls

If you now type into the search field and hit enter, you should get a list of top 50 matching Github users. You could very easily implement a loading indicator by adding an in-progress flag, turn it on when the request is initiated and flipping it back off when it has finished.

Conclusion

This concludes the introductory Arch tutorial. You may have noticed that Arch focuses primarily on state management. State is absolutely central (no pun intended) to Arch. Most of the advanced features of Arch and applications built on it are only possible because of the strict way application state is managed.

In this tutorial, you’ve seen how there are various scopes of state – sizes of the state loop: component local, global - shared between components, global - shared between the app and an API.

The latter case demonstrated one use-case for state observers, but you can extract various different common tasks into state observers (form validation, domain logic computations, service integrations, metrics collection, persistence…). See Application as Data for a larger discussion of the concept of central state.