Skip to content

Commit

Permalink
Merge 3602ec5 into b4c7ac2
Browse files Browse the repository at this point in the history
  • Loading branch information
ZoteTheMighty committed Oct 10, 2018
2 parents b4c7ac2 + 3602ec5 commit 352a68f
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@
* Middleware now run left-to-right instead of right-to-left!
* Errors thrown in `changed` event now have correct stack traces ([#27](https://github.com/Roblox/rodux/pull/27))
* Fixed `createReducer` having incorrect behavior with `nil` state values ([#33](https://github.com/Roblox/rodux/pull/33))
* Added `makeActionCreator` utility for common action creator pattern ([#35](https://github.com/Roblox/rodux/pull/35))

## Public Release (December 13, 2017)
* Initial release!
50 changes: 50 additions & 0 deletions docs/api-reference.md
Expand Up @@ -152,6 +152,56 @@ local reducer = createReducer(initialState, {
})
```

### Rodux.makeActionCreator
```
Rodux.makeActionCreator(name, actionGeneratorFunction) -> actionCreator
```

A helper function that can be used to make action creators.

Action creators are helper objects that will generate actions from provided data and automatically populate the `type` field.

Actions often have a structure that looks like this:

```lua
local MyAction = {
type = "SetFoo",
value = 1,
}
```

They are often generated by functions that take the action's data as arguments:

```lua
local function SetFoo(value)
return {
type = "SetFoo",
value = value,
}
end
```

`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:

Make an action creator in `SetFoo.lua`:
```lua
return makeActionCreator("SetFoo", function(value)
-- The action creator will automatically add the 'type' field
return {
value = value,
}
end)
```

Then check for that action by name in `FooReducer.lua`:
```lua
local SetFoo = require(SetFoo)
...
if action.type == SetFoo.name then
-- change some state!
end
```

## Middleware
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`.

Expand Down
30 changes: 29 additions & 1 deletion docs/introduction/actions.md
Expand Up @@ -22,4 +22,32 @@ store:dispatch(ReceivedNewPhoneNumber("15552345678"))
```

!!! info
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`.
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`.

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:

* Instead of functions, action creators returned from `makeActionCreator` are callable tables that also include a `name` field.
* Action creators will automatically populate the `type` field of each action they create using their `name`.

We can define an action creator like this:

```lua
return makeActionCreator("ReceivedNewPhoneNumber", function(phoneNumber)
return {
phoneNumber = phoneNumber,
}
end)
```

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:

```lua
local MyAction = require(MyAction)
...
if action.type == MyAction.name then
-- change some state!
end
```

!!! info
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!
2 changes: 2 additions & 0 deletions lib/init.lua
@@ -1,13 +1,15 @@
local Store = require(script.Store)
local createReducer = require(script.createReducer)
local combineReducers = require(script.combineReducers)
local makeActionCreator = require(script.makeActionCreator)
local loggerMiddleware = require(script.loggerMiddleware)
local thunkMiddleware = require(script.thunkMiddleware)

return {
Store = Store,
createReducer = createReducer,
combineReducers = combineReducers,
makeActionCreator = makeActionCreator,
loggerMiddleware = loggerMiddleware.middleware,
thunkMiddleware = thunkMiddleware,
}
23 changes: 23 additions & 0 deletions lib/makeActionCreator.lua
@@ -0,0 +1,23 @@
--[[
A helper function to define a Rodux action creator with an associated name.
]]
local function makeActionCreator(name, fn)
assert(type(name) == "string", "Bad argument #1: Expecteda string name for the action creator")
assert(type(fn) == "function", "Bad argument #2: Expected a function that creates action objects")

return setmetatable({
name = name,
}, {
__call = function(self, ...)
local result = fn(...)

assert(type(result) == "table", "Invalid action: An action creator must return a table")

result.type = name

return result
end
})
end

return makeActionCreator
69 changes: 69 additions & 0 deletions lib/makeActionCreator.spec.lua
@@ -0,0 +1,69 @@
return function()
local makeActionCreator = require(script.Parent.makeActionCreator)

it("should set the name of the actionCreator creator", function()
local FooAction = makeActionCreator("foo", function()
return {}
end)

expect(FooAction.name).to.equal("foo")
end)

it("should return a table when called as a function", function()
local FooAction = makeActionCreator("foo", function()
return {}
end)

expect(FooAction()).to.be.a("table")
end)

it("should set the type of the action creator", function()
local FooAction = makeActionCreator("foo", function()
return {}
end)

expect(FooAction().type).to.equal("foo")
end)

it("should set values", function()
local FooAction = makeActionCreator("foo", function(value)
return {
value = value
}
end)

expect(FooAction(100).value).to.equal(100)
end)

it("should throw when its result does not return a table", function()
local FooAction = makeActionCreator("foo", function()
return function() end
end)

expect(FooAction).to.throw()
end)

it("should throw if the first argument is not a string", function()
expect(function()
makeActionCreator(nil, function()
return {}
end)
end).to.throw()

expect(function()
makeActionCreator(100, function()
return {}
end)
end).to.throw()
end)

it("should throw if the second argument is not a function", function()
expect(function()
makeActionCreator("foo", nil)
end).to.throw()

expect(function()
makeActionCreator("foo", {})
end).to.throw()
end)
end

0 comments on commit 352a68f

Please sign in to comment.