Skip to content

Commit 44d3b9c

Browse files
committed
Init stores with state
1 parent 357d508 commit 44d3b9c

File tree

2 files changed

+59
-23
lines changed

2 files changed

+59
-23
lines changed

index.js

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function Choo (opts) {
4040
this._hasWindow = typeof window !== 'undefined'
4141
this._createLocation = nanolocation
4242
this._loaded = false
43-
this._stores = []
43+
this._stores = [ondomtitlechange]
4444
this._tree = null
4545

4646
// properties that are part of the API
@@ -60,11 +60,13 @@ function Choo (opts) {
6060

6161
// listen for title changes; available even when calling .toString()
6262
if (this._hasWindow) this.state.title = document.title
63-
this.emitter.prependListener(this._events.DOMTITLECHANGE, function (title) {
64-
assert.equal(typeof title, 'string', 'events.DOMTitleChange: title should be type string')
65-
self.state.title = title
66-
if (self._hasWindow) document.title = title
67-
})
63+
function ondomtitlechange (state) {
64+
self.emitter.prependListener(self._events.DOMTITLECHANGE, function (title) {
65+
assert.equal(typeof title, 'string', 'events.DOMTitleChange: title should be type string')
66+
state.title = title
67+
if (self._hasWindow) document.title = title
68+
})
69+
}
6870
}
6971

7072
Choo.prototype.route = function (route, handler) {
@@ -76,11 +78,11 @@ Choo.prototype.route = function (route, handler) {
7678
Choo.prototype.use = function (cb) {
7779
assert.equal(typeof cb, 'function', 'choo.use: cb should be type function')
7880
var self = this
79-
this._stores.push(function () {
81+
this._stores.push(function (state) {
8082
var msg = 'choo.use'
8183
msg = cb.storeName ? msg + '(' + cb.storeName + ')' : msg
8284
var endTiming = nanotiming(msg)
83-
cb(self.state, self.emitter, self)
85+
cb(state, self.emitter, self)
8486
endTiming()
8587
})
8688
}
@@ -91,7 +93,7 @@ Choo.prototype.start = function () {
9193
var self = this
9294
if (this._historyEnabled) {
9395
this.emitter.prependListener(this._events.NAVIGATE, function () {
94-
self._matchRoute()
96+
self._matchRoute(self.state)
9597
if (self._loaded) {
9698
self.emitter.emit(self._events.RENDER)
9799
setTimeout(scrollToAnchor.bind(null, window.location.hash), 0)
@@ -129,10 +131,10 @@ Choo.prototype.start = function () {
129131
}
130132

131133
this._stores.forEach(function (initStore) {
132-
initStore()
134+
initStore(self.state)
133135
})
134136

135-
this._matchRoute()
137+
this._matchRoute(this.state)
136138
this._tree = this._prerender(this.state)
137139
assert.ok(this._tree, 'choo.start: no valid DOM node returned for location ' + this.state.href)
138140

@@ -194,25 +196,26 @@ Choo.prototype.mount = function mount (selector) {
194196
}
195197

196198
Choo.prototype.toString = function (location, state) {
197-
this.state = xtend(this.state, state || {})
199+
state = state || {}
200+
state.events = xtend(this._events)
198201

199202
assert.notEqual(typeof window, 'object', 'choo.mount: window was found. .toString() must be called in Node, use .start() or .mount() if running in the browser')
200203
assert.equal(typeof location, 'string', 'choo.toString: location should be type string')
201-
assert.equal(typeof this.state, 'object', 'choo.toString: state should be type object')
204+
assert.equal(typeof state, 'object', 'choo.toString: state should be type object')
202205

203-
// TODO: pass custom state down to each store.
206+
this.emitter.removeAllListeners()
204207
this._stores.forEach(function (initStore) {
205-
initStore()
208+
initStore(state)
206209
})
207210

208-
this._matchRoute(location)
209-
var html = this._prerender(this.state)
211+
this._matchRoute(state, location)
212+
var html = this._prerender(state)
210213
assert.ok(html, 'choo.toString: no valid value returned for the route ' + location)
211214
assert(!Array.isArray(html), 'choo.toString: return value was an array for the route ' + location)
212215
return typeof html.outerHTML === 'string' ? html.outerHTML : html.toString()
213216
}
214217

215-
Choo.prototype._matchRoute = function (locationOverride) {
218+
Choo.prototype._matchRoute = function (state, locationOverride) {
216219
var location, queryString
217220
if (locationOverride) {
218221
location = locationOverride.replace(/\?.+$/, '')
@@ -223,11 +226,10 @@ Choo.prototype._matchRoute = function (locationOverride) {
223226
}
224227
var matched = this.router.match(location)
225228
this._handler = matched.cb
226-
this.state.href = location
227-
this.state.query = nanoquery(queryString)
228-
this.state.route = matched.route
229-
this.state.params = matched.params
230-
return this.state
229+
state.href = location
230+
state.query = nanoquery(queryString)
231+
state.route = matched.route
232+
state.params = matched.params
231233
}
232234

233235
Choo.prototype._prerender = function (state) {

test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,37 @@ tape('should render on the server with hyperscript', function (t) {
2929
t.equal(res.toString().trim(), exp, 'result was OK')
3030
t.end()
3131
})
32+
33+
tape('should not leak state on render', function (t) {
34+
t.plan(6)
35+
36+
var app = choo()
37+
app.use(store)
38+
39+
var routes = ['foo', 'bar']
40+
var states = routes.map(function (route) {
41+
var state = {}
42+
app.route(`/${route}`, view)
43+
app.toString(`/${route}`, state)
44+
return state
45+
})
46+
47+
for (var i = 0, len = routes.length; i < len; i++) {
48+
t.equal(states[i].test, routes[i], 'store was used')
49+
t.equal(states[i].title, routes[i], 'title was added to state')
50+
}
51+
52+
function store (state, emitter) {
53+
state.test = null
54+
emitter.on('test', function (str) {
55+
t.equal(state.test, null, 'state has been reset')
56+
state.test = str
57+
})
58+
}
59+
60+
function view (state, emit) {
61+
emit('test', state.route)
62+
emit(state.events.DOMTITLECHANGE, state.route)
63+
return html`<body>Hello ${state.route}</body>`
64+
}
65+
})

0 commit comments

Comments
 (0)