Skip to content

Commit 09eaefd

Browse files
Rodux Documentation (#36)
Update Rodux documentation
1 parent c1998f0 commit 09eaefd

File tree

13 files changed

+349
-4
lines changed

13 files changed

+349
-4
lines changed

docs/advanced/middleware.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Most of the time, calling `Store:dispatch` sends incoming `action` objects directly to the `reducer` to determine what updates should be made to the `state`. This is enough for most cases, but some features would be difficult to implement if this was all Rodux provided. For example:
2+
3+
- Delayed processing of an `action`.
4+
- Logging `action` objects dispatched to our `store`.
5+
- Performing a network request in response to an `action` and storing the response in the `state`.
6+
7+
Rodux has the concept of `middleware` to deal with these sorts of situations.
8+
9+
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 other side effects! When an `action` is dispatched, `middleware` are run in the order they were specified in [`Store.new`](../api-reference.md#storenew) from left to right.
10+
11+
Here is an example of a `middleware` that could be used to delay the processing of `action` objects dispatched to the `store`.
12+
13+
```lua
14+
local reducer = function(state, action)
15+
-- the body of your reducer
16+
end
17+
18+
local initialState = {}
19+
20+
local delayOneSecondMiddleware = function(nextDispatch, store)
21+
return function(action)
22+
delay(1, function()
23+
--[[
24+
nextDispatch passes the action to the next middleware provided
25+
to the store at initialization or to the reducer if the action
26+
has already been processed by all the provided middleware.
27+
]]
28+
nextDispatch(action)
29+
end)
30+
end
31+
end
32+
33+
local store = Rodux.Store.new(reducer, initialState, {
34+
delayOneSecondMiddleware,
35+
})
36+
```
37+
38+
!!! warning
39+
If the `delayOneSecondMiddleware` function did not call `nextDispatch`, then the `action` would not be processed by any other `middleware` in the `middleware` chain or our `reducer`!
40+
41+
Rodux has two `middlewares` available to you out of the box. See [`Middleware`](../api-reference.md#middleware), [`thunkMiddleware`](../api-reference.md#roduxthunkmiddleware), and [`loggerMiddleware`](../api-reference.md#roduxloggermiddleware) for more details.

docs/advanced/thunks.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
The `thunkMiddleware` packaged with Rodux will intercept any `action` dispatched to our `store` that is a Lua *function* and execute that function instead of forwarding our `action` to the `reducer`. These functions (also called thunks) have access to the `store` and are allowed to dispatch `action` objects themselves as necessary.
2+
3+
```lua
4+
local reducer = function(state, action)
5+
--[[
6+
Reducer that handles all actions for our store,
7+
including actions of the type "MadeNewFriends".
8+
]]
9+
end
10+
11+
local initialState = {}
12+
13+
local store = Rodux.Store.new(reducer, initialState, {
14+
Rodux.thunkMiddleware,
15+
})
16+
17+
--[[
18+
Our thunkMiddleware will process this action as a thunk
19+
since it is a Lua function
20+
]]
21+
store:dispatch(function(store)
22+
getAsyncNewFriendsForUser("Sarah", function(result)
23+
store:dispatch({
24+
type = "MadeNewFriends",
25+
newFriends = result,
26+
})
27+
end)
28+
end)
29+
```
30+
31+
Thunks are a simple way to introduce more complex processing of `action` objects, but you may want to consider creating custom [`middleware`](middleware.md) for complex features instead of relying on thunks alone.

docs/api-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ A single middleware is just a function with the following signature:
164164
(nextDispatch, store) -> (action) -> result
165165
```
166166

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!
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 other side effects!
168168

169169
A simple version of Rodux's `loggerMiddleware` is as easy as:
170170

docs/debugging.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
In the future, Rodux will have tools similar to [Redux's DevTools](https://github.com/gaearon/redux-devtools) and will be documented here. For now, we highly recommend using the [`loggerMiddleware`](api-reference.md#roduxloggermiddleware) to observe the `state` as `action` objects are dispatched to your `store`.

docs/example.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
The following is an example of a Rodux store that keeps track of the current user's phone number and the names of their friends. It demonstrates the use of the Rodux `store`, `actions`, `reducers`, and `middleware` in a real world setting. The `loggerMiddleware` has been included to demonstrate how to include `middleware` in your `store` and to provide valuable output in response to dispatched `action` objects.
2+
3+
!!! info
4+
This example assumes that you've successfully [installed Rodux](introduction/installation.md) into `ReplicatedStorage` and placed the contents of the following in a LocalScript under `StarterPlayer/StarterPlayerScripts`!
5+
6+
```lua
7+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
8+
9+
local Rodux = require(ReplicatedStorage.Rodux)
10+
11+
-- Action creator for the ReceivedNewPhoneNumber action
12+
local function ReceivedNewPhoneNumber(phoneNumber)
13+
return {
14+
type = "ReceivedNewPhoneNumber",
15+
phoneNumber = phoneNumber,
16+
}
17+
end
18+
19+
-- Action creator for the MadeNewFriends action
20+
local function MadeNewFriends(listOfNewFriends)
21+
return {
22+
type = "MadeNewFriends",
23+
newFriends = listOfNewFriends,
24+
}
25+
end
26+
27+
-- Reducer for the current user's phone number
28+
local phoneNumberReducer = Rodux.createReducer("", {
29+
ReceivedNewPhoneNumber = function(state, action)
30+
return action.phoneNumber
31+
end,
32+
})
33+
34+
-- Reducer for the current user's list of friends
35+
local friendsReducer = Rodux.createReducer({}, {
36+
MadeNewFriends = function(state, action)
37+
local newState = {}
38+
39+
-- Since state is read-only, we copy it into newState
40+
for index, friend in ipairs(state) do
41+
newState[index] = friend
42+
end
43+
44+
for _, friend in ipairs(action.newFriends) do
45+
table.insert(newState, friend)
46+
end
47+
48+
return newState
49+
end,
50+
})
51+
52+
local reducer = Rodux.combineReducers({
53+
myPhoneNumber = phoneNumberReducer,
54+
myFriends = friendsReducer,
55+
})
56+
57+
local store = Rodux.Store.new(reducer, nil, {
58+
Rodux.loggerMiddleware,
59+
})
60+
61+
store:dispatch(ReceivedNewPhoneNumber("15552345678"))
62+
store:dispatch(MadeNewFriends({
63+
"Cassandra",
64+
"Joe",
65+
}))
66+
67+
--[[
68+
Expected output to the developer console:
69+
70+
Action dispatched: {
71+
phoneNumber = "12345678" (string)
72+
type = "ReceivedNewPhoneNumber" (string)
73+
}
74+
State changed to: {
75+
myPhoneNumber = "12345678" (string)
76+
myFriends = {
77+
}
78+
}
79+
Action dispatched: {
80+
newFriends = {
81+
1 = "Cassandra" (string)
82+
2 = "Joe" (string)
83+
}
84+
type = "MadeNewFriends" (string)
85+
}
86+
State changed to: {
87+
myPhoneNumber = "12345678" (string)
88+
myFriends = {
89+
1 = "Cassandra" (string)
90+
2 = "Joe" (string)
91+
}
92+
}
93+
]]
94+
```

docs/index.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# Home
2-
Rodux is a central state management library based on Dan Abramov's [Redux](http://redux.js.org/) library for JavaScript.
1+
Rodux is a central state management library based on Dan Abramov's [Redux](http://redux.js.org/) library for JavaScript. It exposes a very similar API and implements nearly identical semantics.
32

4-
This documentation is incomplete!
3+
This documentation is based on the structure of Redux's documentation, but is a work in progress. Many things from Redux also apply to Rodux, but if you find anything missing or incorrect, [open an issue on GitHub](https://github.com/Roblox/Rodux/issues)!
4+
5+
!!! info
6+
This documentation assumes some familiarity with Lua. If you're new to Lua, [*Programming in Lua* by Roberto Ierusalimschy](https://www.lua.org/pil/) is a good introduction, and the first edition (for Lua 5.0) is available online for free.

docs/introduction/actions.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Whenever the `state` in your `store` needs to be updated in response to an event, you `dispatch` an `action` to your `store` with any relevant information required to make said update. An `action` is usually a Lua table with a `type` field. They are usually created via an action creator Lua module like the following:
2+
3+
```lua
4+
local function ReceivedNewPhoneNumber(phoneNumber)
5+
return {
6+
type = "ReceivedNewPhoneNumber",
7+
phoneNumber = phoneNumber,
8+
}
9+
end
10+
11+
return ReceivedNewPhoneNumber
12+
```
13+
14+
We can then `dispatch` an `action` to our `store` via [`Store:dispatch`](../api-reference.md#storedispatch) like so:
15+
16+
```lua
17+
local store = Store.new(function(currentState, action)
18+
-- The body of your reducer
19+
end)
20+
21+
store:dispatch(ReceivedNewPhoneNumber("15552345678"))
22+
```
23+
24+
!!! 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`.

docs/introduction/installation.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
There are two supported ways to get started with Rodux.
2+
3+
For our examples, we'll install `Rodux` to `ReplicatedStorage`. In practice, it's okay to install Rodux anywhere you want!
4+
5+
### Method 1: Model File (Roblox Studio)
6+
* Download the `rbxmx` model file attached to the latest release from the [GitHub releases page](https://github.com/Roblox/Rodux/releases).
7+
* Insert the model into Studio into a place like `ReplicatedStorage`
8+
9+
### Method 2: Filesystem
10+
* Copy the `lib` directory into your codebase
11+
* Rename the folder to `Rodux`
12+
* Use a plugin like [Rojo](https://github.com/LPGhatguy/rojo) to sync the files into a place

docs/introduction/motivation.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
As applications become more complex it can be difficult to manage the state of our application in a way that is transparent and compartmentalized. State changes caused by network responses and user input become difficult to follow as our business logic gets spread out between an increasing number of models and views, producing unpredictable and undesirable results. Rodux tries to address this problem by following three core principles.
2+
3+
### Single Source of Truth
4+
By collecting all of our application's state in a single object, we can quickly inspect the entirety of the data backing our business logic. We will no longer need to track down data models squirrelled away in disparate parts of our code base.
5+
6+
### State is Read-only
7+
All changes to our single state object are accomplished by dispatching actions to our store, so which part of our code caused what changes to our underlying data will be completely transparent and reproducible. Features that were once difficult like undo/redo become trivial when all changes to our data are controlled via a single, consistent interface.
8+
9+
### Changes Are Made With Pure Functions
10+
Actions dispatched to our store will be processed by pure functions called reducers. These reducers simply take an action and our store's current state as input and output the store's new state in response to that action. These functions have no side effects and give us a single location to put all of our business logic instead of spreading that business logic out over numerous views and models.
11+
12+
That's all there is to it! The API and tools Rodux provides are relatively simple, but solve many of the most common problems that occur in complex, asynchronous applications by introducing a single paradigm for all of our data management.

docs/introduction/reducers.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
When you initialize your `store` with [`Store.new`](../api-reference.md#storenew), you provide a single function called a `reducer` which will consume any `action` dispatched to your `store` and create a new `state` object based on the current `state` of your `store`.
2+
3+
```lua
4+
local phoneNumberReducer = function(state, action)
5+
if action.type == "ReceivedNewPhoneNumber" then
6+
return action.phoneNumber
7+
end
8+
9+
return state
10+
end
11+
```
12+
13+
Note that `state` is never actually modified by our `reducer`. The `state` of our `store` is *read-only*, so our `reducer` must construct a new `state` object in response to the received `action`.
14+
15+
For complex applications, it is often useful to break down the global `reducer` you provide to the `store` into a set of smaller `reducer` functions, each of which is responsible for a portion of the `state`.
16+
17+
```lua
18+
local friendsReducer = function(state, action)
19+
--[[
20+
The state might be nil the first time this reducer is executed.
21+
In that case, we need to initialize our state to be the empty table.
22+
]]
23+
state = state or {}
24+
25+
if action.type == "MadeNewFriends" then
26+
local newState = {}
27+
28+
-- Since state is read-only, we copy it into newState
29+
for index, friend in ipairs(state) do
30+
newState[index] = friend
31+
end
32+
33+
for _, friend in ipairs(action.newFriends)
34+
table.insert(newState, friend)
35+
end
36+
37+
return newState
38+
end
39+
40+
return state
41+
end
42+
43+
--[[
44+
note that the reducer for our entire application is defined by a table of
45+
sub-reducers where each sub-reducer is responsible for one portion of the
46+
overall state.
47+
]]
48+
local reducer = function(action, state)
49+
return {
50+
myPhoneNumber = phoneNumberReducer(state.myPhoneNumber, action),
51+
myFriends = friendsReducer(state.myFriends, action),
52+
}
53+
end
54+
```
55+
56+
Alternatively, you can use [`Rodux.createReducer`](../api-reference.md#roduxcreatereducer) and [`Rodux.combineReducers`](../api-reference.md#roduxcombinereducers) to generate the same code as seen above. Using `Rodux.createReducer` and `Rodux.combineReducers` to create your `reducer` functions isn't as verbose and is less prone to developer error.
57+
58+
```lua
59+
local phoneNumberReducer = Rodux.createReducer(nil, {
60+
ReceivedNewPhoneNumber = function(state, action)
61+
return action.phoneNumber
62+
end,
63+
})
64+
65+
local friendsReducer = Rodux.createReducer({}, {
66+
MadeNewFriends = function(state, action)
67+
local newState = {}
68+
69+
-- Since state is read-only, we copy it into newState
70+
for index, friend in ipairs(state) do
71+
newState[index] = friend
72+
end
73+
74+
for _, friend in ipairs(action.friends)
75+
table.insert(newState, friend)
76+
end
77+
78+
return newState
79+
end,
80+
})
81+
82+
local reducer = Rodux.combineReducers({
83+
myPhoneNumber = phoneNumberReducer,
84+
myFriends = friendsReducer,
85+
})
86+
```

0 commit comments

Comments
 (0)