RoduxUtils
A collection of useful functions for projects that use Rodux.
Types
EqualityFn
type
EqualityFn =
(
a:
any?
,
b:
any?
)
→
boolean
Any type of function that compares two values and returns a boolean.
MemoizeOptions
interface
MemoizeOptions {
}
MemoizeOptions can be used to customize the Memoize instance
returned from defaultMemoize
.
ReducerMap
ReducerAndPrepareFn
interface
ReducerAndPrepareFn {
reducer:
Reducer
prepare:
PrepareFn
}
Functions
createSelectorCreator
RoduxUtils.
createSelectorCreator
(
...:
any?
--
any additional arguments to pass to memoizeFn
) →
createSelector
Types
type
MemoizeFn =
(
fn:
function
,
...any?
)
→
function
Used to create a custom version of createSelector
.
local customSelectorCreator = createSelectorCreator(
customMemoize, -- function to be used to memoize `fn`
option1, -- option1 will be passed as the second argument to `customMemoize`
option2 -- option2 will be passed as the third argument to `customMemoize`
)
local customSelector = customSelectorCreator(
{
selectFoo,
selectBar,
},
resultFunc -- resultFunc will be passed as the first argument to `customMemoize`
)
You can find more examples of this in action here.
createSelector
RoduxUtils.
createSelector
(
inputSelectors:
{
Selector
}
,
resultFn:
ResultFn
,
memoizeOptions:
MemoizeOptions?
|
any?
,
--
options for defaultMemoize
, or the first argument to a custom memoization function
...:
any?
--
additional arguments for a custom memoize function
) →
(
)
Create a memoized selector.
local selectTotal = createSelector({
function(state)
return state.values.value1
end,
function(state)
return state.values.value2
end,
}, function(value1, value2)
return value1 + value2
end)
defaultMemoize
RoduxUtils.
defaultMemoize
(
fn:
(
Args...
)
→
any?
,
--
The function you want to memoize
) →
(
)
Memoizes a function. This is the default memoization function for
createSelector
.
createAction
RoduxUtils.
createAction
(
name:
Type
,
prepareFn:
PrepareFn?
) →
ActionCreator
<
Type
>
Similar to Rodux's makeActionCreator
, except any additional data is put
inside of a payload
property by default. This can be overriden with a
custom prepareFn.
Examples
Using the default structure
local todoAdded = createAction("todoAdded")
print(todoAdded("Buy groceries")) -- { type = "todoAdded", payload = "Buy groceries" }
Using a custom structure
local playerJoined = createAction("playerJoined", function(userId)
return {
payload = userId,
isRoblox = userId == 1,
}
end)
print(playerJoined(1)) -- { type = "playerJoined", payload = 1, isRoblox = true }
createReducer
RoduxUtils.
createReducer
(
initialState:
any?
,
defaultHandler:
Reducer?
--
not used with BuilderCallback
) →
Reducer
A simpler way to write a reducer function. Each reducer made with
createReducer
is wrapped in an Immut producer, meaning that you can mutate
the state
value passed into your handlers. You can add cases for specific
action types just like you can with Rodux's createReducer
, but you can
also add Matcher handlers that will run when a condition that you define is
met.
Multiple matchers can run for the same action. They are run sequentially in the order you defined them.
Examples
Callback notation
The recommended way to use createReducer
is by using a ReducerBuilder with
the callback notation. It is more readable than using the map notation.
local reducer = createReducer(initialState, function(builder)
builder
:addCase("itemAdded", function(state, action)
-- you can mutate `state` here, it's fine!
state.items[action.itemId] = action.item
end)
:addCase("itemRemoved", function(state, action)
state.items[action.itemId] = nil
end)
-- run this handler if the action contains a `shouldLog` property
:addMatcher(function(action)
return action.shouldLog == true
end, function(state, action)
-- remember: we can't use table.insert on a draft
Draft.insert(state.loggedActions, action)
end)
-- set a fallback for any time an action is dispatched that isn't
-- handled by a matcher or case reducer
:addDefaultCase(function(state, action)
state.unhandledActions += 1
end)
end)
Map notation
You can also use the map notation if you prefer. It should feel similar to
the createReducer
function that comes with Rodux. You cannot add a default
case when using the map notation.
This is primarily meant for internal use.
local reducer = createReducer(initialState, {
-- case reducers
itemAdded = function(state, action)
state.items[action.itemId] = action.item
end,
itemRemoved = function(state, action)
state.items[action.itemId] = nil
end,
}, {
-- matchers
{
matcher = function(action)
return action.shouldLog == true
end,
reducer = function(state, action)
Draft.insert(state.loggedActions, action)
end,
}
})
-- we can't add a default case with the map notation
createSlice
Types
interface
Slice {
name:
string
reducer:
Reducer
actions:
{
[
string
]
:
ActionCreator
}
,
initialState:
State?
}
Automatically generates a reducer and action creators for you. Uses
createAction
and createReducer
internally, so you're able to customize
each generated action creator and use drafts in the reducers.
Example
Let's create a slice for a todo list. We'll make actions for adding & removing todos from the list, but not for marking a todo as complete. We'll assume our project already has an action for completing tasks, and reuse it in our todo slice.
local todoSlice = createSlice({
name = "todo",
initialState = {},
reducers = {
-- uses createReducer, so you can mutate state because it's a draft
todoRemoved = function(state, action)
state[action.payload] = nil
end,
todoAdded = {
reducer = function(state, action)
state[action.payload.name] = { done = action.payload.done }
end,
-- customize the generated action creator
prepare = function(name, done)
return {
payload = {
name = name,
done = done,
}
}
end,
},
},
-- add additional cases that might be relevant to this slice, but have
-- an action creator generated elsewhere (like in another slice)
extraReducers = function(builder)
builder:addCase("taskCompleted", function(state, action)
local todo = state[action.payload.name]
if todo then
todo.done = true
end
end)
end,
)
Now, let's set up the store and use our slice.
local todosSlice = require(ReplicatedStorage.Todos.slice)
local tasksSlice = require(ReplicatedStorage.Tasks.slice)
local todos = todosSlice.actions
local tasks = tasksSlice.actions
local reducer = Rodux.combineReducers({
todos = todosSlice.reducer,
tasks = tasksSlice.reducer,
})
local store = Store.new(reducer)
-- add a new todo, mark it as not done
store:dispatch(todos.todoAdded("Buy groceries", false))
-- complete a task, and mark the todo as done
store:dispatch(tasks.taskCompleted("Buy groceries")
-- remove the todo entirely
store:dispatch(todos.todoRemoved("Buy groceries"))