Skip to content

Commit f060348

Browse files
Add createAction (#35)
Closes #31. Adds an implementation of the generic action-creator-creator that's used commonly for Rodux projects within Roblox.
1 parent 9e1736a commit f060348

File tree

6 files changed

+175
-1
lines changed

6 files changed

+175
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* Middleware now run left-to-right instead of right-to-left!
1212
* Errors thrown in `changed` event now have correct stack traces ([#27](https://github.com/Roblox/rodux/pull/27))
1313
* Fixed `createReducer` having incorrect behavior with `nil` state values ([#33](https://github.com/Roblox/rodux/pull/33))
14+
* Added `makeActionCreator` utility for common action creator pattern ([#35](https://github.com/Roblox/rodux/pull/35))
1415

1516
## Public Release (2017-12-13)
1617
* Initial release!

docs/api-reference.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,56 @@ local reducer = createReducer(initialState, {
152152
})
153153
```
154154

155+
### Rodux.makeActionCreator
156+
```
157+
Rodux.makeActionCreator(name, actionGeneratorFunction) -> actionCreator
158+
```
159+
160+
A helper function that can be used to make action creators.
161+
162+
Action creators are helper objects that will generate actions from provided data and automatically populate the `type` field.
163+
164+
Actions often have a structure that looks like this:
165+
166+
```lua
167+
local MyAction = {
168+
type = "SetFoo",
169+
value = 1,
170+
}
171+
```
172+
173+
They are often generated by functions that take the action's data as arguments:
174+
175+
```lua
176+
local function SetFoo(value)
177+
return {
178+
type = "SetFoo",
179+
value = value,
180+
}
181+
end
182+
```
183+
184+
`makeActionCreator` looks similar, but it automatically populates the action's type with the action creator's name. This makes it easier to keep track of which actions your reducers are responding to:
185+
186+
Make an action creator in `SetFoo.lua`:
187+
```lua
188+
return makeActionCreator("SetFoo", function(value)
189+
-- The action creator will automatically add the 'type' field
190+
return {
191+
value = value,
192+
}
193+
end)
194+
```
195+
196+
Then check for that action by name in `FooReducer.lua`:
197+
```lua
198+
local SetFoo = require(SetFoo)
199+
...
200+
if action.type == SetFoo.name then
201+
-- change some state!
202+
end
203+
```
204+
155205
## Middleware
156206
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`.
157207

docs/introduction/actions.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,32 @@ store:dispatch(ReceivedNewPhoneNumber("15552345678"))
2222
```
2323

2424
!!! info
25-
In most cases your `action` will be sent directly to the `reducer` to be processed. However, if you specified any `middleware` when initializing your `store`, your `action` might also be processed by that `middleware`.
25+
In most cases your `action` will be sent directly to the `reducer` to be processed. However, if you specified any `middleware` when initializing your `store`, your `action` might also be processed by that `middleware`.
26+
27+
Additionally, Rodux provides a helper method called `makeActionCreator` to generate 'action creators'. These are a lot like the `ReceivedNewPhoneNumber` function above, except for two key differences:
28+
29+
* Instead of functions, action creators returned from `makeActionCreator` are callable tables that also include a `name` field.
30+
* Action creators will automatically populate the `type` field of each action they create using their `name`.
31+
32+
We can define an action creator like this:
33+
34+
```lua
35+
return makeActionCreator("ReceivedNewPhoneNumber", function(phoneNumber)
36+
return {
37+
phoneNumber = phoneNumber,
38+
}
39+
end)
40+
```
41+
42+
Since the `name` of the action creator populates the `type` of the actions it creates, we can use an action creators `name` to identify actions that were created by it. As we'll see in the Reducers section, this is helpful for determining which action we're processing:
43+
44+
```lua
45+
local MyAction = require(MyAction)
46+
...
47+
if action.type == MyAction.name then
48+
-- change some state!
49+
end
50+
```
51+
52+
!!! info
53+
Actions are nothing more than tables with a `type` field, so there are many ways to generate them! If `makeActionCreator` doesn't work for your project, you can always generate actions and action creators however you like!

src/init.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
local Store = require(script.Store)
22
local createReducer = require(script.createReducer)
33
local combineReducers = require(script.combineReducers)
4+
local makeActionCreator = require(script.makeActionCreator)
45
local loggerMiddleware = require(script.loggerMiddleware)
56
local thunkMiddleware = require(script.thunkMiddleware)
67

78
return {
89
Store = Store,
910
createReducer = createReducer,
1011
combineReducers = combineReducers,
12+
makeActionCreator = makeActionCreator,
1113
loggerMiddleware = loggerMiddleware.middleware,
1214
thunkMiddleware = thunkMiddleware,
1315
}

src/makeActionCreator.lua

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--[[
2+
A helper function to define a Rodux action creator with an associated name.
3+
]]
4+
local function makeActionCreator(name, fn)
5+
assert(type(name) == "string", "Bad argument #1: Expected a string name for the action creator")
6+
7+
assert(type(fn) == "function", "Bad argument #2: Expected a function that creates action objects")
8+
9+
return setmetatable({
10+
name = name,
11+
}, {
12+
__call = function(self, ...)
13+
local result = fn(...)
14+
15+
assert(type(result) == "table", "Invalid action: An action creator must return a table")
16+
17+
result.type = name
18+
19+
return result
20+
end
21+
})
22+
end
23+
24+
return makeActionCreator

src/makeActionCreator.spec.lua

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
return function()
2+
local makeActionCreator = require(script.Parent.makeActionCreator)
3+
4+
it("should set the name of the actionCreator creator", function()
5+
local FooAction = makeActionCreator("foo", function()
6+
return {}
7+
end)
8+
9+
expect(FooAction.name).to.equal("foo")
10+
end)
11+
12+
it("should return a table when called as a function", function()
13+
local FooAction = makeActionCreator("foo", function()
14+
return {}
15+
end)
16+
17+
expect(FooAction()).to.be.a("table")
18+
end)
19+
20+
it("should set the type of the action creator", function()
21+
local FooAction = makeActionCreator("foo", function()
22+
return {}
23+
end)
24+
25+
expect(FooAction().type).to.equal("foo")
26+
end)
27+
28+
it("should set values", function()
29+
local FooAction = makeActionCreator("foo", function(value)
30+
return {
31+
value = value
32+
}
33+
end)
34+
35+
expect(FooAction(100).value).to.equal(100)
36+
end)
37+
38+
it("should throw when its result does not return a table", function()
39+
local FooAction = makeActionCreator("foo", function()
40+
return function() end
41+
end)
42+
43+
expect(FooAction).to.throw()
44+
end)
45+
46+
it("should throw if the first argument is not a string", function()
47+
expect(function()
48+
makeActionCreator(nil, function()
49+
return {}
50+
end)
51+
end).to.throw()
52+
53+
expect(function()
54+
makeActionCreator(100, function()
55+
return {}
56+
end)
57+
end).to.throw()
58+
end)
59+
60+
it("should throw if the second argument is not a function", function()
61+
expect(function()
62+
makeActionCreator("foo", nil)
63+
end).to.throw()
64+
65+
expect(function()
66+
makeActionCreator("foo", {})
67+
end).to.throw()
68+
end)
69+
end

0 commit comments

Comments
 (0)