Quickstart
This guide will teach you how to build a simple app using Microcosm and React.
What we’ll cover
- Project setup
- Routing
- Presenters
- Domains
- Actions
Project setup
Let’s clone the microcosm-quickstart
repo and install dependencies. Type these commands into your terminal:
git clone https://github.com/vigetlabs/microcosm-quickstart
cd microcosm-quickstart
npm install
Don’t have npm? Learn how to install Node.js and npm here.
Working your way around the application
The starter project will come with everything you need to get started with Microcosm. This also includes a project structure:
src/
├── actions
├── presenters
├── domains
├── effects
├── style
├── views
├── index.js
├── repo.js
└── routes.js
Microcosm follows the Model View Presenter (MVP) architectural pattern. Don’t worry about that too much now, we’ll cover it as we build out a quick application.
Launch the app
Run the following command in your terminal:
# Stop the server at any time with ctrl-c
npm start
This will probably open your default browser to a new tab at http://localhost:3000. Congratulations! You just created and booted your first Microcosm app!
Personalize it
The current site copy is cheerful, but a little too generic. This text lives inside of the application layout file.
Let’s update it, open src/views/layout.js
// src/views/layout.js
import React from 'react'
export default function Layout ({children}) {
return (
<div>
<h1>Solar System</h1>
{children}
</div>
)
}
Foreshadowing things to come…
Define a route
Let’s build an application that shows a list of planets. We’ll place that content in an index route, which will display when a user visits the homepage.
We use react-router
for routing. It’s already included in microcosm-quickstart
, so let’s go ahead and add an index route.
Crack open src/routes.js
and edit it to look like:
// src/routes.js
import React from 'react'
import {Route, IndexRoute} from 'react-router'
import App from './presenters/application'
import Planets from './presenters/planets'
export default (
<Route path="/" component={App}>
<IndexRoute component={Planets} />
</Route>
)
Your build process is probably yelling at you right now with something like:
Error in ./src/routes.js
Module not found: ./presenters/planets in ~/microcosm-quickstart/src
That’s because we haven’t added a presenter yet. We’ll do that next.
Define a presenter
Presenters are just special React components that are charged with higher levels of responsibility. We make a distinction between them and “passive view” components for a couple of reasons:
- Presenters provide an answer for where to keep data operations and dispatch application actions.
- Presenters are a gateway. They keep application concerns outside of the majority of the presentation layer.
- Testing and refactoring are significantly easier because everything in
src/views
is isolated. It has no idea how the application works.
Let’s create a presenter! Make a new JavaScript file at src/presenters/planets.js
:
// src/presenters/planets.js
import React from 'react'
import Presenter from 'microcosm/addons/presenter'
class Planets extends Presenter {
render () {
return (
<ul>
<li>Mercury</li>
<li>Venus</li>
<li>Earth</li>
<li>Mars</li>
<li>Jupiter</li>
<li>Saturn</li>
<li>Uranus</li>
<li>Neptune</li>
<li>Pluto</li>
</ul>
)
}
}
export default Planets
Check out the list of planets in your browser! Now you might be asking yourself:
You said that Presenters isolate the view layer from the application. Where’s the separation?
And you’d be correct! Let’s make a planets list view component. Create a new React component at src/views/planet-list.js
:
// src/views/planet-list.js
import React from 'react'
export default function PlanetList () {
return (
<ul>
<li>Mercury</li>
<li>Venus</li>
<li>Earth</li>
<li>Mars</li>
<li>Jupiter</li>
<li>Saturn</li>
<li>Uranus</li>
<li>Neptune</li>
<li>Pluto</li>
</ul>
)
}
Then update the Planets presenter to use this new view component:
// src/presenters/planets.js
import React from 'react'
import Presenter from 'microcosm/addons/presenter'
import PlanetList from '../views/planet-list'
class Planets extends Presenter {
render () {
return <PlanetList />
}
}
export default Planets
Awesome. Well, not really. Now the PlanetList
component knows all about the data. It shouldn’t care whether or not Pluto’s a real planet. Let’s fix that.
We need to prepare data in the presenter to send down into the PlanetList
view. Presenters can implement a getModel
method that to do just that:
// src/presenters/planets
import React from 'react'
import Presenter from 'microcosm/addons/presenter'
import PlanetList from '../views/planet-list'
class Planets extends Presenter {
getModel () {
return {
planets: () => ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto']
}
}
render () {
const { planets } = this.model
return <PlanetList planets={planets} />
}
}
export default Planets
And in our PlanetList:
// src/views/planet-list.js
import React from 'react'
export default function PlanetList ({ planets = [] }) {
return (
<ul>
{ planets.map(p => <li key={p}>{p}</li>)}
</ul>
)
}
Much better.
Defining a domain
We can do better. Application data really belongs in Microcosm. To do this, we need to make a Planets domain. This domain will define all of the operations for data related to planets. Create a new JavaScript file at ./src/domains/planets.js
:
// src/domains/planets.js
const Planets = {
getInitialState() {
return [
'Mercury', 'Venus', 'Earth', 'Mars',
'Jupiter', 'Saturn', 'Uranus', 'Neptune',
'Pluto'
]
}
}
export default Planets
We call instances of Microcosm repos. An isolated warehouse to manage application state. Hooking up the planets domain is easy:
// src/repo.js
import Microcosm from 'microcosm'
import Planets from './domains/planets'
class Repo extends Microcosm {
setup () {
this.addDomain('planets', Planets)
}
}
export default Repo
Here we’re saying, “Mount the Planets domain to 'planets'
.” It will managing everything under repo.state.planets
.
We can subscribe to that in our Planets presenter. Open it up once more:
import React from 'react'
import Presenter from 'microcosm/addons/presenter'
import PlanetList from '../views/planet-list'
class Planets extends Presenter {
getModel () {
return {
// I'm new. Pull planets out of the repo's state
planets: state => state.planets
}
}
render () {
const { planets } = this.model
return <PlanetList planets={planets} />
}
}
export default Planets
Cool. Each value in the key/value map returned from model
is processed by the Presenter. When given a function, it will invoke it with the current application state.
Awesome. Nice and separated.
Defining an action
Let’s simulate what would happen if you were working with a planets API. In this case, the Planets domain wouldn’t know about all of the planets right on start-up. We need to fetch information from a server.
Microcosm actions are responsible for dealing with asynchronous state. Let’s move the data inside of planets out into an action:
// src/actions/planets.js
export function getPlanets() {
// This isn't *really* an AJAX request, but it
// accomplishes what we want...
return new Promise(function (resolve, reject) {
resolve([
'Mercury', 'Venus', 'Earth', 'Mars',
'Jupiter', 'Saturn', 'Uranus', 'Neptune',
'Pluto'
])
})
}
And in the Planet’s domain, subscribe to it using the register()
function:
// src/domains/planets.js
import {getPlanets} from '../actions/planets'
const Planets = {
getInitialState () {
// Remember, we put the planets data into the action
return []
},
append (planets, data) {
return planets.concat(data)
},
register () {
return {
// Curious? This works because Microcosm assigns a unique
// toString() method to each action pushed into it. That means
// the action can be used as a unique key in an object.
[getPlanets]: this.append
}
}
}
export default Planets
You might have noticed that the planets went missing from the page. Of course! We aren’t asking for them.
Let’s use the Presenter’s setup
lifecycle hook to fetch the planet when the presenter boots up. setup
is given the current repo
for the presenter specifically for this purpose:
// src/presenters/planets.js
import React from 'react'
import Presenter from 'microcosm/addons/presenter'
import PlanetList from '../views/planet-list'
import {getPlanets} from '../actions/planets'
class Planets extends Presenter {
setup (repo) {
repo.push(getPlanets)
}
getModel () {
return {
planets: state => state.planets
}
}
render () {
const { planets } = this.model
return <PlanetList planets={planets} />
}
}
export default Planets
And we’re back! When the router is about to mount the Planets presenter to the page, it will call setup
. This will cause a getPlanets
to get queued up with the application’s repo.
Microcosm will process the action, sending updates to the domains who indicate they want to get updates based on their register
function.
Handling user interaction
This is looking great, but most applications respond to user interaction in some way, so lets look at one way to handle that in Microcosm.
Microcosm provides a couple of options for capturing user input and passing it in to actions. One of these is by using the ActionButton
addon. Let’s modify our View layer to pull that in:
import React from 'react'
import ActionButton from 'microcosm/addons/action-button'
import {addPlanet} from '../actions/planets'
export default function PlanetList ({ planets = [] }) {
return (
<div>
<ul>
{ planets.map(p => <li key={p}>{p}</li>)}
</ul>
<ActionButton action={addPlanet} value="Alpha Centauri">
Add Planet
</ActionButton>
</div>
)
}
When clicked, our button will broadcast the addPlanet
action. This won’t do anything currently because the addPlanet
action doesn’t exist. Let’s create that action and subscribe our Domain to it:
// add to src/actions/planets.js
export function addPlanet (planet) {
return planet
}
// update register() method in src/domains/planets.js
register () {
return {
[getPlanets]: this.append,
[addPlanet]: this.append
}
}
There we have it! Now we have a button in our view which triggers an action that adds a new planet to our data model. When the Domain updates the state with the new planet, our Presenter and View will update accordingly to display the new data.
Wrapping up
That’s it! You’ve just gone through a whirlwind tour of Microcosm and our preferred architecture. Happy trailblazing!