Skip to content

Commit abe2847

Browse files
authored
Introduce new middleware API (#29)
1 parent d1161cd commit abe2847

File tree

6 files changed

+111
-22
lines changed

6 files changed

+111
-22
lines changed

CHANGELOG.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
# Rodux Changelog
22

33
## Current master
4-
* No changes
4+
* Added `combineReducers` utility, mirroring Redux's ([#9](https://github.com/Roblox/rodux/pull/9))
5+
* Added `createReducer` utility, similar to `redux-create-reducer` ([#10](https://github.com/Roblox/rodux/pull/10))
6+
* `type` is now required as a field on all actions
7+
* Introduced middleware ([#13](https://github.com/Roblox/rodux/pull/13))
8+
* Thunks are no longer enabled by default, use `Rodux.thunkMiddleware` to add them back.
9+
* Added `Rodux.loggerMiddleware` as a simple debugger
10+
* The middleware API changed in [#29](https://github.com/Roblox/rodux/pull/29) in a backwards-incompatible way!
11+
* Middleware now run left-to-right instead of right-to-left!
12+
* Errors thrown in `changed` event now have correct stack traces ([#27](https://github.com/Roblox/rodux/pull/27))
513

6-
## 1.0.0 (TODO: Date)
7-
* Initial release
14+
## Public Release (December 13, 2017)
15+
* Initial release!

docs/api-reference.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,30 +153,41 @@ local reducer = createReducer(initialState, {
153153
```
154154

155155
## Middleware
156-
Rodux provides an API that allows changing the way that actions are dispatched called *middleware*. To attach middlewares to a store, pass a list of middleware as the third argument to `Store.new`.
156+
Rodux provides an API that allows changing the way that actions are dispatched called *middleware*. To attach middleware to a store, pass a list of middleware as the third argument to `Store.new`.
157+
158+
!!! warn
159+
The middleware API changed in [#29](https://github.com/Roblox/rodux/pull/29) -- middleware written against the old API will not work!
157160

158161
A single middleware is just a function with the following signature:
159162

160163
```
161-
(next) -> (store, action) -> result
164+
(nextDispatch, store) -> (action) -> result
162165
```
163166

164-
That is, middleware is a function that accepts the next middleware to apply and returns a new function. That function takes the `Store` and the current action and can dispatch more actions, log to output, or do network requests!
167+
A middleware is a function that accepts the next dispatch function in the *middleware chain*, as well as the store the middleware is being used with, and returns a new function. That function is called whenever an action is dispatched and can dispatch more actions, log to output, or perform any side effects!
165168

166169
A simple version of Rodux's `loggerMiddleware` is as easy as:
167170

168171
```lua
169-
local function simpleLogger(next)
170-
return function(store, action)
172+
local function simpleLogger(nextDispatch, store)
173+
return function(action)
171174
print("Dispatched action of type", action.type)
172175

173-
return next(store, action)
176+
return nextDispatch(action)
174177
end
175178
end
176179
```
177180

178181
Rodux also ships with several middleware that address common use-cases.
179182

183+
To apply middleware, pass a list of middleware as the third argument to `Store.new`:
184+
185+
```lua
186+
local store = Store.new(reducer, initialState, { simpleLogger })
187+
```
188+
189+
Middleware runs from left to right when an action is dispatched. That means that if a middleware does not call `nextDispatch` when handling an action, any middleware after it will not run.
190+
180191
### Rodux.loggerMiddleware
181192
A middleware that logs actions and the new state that results from them.
182193

lib/Store.lua

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,19 @@ function Store.new(reducer, initialState, middlewares)
4848
table.insert(self._connections, connection)
4949

5050
if middlewares then
51-
local dispatch = Store.dispatch
52-
for _, middleware in ipairs(middlewares) do
53-
dispatch = middleware(dispatch)
51+
local unboundDispatch = self.dispatch
52+
local dispatch = function(...)
53+
return unboundDispatch(self, ...)
5454
end
5555

56-
self.dispatch = dispatch
56+
for i = #middlewares, 1, -1 do
57+
local middleware = middlewares[i]
58+
dispatch = middleware(dispatch, self)
59+
end
60+
61+
self.dispatch = function(self, ...)
62+
return dispatch(...)
63+
end
5764
end
5865

5966
return self

lib/Store.spec.lua

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,30 +36,93 @@ return function()
3636
end)
3737

3838
it("should modify the dispatch method when middlewares are passed", function()
39+
local middlewareInstantiateCount = 0
3940
local middlewareInvokeCount = 0
41+
local passedDispatch
42+
local passedStore
43+
local passedAction
4044

4145
local function reducer(state, action)
46+
if action.type == "test" then
47+
return "test state"
48+
end
49+
4250
return state
4351
end
4452

45-
local function testMiddleware(next)
46-
return function(store, action)
53+
local function testMiddleware(nextDispatch, store)
54+
middlewareInstantiateCount = middlewareInstantiateCount + 1
55+
passedDispatch = nextDispatch
56+
passedStore = store
57+
58+
return function(action)
4759
middlewareInvokeCount = middlewareInvokeCount + 1
48-
next(store, action)
60+
passedAction = action
61+
62+
nextDispatch(action)
4963
end
5064
end
5165

5266
local store = Store.new(reducer, "initial state", { testMiddleware })
5367

68+
expect(middlewareInstantiateCount).to.equal(1)
69+
expect(middlewareInvokeCount).to.equal(0)
70+
expect(passedDispatch).to.be.a("function")
71+
expect(passedStore).to.equal(store)
72+
5473
store:dispatch({
5574
type = "test",
5675
})
5776

77+
expect(middlewareInstantiateCount).to.equal(1)
5878
expect(middlewareInvokeCount).to.equal(1)
79+
expect(passedAction.type).to.equal("test")
80+
81+
store:flush()
82+
83+
expect(store:getState()).to.equal("test state")
5984

6085
store:destruct()
6186
end)
6287

88+
it("should execute middleware left-to-right", function()
89+
local events = {}
90+
91+
local function reducer(state)
92+
return state
93+
end
94+
95+
local function middlewareA(nextDispatch, store)
96+
table.insert(events, "instantiate a")
97+
return function(action)
98+
table.insert(events, "execute a")
99+
return nextDispatch(action)
100+
end
101+
end
102+
103+
local function middlewareB(nextDispatch, store)
104+
table.insert(events, "instantiate b")
105+
return function(action)
106+
table.insert(events, "execute b")
107+
return nextDispatch(action)
108+
end
109+
end
110+
111+
local store = Store.new(reducer, 5, { middlewareA, middlewareB })
112+
113+
expect(#events).to.equal(2)
114+
expect(events[1]).to.equal("instantiate b")
115+
expect(events[2]).to.equal("instantiate a")
116+
117+
store:dispatch({
118+
type = "test",
119+
})
120+
121+
expect(#events).to.equal(4)
122+
expect(events[3]).to.equal("execute a")
123+
expect(events[4]).to.equal("execute b")
124+
end)
125+
63126
it("should send an initial action with a 'type' field", function()
64127
local lastAction
65128
local callCount = 0

lib/loggerMiddleware.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ local loggerMiddleware = {
3939
outputFunction = print,
4040
}
4141

42-
function loggerMiddleware.middleware(next)
43-
return function(store, action)
44-
local result = next(store, action)
42+
function loggerMiddleware.middleware(nextDispatch, store)
43+
return function(action)
44+
local result = nextDispatch(action)
4545

4646
loggerMiddleware.outputFunction(("Action dispatched: %s\nState changed to: %s"):format(
4747
prettyPrint(action),

lib/thunkMiddleware.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
This middleware consumes the function; middleware further down the chain
55
will not receive it.
66
]]
7-
local function thunkMiddleware(next)
8-
return function(store, action)
7+
local function thunkMiddleware(nextDispatch, store)
8+
return function(action)
99
if typeof(action) == "function" then
1010
return action(store)
1111
else
12-
return next(store, action)
12+
return nextDispatch(action)
1313
end
1414
end
1515
end

0 commit comments

Comments
 (0)