This is a more complete example that shows three of the decorators at work together: state, props, and the atomicity guard.
import page from 'page'
import { Route } from 'page-fu'
import React from 'react'
import ReactDOM from 'react-dom'
import { LoadingView, PatientView } from './components'
const asJSON = response => response.json();
const PatientRoute = Route({
getInitialState() {
return {
domNode: document.body.appendChild(document.createElement('div')),
patient: null,
}
},
enter() {
const { patientId } = this.props.params
fetch(`/api/patients/${patientId}`).then(asJSON).then(payload => {
if (this.isActive()) {
this.setState({ patient: payload.patients[0] })
}
})
this.render()
},
stateDidChange() {
this.render()
},
exit() {
// clean up our DOM artifacts
ReactDOM.unmountComponentAtNode(this.state.domNode)
this.state.domNode.remove()
},
render() {
const { domNode, patient } = this.state
if (!patient) {
ReactDOM.render(<LoadingView />, domNode);
}
else {
ReactDOM.render(<PatientView patient={patient} />, domNode)
}
}
})
// register our route handler
page('/patients/:id', PatientRoute.enter)
page.exit('/patients/:id', PatientRoute.exit)
Let's assume that for some reason we want to opt out of using one or some of
the default decorators provided by Route, or maybe we want to extend it
with a new one. We're in luck because as Route is just a composition of
functions, we have the flexibility to do so.
In this example we'll add a new decorator that injects our route with a DOM node (the implementation of which will be left for another example), and pipe it along the withState and withProps decorators provided by page-fu:
import { withState, withProps } from 'page-fu'
import { flow } from 'lodash'
import withDOMNode from './withDOMNode'
const WithStateAndProps = flow([ withState, withProps, withDOMNode ]);
// now you're free to use it just like { Route } from 'page-fu' !
const MyRoute = WithStateAndProps({
enter() {
console.assert(typeof this.props === 'object')
console.assert(typeof this.state === 'object')
console.assert(typeof this.domNode === 'object')
}
})
Building on the previous example, let's now look into the implementation
of such a withDOMNode decorator.
In all respects, it's just like a regular page.js middleware but with two
caveats we need to watch out for:
enter and exit hooks must be called with the right context, this, and forwarded the same arguments they expect (ctx, next)exit in the chain is aware of its next
callback, if it's not, we call it on its behalfimport { withState, withProps, ensureNextIsCalled } from 'page-fu'
import { flow } from 'lodash'
const withDOMNode = instance => {
// grab a reference to the original enter & exit hooks so that we can
// yield to them in our overrides:
//
// Note that they may not always exist so don't assume anything!
const enter = instance.enter || Function.prototype;
// read more about this in the ensureNextIsCalled documentation page
const exit = ensureNextIsCalled(instance.exit);
// we can utilize the closure to guard our private state from the other
// decorators and the handler itself too:
let domNode;
return Object.assign({}, instance, {
enter(ctx, next) {
// set up our state for this dispatch:
domNode = document.createElement('div');
// now yield to the next in chain:
enter.call(this, ctx, next);
},
// any public APIs can go here:
getDOMNode() {
return domNode;
},
exit(ctx, next) {
// wait for the rest of chain to clean up *before* we clean
// up after ourselves:
exit.call(this, ctx, err => {
// now we clean up:
domNode.remove();
domNode = null;
// and finally, let the next handler take over:
next(err);
});
}
})
};