nanofetcher
v0.1.1
Published
nanocomponent with support for fetching async data
Downloads
4
Readme
nanofetcher
nanocomponent with support for fetching async data
Features
- Extends
nanocomponent
with data fetching lifecycle events - Uses a placeholder element and element hydration
- Allows preloading data before component is mounted in DOM
- Supports any type of async data fetching, including remote API calls
- Dedupes unnecessary data fetches
- Implemented in <150 LOC
- Works well with component cachers like
component-box
Installation
$ npm install nanofetcher
Usage
// post.js
var Nanofetcher = require('nanofetcher')
var html = require('bel')
module.exports = Post
function Post () {
if (!(this instanceof Post)) return new Post()
Nanofetcher.call(this)
}
Post.prototype = Object.create(Nanofetcher.prototype)
Post.prototype.constructor = Post
Post.identity = function (postID) {
return String(postID)
}
Post.prototype.init = function (postID) {
this.postID = postID
}
Post.prototype.placeholder = function () {
return html`<div>Loading...</div>`
}
Post.prototype.hydrate = function (postData) {
return html`<div>
<h1>${postData.title}</h1>
<div>${postData.body}</div>
</div>`
}
Post.prototype.fetch = function (cb) {
// fetch async data here, return cb(err) or cb(null, data)
api(this.postID, cb)
}
Post.prototype.update = function (postID) {
return postID !== this.postID
}
// index.js
var choo = require('choo')
var html = require('bel')
var Post = require('./post.js')
var post = new Post()
var app = choo()
app.route('/post/:id', postView)
app.mount('body')
function postView (state, emit) {
return html`
<body>
${post.render(state.params.id)}
</body>
`
}
Patterns
These are some common patterns you might use when writing nanofetcher components.
Prefetching
// index.js
var choo = require('choo')
var html = require('bel')
var Post = require('./post.js')
var post = new Post()
var app = choo()
app.route('/', homeView)
app.route('/post/:id', postView)
app.mount('body')
function homeView (state, emit) {
// prefetch the data for post 10, before the user clicks the link
post.prefetch(10, () => console.log('prefetched!'))
return html`
<body>
<a href="/post/10">My Post</a>
</body>
`
}
function postView (state, emit) {
// will appear instantly if prefetching finished before link was clicked
return html`
<body>
${post.render(state.params.id)}
</body>
`
}
ES6 Classes / Promises
Nanofetcher components can be also be written with ES6 Classes and promises. If fetch
returns a promise, Nanofetcher
will automatically return a promise when prefetch
is called. Other lifecycle hooks will continue to work normally.
(When using promises, you do not need to pass a callback to prefetch
.)
// post.js
var Nanofetcher = require('nanofetcher')
var html = require('bel')
module.exports = Post
class Post extends Nanofetcher {
static identity (postID) {
return String(postID)
}
init (postID) {
this.postID = postID
}
update (postID) {
return this.postID !== postID
}
placeholder () {
return html`<div>Loading...</div>`
}
hydrate (postData) {
return html`<div>
<h1>${postData.title}</h1>
<div>${postData.body}</div>
</div>`
}
fetch () {
// return a promise
return promiseAPI(this.postID)
}
}
var Post = require('./post.js')
var post = new Post()
post.prefetch(1).then(() => { /* post 1 data prefetched */ })
FAQ
What is the component lifecycle?
First, make sure you're familiar with nanocomponent
lifecycle events. Nanofetcher components
have one of two possible lifecycles.
If a component is rendered normally, Nanofetcher splits Nanocomponent's usual
createElement
into two hooks:init
andplaceholder
.init
is used to set instance properties from arguments and kick off anything that needs to run along side node rendering.placeholder
returns an element that represents the component's default or loading state before async data finishes fetching. Once the placeholder element has been mounted in the DOM,load
fires andfetch
is called to begin async data fetching. When thefetch
callback returns,hydrate
is called with the resulting data.hydrate
should return an element that represent's the component's fully-loaded state. When the hydrated element has been morphed into the DOM,done
is called.If a component's data is prefetched before it is rendered,
prefetch
callsinit
to set instance properties from arguments, and thenfetch
to kick off async data fetching. When the component is eventually rendered, Nanocomponent'screateElement
callshydrate
to immediately mount the fully-loaded state of the component. If the component is rendered beforeprefetch
returns,placeholder
will be called instead ofhydrate
in Nanocomponent'screateElement
andhydrate
will be called whenprefetch
returns.
Why shouldn't I implement createElement
?
Nanofetcher calls Nanocomponent's createElement
under the hood. Conceptually, createElement
is replaced by
Nanofetcher's init
+ placeholder
hooks (if an element is rendered without first prefetching) or the hydrate
hook (if an element has already prefetched data). This allows init
to be skipped when an element is created and mounted if it has already been run during prefetching.
How do I know when data is populated and element is mounted?
The done
hook is called after async data has been fetched and the hydrated element has been mounted in the
DOM. If prefetching has finished before the component is rendered, done
will fire at the same time as load
.
Otherwise, load
will fire when the placeholder is mounted and done
will fire when the hydrated element is mounted.
API
component = Nanofetcher()
Create a new Nanofetcher instance. Additional methods can be set on the
prototype. See nanocomponent
for more details
component.render([arguments…])
Render the component. See nanocomponent
for more details.
component.prefetch([arguments…], cb)
Must be called with a callback after render arguments (unless fetch
returns a promise). Prefetch async data before the component
is rendered or mounted in the DOM. Arguments must stay the same when component is rendered. Callback
(function (err) {}
) called when prefetch finishes. Calling prefetch multiple times will only trigger one async
data fetch, but all callbacks will wait until the fetch finishes.
component.rerender()
Re-run .render
using the last arguments
that were passed to the render
call. Render the component. See
nanocomponent
for more details.
component.element
A getter
property that returns the component's DOM node if its mounted in the page and
null
when its not.
String = Nanofetcher.identity([arguments…])
Must be implemented. Static identity
method which returns an id for a given set of arguments. Used
to ensure that async fetched data matches currently mounted element.
Nanofetcher.prototype.init([arguments…])
Called once, either at the beginning of prefetching (if component.prefetch
is called) or the beginning of
Nanocomponent.prototype.createElement()
. Use to set instance properties from arguments.
DOMNode = Nanofetcher.prototype.placeholder()
Must be implemented. Return a DOMNode representing the default or loading state of the component.
This DOMNode will be rendered before async data finishes fetching. Elements returned from placeholder
must always return the same root node type.
DOMNode = Nanofetcher.prototype.hydrate(data)
Must be implemented. Return a DOMNode representing the hydrated, fully-loaded state of the component,
given async fetched data. Elements returned from hydrate
must always return the same root node type and
must share the same root node type as placeholder
.
Nanofetcher.prototype.fetch(cb)
Must be implemented. Implement asynchronous data fetching. Call callback (function (err, data) {}
)
with error or fetched data or return a promise.
Boolean = Nanofetcher.prototype.update([arguments…])
Must be implemented. Return a boolean to determine if
prototype.createElement()
should be called. See nanocomponent
for more details.
Nanofetcher.prototype.done(el)
Called when the component is mounted on the DOM and async data has been fetched & hydrated.
Nanofetcher.prototype.beforerender(el)
A function called right after createElement
returns with el
, but before the fully rendered
element is returned to the render
caller. See nanocomponent
for more details.
Nanofetcher.prototype.load(el)
Called when the component is mounted on the DOM. See nanocomponent
for more details.
Nanofetcher.prototype.unload(el)
Called when the component is removed from the DOM. See nanocomponent
for more details.
the hood.
Nanofetcher.prototype.afterupdate(el)
Called after a mounted component updates (e.g. update
returns true). See nanocomponent
for more details.
Nanofetcher.prototype.afterreorder(el)
Called after a component is re-ordered. See nanocomponent
for more details.