Skip to main content

RoduxUtils

A collection of useful functions for projects that use Rodux.

Types

EqualityFn

type EqualityFn = (
aany?,
bany?
) → boolean

Any type of function that compares two values and returns a boolean.

MemoizeOptions

interface MemoizeOptions {
equalityCheckEqualityFn?
resultEqualityCheckEqualityFn?
maxSizenumber?
}

MemoizeOptions can be used to customize the Memoize instance returned from defaultMemoize.

ReducerMap

type ReducerMap = {[string]Reducer | ReducerAndPrepareFn}

ReducerAndPrepareFn

interface ReducerAndPrepareFn {
reducerReducer
preparePrepareFn
}

Functions

createSelectorCreator

RoduxUtils.createSelectorCreator(
memoizeFnMemoizeFn,--

your custom memoization function

...any?--

any additional arguments to pass to memoizeFn

) → createSelector

Types

type MemoizeFn = (
fnfunction,
...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},
resultFnResultFn,
memoizeOptionsMemoizeOptions? | 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

equalityFnOrOptions(EqualityFn | MemoizeOptions)?
) → ()

Memoizes a function. This is the default memoization function for createSelector.

createAction

RoduxUtils.createAction(
nameType,
prepareFnPrepareFn?
) → 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(
initialStateany?,
callbackOrHandlersBuilderCallback | Cases?,
matchersMatchers?,--

not used with BuilderCallback

defaultHandlerReducer?--

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

RoduxUtils.createSlice(optionsSliceOptions<State>) → Slice<State>

Types

interface SliceOptions {
namestring
initialStateState?
reducersReducerMap
extraReducers((builderReducerBuilder) → () | Cases)?
}

interface Slice {
namestring
reducerReducer
actions{[string]ActionCreator},
initialStateState?
}

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"))
Show raw api
{
    "functions": [
        {
            "name": "createSelectorCreator",
            "desc": "Used to create a custom version of `createSelector`.\n\n```lua\nlocal customSelectorCreator = createSelectorCreator(\n\tcustomMemoize, -- function to be used to memoize `fn`\n\toption1, -- option1 will be passed as the second argument to `customMemoize`\n\toption2 -- option2 will be passed as the third argument to `customMemoize`\n)\n\nlocal customSelector = customSelectorCreator(\n\t{\n\t\tselectFoo,\n\t\tselectBar,\n\t},\n\tresultFunc -- resultFunc will be passed as the first argument to `customMemoize`\n)\n```\n\nYou can find more examples of this in action [here](https://github.com/reduxjs/reselect#createselectorcreatormemoize-memoizeoptions).",
            "params": [
                {
                    "name": "memoizeFn",
                    "desc": "your custom memoization function",
                    "lua_type": "MemoizeFn"
                },
                {
                    "name": "...",
                    "desc": "any additional arguments to pass to `memoizeFn`",
                    "lua_type": "any?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "createSelector"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 39,
                "path": "src/createSelectorCreator.luau"
            }
        },
        {
            "name": "createSelector",
            "desc": "Create a memoized selector.\n\n```lua\nlocal selectTotal = createSelector({\n\tfunction(state)\n\t\treturn state.values.value1\n\tend,\n\tfunction(state)\n\t\treturn state.values.value2\n\tend,\n}, function(value1, value2)\n\treturn value1 + value2\nend)\n```\n\t",
            "params": [
                {
                    "name": "inputSelectors",
                    "desc": "",
                    "lua_type": "{ Selector }"
                },
                {
                    "name": "resultFn",
                    "desc": "",
                    "lua_type": "ResultFn"
                },
                {
                    "name": "memoizeOptions",
                    "desc": "options for `defaultMemoize`, or the first argument to a custom memoization function",
                    "lua_type": "MemoizeOptions? | any?"
                },
                {
                    "name": "...",
                    "desc": "additional arguments for a custom memoize function",
                    "lua_type": "any?\n"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 64,
                "path": "src/createSelectorCreator.luau"
            }
        },
        {
            "name": "defaultMemoize",
            "desc": "Memoizes a function. This is the default memoization function for\n`createSelector`.",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function you want to memoize",
                    "lua_type": "(Args...) -> any?"
                },
                {
                    "name": "equalityFnOrOptions",
                    "desc": "",
                    "lua_type": "(EqualityFn | MemoizeOptions)?\n"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 18,
                "path": "src/defaultMemoize/init.luau"
            }
        },
        {
            "name": "createAction",
            "desc": "Similar to Rodux's `makeActionCreator`, except any additional data is put\ninside of a `payload` property by default. This can be overriden with a\ncustom prepareFn.\n\n## Examples\n\n#### Using the default structure\n\n```lua\nlocal todoAdded = createAction(\"todoAdded\")\n\n\nprint(todoAdded(\"Buy groceries\")) -- { type = \"todoAdded\", payload = \"Buy groceries\" }\n```\n\n#### Using a custom structure\n\n```lua\nlocal playerJoined = createAction(\"playerJoined\", function(userId)\n\treturn {\n\t\tpayload = userId,\n\t\tisRoblox = userId == 1,\n\t}\nend)\n\nprint(playerJoined(1)) -- { type = \"playerJoined\", payload = 1, isRoblox = true }\n```",
            "params": [
                {
                    "name": "name",
                    "desc": "",
                    "lua_type": "Type"
                },
                {
                    "name": "prepareFn",
                    "desc": "",
                    "lua_type": "PrepareFn?\n"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "ActionCreator<Type>\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 43,
                "path": "src/createAction.luau"
            }
        },
        {
            "name": "createReducer",
            "desc": "A simpler way to write a reducer function. Each reducer made with\n`createReducer` is wrapped in an Immut producer, meaning that you can mutate\nthe `state` value passed into your handlers. You can add cases for specific\naction types just like you can with Rodux's `createReducer`, but you can\nalso add Matcher handlers that will run when a condition that you define is\nmet. \n\nMultiple matchers can run for the same action. They are run sequentially in \nthe order you defined them.\n\n### Examples\n\n#### Callback notation\n\nThe recommended way to use `createReducer` is by using a ReducerBuilder with\nthe callback notation. It is more readable than using the map notation.\n\n```lua\nlocal reducer = createReducer(initialState, function(builder)\n\tbuilder\n\t\t:addCase(\"itemAdded\", function(state, action)\n\t\t\t-- you can mutate `state` here, it's fine!\n\t\t\tstate.items[action.itemId] = action.item\n\t\tend)\n\t\t:addCase(\"itemRemoved\", function(state, action)\n\t\t\tstate.items[action.itemId] = nil\n\t\tend)\n\t\t-- run this handler if the action contains a `shouldLog` property\n\t\t:addMatcher(function(action)\n\t\t\treturn action.shouldLog == true\n\t\tend, function(state, action)\n\t\t\t-- remember: we can't use table.insert on a draft\n\t\t\tDraft.insert(state.loggedActions, action)\n\t\tend)\n\t\t-- set a fallback for any time an action is dispatched that isn't\n\t\t-- handled by a matcher or case reducer\n\t\t:addDefaultCase(function(state, action)\n\t\t\tstate.unhandledActions += 1\n\t\tend)\nend)\n```\n\n#### Map notation\n\nYou can also use the map notation if you prefer. It should feel similar to \nthe `createReducer` function that comes with Rodux. You cannot add a default\ncase when using the map notation.\n\nThis is primarily meant for internal use.\n\n```lua\nlocal reducer = createReducer(initialState, {\n\t-- case reducers\n\titemAdded = function(state, action)\n\t\tstate.items[action.itemId] = action.item\n\tend,\n\titemRemoved = function(state, action)\n\t\tstate.items[action.itemId] = nil\n\tend,\n}, {\n\t-- matchers\n\t{\n\t\tmatcher = function(action)\n\t\t\treturn action.shouldLog == true\n\t\tend,\n\t\treducer = function(state, action)\n\t\t\tDraft.insert(state.loggedActions, action)\n\t\tend,\n\t}\n})\n-- we can't add a default case with the map notation\n```",
            "params": [
                {
                    "name": "initialState",
                    "desc": "",
                    "lua_type": "any?"
                },
                {
                    "name": "callbackOrHandlers",
                    "desc": "",
                    "lua_type": "BuilderCallback | Cases?"
                },
                {
                    "name": "matchers",
                    "desc": "not used with BuilderCallback",
                    "lua_type": "Matchers?"
                },
                {
                    "name": "defaultHandler",
                    "desc": "not used with BuilderCallback",
                    "lua_type": "Reducer?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Reducer"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 93,
                "path": "src/createReducer.luau"
            }
        },
        {
            "name": "createSlice",
            "desc": "Automatically generates a reducer and action creators for you. Uses\n`createAction` and `createReducer` internally, so you're able to customize\neach generated action creator and use drafts in the reducers.\n\n### Example\n\nLet's create a slice for a todo list. We'll make actions for adding &\nremoving todos from the list, but not for marking a todo as complete.\nWe'll assume our project already has an action for completing tasks, and\nreuse it in our todo slice.\n\n```lua\nlocal todoSlice = createSlice({\n\tname = \"todo\", \n\tinitialState = {},\n\treducers = {\n\t\t-- uses createReducer, so you can mutate state because it's a draft\n\t\ttodoRemoved = function(state, action)\n\t\t\tstate[action.payload] = nil\n\t\tend,\n\t\ttodoAdded = {\n\t\t\treducer = function(state, action)\n\t\t\t\tstate[action.payload.name] = { done = action.payload.done }\n\t\t\tend,\n\t\t\t-- customize the generated action creator\n\t\t\tprepare = function(name, done)\n\t\t\t\treturn {\n\t\t\t\t\tpayload = {\n\t\t\t\t\t\tname = name,\n\t\t\t\t\t\tdone = done,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tend,\n\t\t},\n\t},\n\t-- add additional cases that might be relevant to this slice, but have\n\t-- an action creator generated elsewhere (like in another slice)\n\textraReducers = function(builder)\n\t\tbuilder:addCase(\"taskCompleted\", function(state, action)\n\t\t\tlocal todo = state[action.payload.name]\n\n\t\t\tif todo then\n\t\t\t\ttodo.done = true\n\t\t\tend\n\t\tend)\n\tend,\n)\n```\n\nNow, let's set up the store and use our slice.\n\n```lua\nlocal todosSlice = require(ReplicatedStorage.Todos.slice)\nlocal tasksSlice = require(ReplicatedStorage.Tasks.slice)\n\nlocal todos = todosSlice.actions\nlocal tasks = tasksSlice.actions\n\nlocal reducer = Rodux.combineReducers({\n\ttodos = todosSlice.reducer,\n\ttasks = tasksSlice.reducer,\n})\n\nlocal store = Store.new(reducer)\n\n-- add a new todo, mark it as not done\nstore:dispatch(todos.todoAdded(\"Buy groceries\", false))\n\n-- complete a task, and mark the todo as done\nstore:dispatch(tasks.taskCompleted(\"Buy groceries\")\n\n-- remove the todo entirely\nstore:dispatch(todos.todoRemoved(\"Buy groceries\"))\n```",
            "params": [
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "SliceOptions<State>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Slice<State>\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 157,
                "path": "src/createSlice.luau"
            }
        }
    ],
    "properties": [],
    "types": [
        {
            "name": "MemoizeFn",
            "desc": "",
            "lua_type": "(fn: function, ...any?) -> function",
            "source": {
                "line": 11,
                "path": "src/createSelectorCreator.luau"
            }
        },
        {
            "name": "EqualityFn",
            "desc": "Any type of function that compares two values and returns a boolean.",
            "lua_type": "(a: any?, b: any?) -> boolean",
            "source": {
                "line": 7,
                "path": "src/defaultMemoize/types.luau"
            }
        },
        {
            "name": "MemoizeOptions",
            "desc": "MemoizeOptions can be used to customize the Memoize instance\nreturned from `defaultMemoize`.",
            "fields": [
                {
                    "name": "equalityCheck",
                    "lua_type": "EqualityFn?",
                    "desc": ""
                },
                {
                    "name": "resultEqualityCheck",
                    "lua_type": "EqualityFn?",
                    "desc": ""
                },
                {
                    "name": "maxSize",
                    "lua_type": "number?",
                    "desc": ""
                }
            ],
            "source": {
                "line": 11,
                "path": "src/defaultMemoize/getOptions.luau"
            }
        },
        {
            "name": "ReducerMap",
            "desc": "",
            "lua_type": "{ [string]: Reducer | ReducerAndPrepareFn }",
            "source": {
                "line": 17,
                "path": "src/createSlice.luau"
            }
        },
        {
            "name": "ReducerAndPrepareFn",
            "desc": "",
            "fields": [
                {
                    "name": "reducer",
                    "lua_type": "Reducer",
                    "desc": ""
                },
                {
                    "name": "prepare",
                    "lua_type": "PrepareFn",
                    "desc": ""
                }
            ],
            "source": {
                "line": 25,
                "path": "src/createSlice.luau"
            }
        },
        {
            "name": "SliceOptions",
            "desc": "",
            "fields": [
                {
                    "name": "name",
                    "lua_type": "string",
                    "desc": ""
                },
                {
                    "name": "initialState",
                    "lua_type": "State?",
                    "desc": ""
                },
                {
                    "name": "reducers",
                    "lua_type": "ReducerMap",
                    "desc": ""
                },
                {
                    "name": "extraReducers",
                    "lua_type": "((builder: ReducerBuilder) -> () | Cases)?",
                    "desc": ""
                }
            ],
            "source": {
                "line": 35,
                "path": "src/createSlice.luau"
            }
        },
        {
            "name": "Slice",
            "desc": "",
            "fields": [
                {
                    "name": "name",
                    "lua_type": "string",
                    "desc": ""
                },
                {
                    "name": "reducer",
                    "lua_type": "Reducer",
                    "desc": ""
                },
                {
                    "name": "actions",
                    "lua_type": "{ [string]: ActionCreator },",
                    "desc": ""
                },
                {
                    "name": "initialState",
                    "lua_type": "State?",
                    "desc": ""
                }
            ],
            "source": {
                "line": 50,
                "path": "src/createSlice.luau"
            }
        }
    ],
    "name": "RoduxUtils",
    "desc": "A collection of useful functions for projects that use Rodux.",
    "source": {
        "line": 6,
        "path": "src/init.luau"
    }
}