Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add .use() API method #192

Merged
merged 3 commits into from
Jul 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 68 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,41 @@ const view = (state, prev, send) => {
</div>`
}
```
In this example, when the `Add` button is clicked, the view will dispatch an `add` action that the model’s `add` reducer will receive. [As seen above](#models), the reducer will add an item to the state’s `todos` array. The state change will cause this view to be run again with the new state, and the resulting DOM tree will be used to [efficiently patch the DOM](#does-choo-use-a-virtual-dom).
In this example, when the `Add` button is clicked, the view will dispatch an
`add` action that the model’s `add` reducer will receive. [As seen
above](#models), the reducer will add an item to the state’s `todos` array. The
state change will cause this view to be run again with the new state, and the
resulting DOM tree will be used to [efficiently patch the
DOM](#does-choo-use-a-virtual-dom).

### Plugins
Sometimes it's necessary to change the way `choo` itself works. For example to
report whenever an action is triggered, handle errors globally or perist state
somewhere. This is done through something called `plugins`. Plugins are objects
that contain `hook` functions and are passed to `app.use()`:

```js
const log = require('choo-log')
const choo = require('choo')
const app = choo()

app.use(log())

const tree = app.start()
document.body.appendChild(tree)
```

Generally people using `choo` shouldn't be too worried about the specifics of
`plugins`, as the internal API is (unfortunatly by necessecity) quite complex.
After all they're the most powerful way to modify a `choo` appliction.

__:warning: Warning :warning:: plugins should only be used as a last resort.
It creates peer dependencies which makes upgrading versions and switching
frameworks a lot harder. Please exhaust all other options before using
plugins.__

If you want to learn more about creating your own `plugins`, and which `hooks`
are available, head on over to [app.use()](#appusehooks).

## Badges
Using `choo` in a project? Show off which version you've used using a badge:
Expand All @@ -400,26 +434,8 @@ the first time, consider reading through the [handbook][handbook] or
[concepts](#concepts) first :sparkles:

### app = choo(opts)
Initialize a new `choo` app. Takes an optional object of handlers. Handlers can
be:
- __onError(err, state, createSend):__ called when an `effect` or
`subscription` emit an error. If no handler is passed, the default handler
will `throw` on each error.
- __onAction(action, state, name, caller, createSend):__ called when an
`action` is fired.
- __onStateChange(action, state, prev, caller, createSend):__ called after a
reducer changes the `state`.

`createSend()` is a special function that allows the creation of a new named
`send()` function. The first argument should be a string which is the name, the
second argument is a boolean `callOnError` which can be set to `true` to call
the `onError` hook istead of a provided callback. It then returns a
`send(actionName, data?)` function.

Handlers should be used with care, as they're the most powerful interface into
the state. For application level code it's generally recommended to delegate to
actions inside models using the `send()` call, and only shape the actions
inside the handlers.
Initialize a new `choo` app. Takes an optional object of handlers that is
passed to [app.use()](#appusehooks).

### app.model(obj)
Create a new model. Models modify data and perform IO. Takes the following
Expand Down Expand Up @@ -460,6 +476,37 @@ is URI partials and `send()` can be called to trigger actions. If
`defaultRoute` is passed in, that will be called if no paths match. If no
`defaultRoute` is specified it will throw instead.

### app.use(hooks)
Register an object of hooks on the application. This is useful to extend the
way `choo` works, adding custom behavior and listeners. Generally returning
objects of `hooks` is done by returning them from functions (which we call
`plugins` throughout the documentation).

There are several `hooks` that are picked up by `choo`:
- __onError(err, state, createSend):__ called when an `effect` or
`subscription` emit an error. If no handler is passed, the default handler
will `throw` on each error.
- __onAction(action, state, name, caller, createSend):__ called when an
`action` is fired.
- __onStateChange(action, state, prev, caller, createSend):__ called after a
reducer changes the `state`.

__:warning: Warning :warning:: plugins should only be used as a last resort.
It creates peer dependencies which makes upgrading versions and switching
frameworks a lot harder. Please exhaust all other options before using
plugins.__

`createSend()` is a special function that allows the creation of a new named
`send()` function. The first argument should be a string which is the name, the
second argument is a boolean `callOnError` which can be set to `true` to call
the `onError` hook istead of a provided callback. It then returns a
`send(actionName, data?)` function.

Hooks should be used with care, as they're the most powerful interface into
the state. For application level code it's generally recommended to delegate to
actions inside models using the `send()` call, and only shape the actions
inside the hooks.

### html = app.toString(route, state?)
Render the application to a string of HTML. Useful for rendering on the server.
First argument is a path that's passed to the router. Second argument is an
Expand Down
17 changes: 12 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@ module.exports = choo
function choo (opts) {
opts = opts || {}

const _store = start._store = barracks(xtend(opts, { onStateChange: render }))
const _store = start._store = barracks()
var _router = start._router = null
var _defaultRoute = null
var _rootNode = null
var _routes = null
var _frame = null

_store.use({ onStateChange: render })
_store.use(opts)

start.toString = toString
start.router = router
start.model = model
start.start = start
start.use = use

return start

Expand Down Expand Up @@ -83,10 +87,6 @@ function choo (opts) {
// update the DOM after every state mutation
// (obj, obj, obj, str, fn) -> null
function render (data, state, prev, name, createSend) {
if (opts.onStateChange) {
opts.onStateChange(data, state, prev, name, createSend)
}

if (!_frame) {
_frame = nanoraf(function (state, prev) {
const newTree = _router(state.location.pathname, state, prev)
Expand All @@ -109,6 +109,13 @@ function choo (opts) {
_store.model(model)
}

// register a plugin
// (obj) -> null
function use (hooks) {
assert.equal(typeof hooks, 'object', 'choo.use: hooks should be an object')
_store.use(hooks)
}

// create a new router with a custom `createRoute()` function
// (str?, obj, fn?) -> null
function createRouter (defaultRoute, routes, createSend) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
],
"license": "MIT",
"dependencies": {
"barracks": "^8.0.0",
"barracks": "^8.1.1",
"document-ready": "~1.0.2",
"global": "^4.3.0",
"hash-match": "^1.0.2",
Expand Down