Skip to main content

Store

The Store object is one of the API changes going from Rocrastinate to Helium, as it is no longer a function that returns a table and is instead a proper Lua object. The Store object is inspired by Redux, a state management library that is often used in JavaScript web applications. Like Redux, the Store is where we can centralize the state of our application. It uses "Actions" to update the application state, and "Reducers" to control how the state changes with a given action.

Helium's Store is NOT equivalent to Redux or Rodux. Some major differences are as follows:

  • Helium stores must be passed as the first argument in the constructors of components that use Store objects. This means that the "store" a component is in is not determined by context, but by explicit argument.
  • Redux reduces actions by re-creating the entire application state. For the sake of optimization, enabled by the coupling of Helium Components with Store, Helium Store reducers are passed the functions GetState and SetState, which copy/mutate the application's state respectively.
  • With React/Redux (or Roact/Rodux), changes to the store will immediately re-render a component. In contrast, changes in a Helium store will immediately call QueueUpdate(), which defers rendering changes to the next frame binding.

Actions

"Actions" are the only source of information for our Helium Store. They represent information needed to change some portion of the application state, and are represented as lua objects. They are sent using Store:Fire(Action) or Store:Dispatch(Action).

Actions should typically be represented as tables, and have a Type property denoting what kind of action is being sent. For example:

local MyAction = {
	Type = "AddCoins";
	Amount = 1;
}

Store:Fire(MyAction)
typedef GenericDictionary table<LuaString, any>;

GenericDictionary MyAction = table(
	Type: L"AddCoins",
	Amount: 1,
);

Store::Fire(MyAction);

Action Creators

Typically, instead of creating actions directly, we can use "Action Creators", which are simply functions that create actions for us with a given set of arguments. Note that these only create the action, and do not dispatch them:

local function AddCoins(Amount: number)
	return {
		Type = "AddCoins";
		Amount = Amount;
	}
end

Store:Fire(AddCoins(1))
typedef GenericDictionary table<LuaString, any>;

GenericDictionary AddCoins(int Amount)
{
	return table(
		Type: L"AddCoins",
		Amount: Amount,
	);
}

Store::Fire(AddCoins(1));

Actions can be dispatched at any time from anywhere in the application, including the middle of a Redraw().

Helium actually has a built-in function used to creating this, called MakeActionCreator. This can be used as seen in the following code:

local AddCoins = Helium.MakeActionCreator("AddCoins", function(Amount: number)
	return {
		Amount = Amount;
	}
end)

Store:Fire(AddCoins(1))
typedef GenericDictionary table<LuaString, any>;

GenericDictionary AddCoinsAction(int Amount)
{
	return table(Amount: Amount);
}

void AddCoins = Helium.MakeActionCreator(L"AddCoins", AddCoinsAction);

Store::Fire(AddCoins(1));

Responding to Actions

Like Redux, Helium uses "Reducers", which are functions that respond to an action by modifying a certain portion of the store's state.

Reducers are given three arguments: (Action, GetState, SetState).

  • Action is the action that was dispatched.
  • GetState(...KeyPath) is a function that gets a value in the store by nested keys.
  • SetState(...KeyPath, Value) is a function that sets a value in the store by nested keys.

If we want to set the value of Coins in the store whenever an AddCoins action is received, we can use the following code:

local function Reducer(Action, GetState, SetState)
	if Action.Type == "AddCoins" then
		local Coins = GetState("Coins")
		SetState("Coins", Coins + Action.Amount)
	end
end

If you're using the MakeActionCreator function, you can set it up like so:

local AddCoins = require(ReplicatedStorage.AddCoins)

local function Reducer(Action, GetState, SetState)
	if Action.Type == AddCoins.ActionName then
		local Coins = GetState("Coins")
		SetState("Coins", Coins + Action.Amount)
	end
end

This code makes a few assumptions:

  1. There is already a value in the store named Coins, and that it is a number.
  2. That the action has a property named Type.
  3. That the action (which we've identified as an AddCoins action) has a property named Amount, and that it is a number.

It is generally best to centralize actions or action creators in an Actions module, so that these assumptions can be standardized. Additionally, we need to declare the initial state of our store somewhere:

local InitialState = {
	Coins = 0;
}

Then, when we call

Store:Fire(AddCoins(1))

our store state should conceptually be mutated to look something like this table:

{
	Coins = 1;
}

Additionally, we can nest tables in our store structure:

local InitialState = {
	PlayerStats = {Coins = 0};
}

local function Reducer(Action, GetState, SetState)
	if Action.Type == AddCoins.ActionName then
		local Coins = GetState("PlayerStats", "Coins")
		SetState("PlayerStats", "Coins", Coins + Action.Amount)
	end
end
GenericDictionary InitialState = table(PlayerStats: table(Coins: 0));

void Reducer(GenericDictionary Action, void GetState, SetState)
{
	if Action.Type == AddCoins.ActionName
	{
		int Coins = GetState(L"PlayerStats", L"Coins");
		SetState(L"PlayerStats", L"Coins", Coins + Action.Amount);
	}
}

In the above example, we provide an aditional argument to GetState and SetState. These are just strings representing the path of nested keys leading to the exact value we want to get/set in our store.

If we kept this all in the same module, we may run into a problem when our tree becomes more complex:

local function Reducer(Action, GetState, SetState)
	if Action.Type == "DoSomethingInASpecificDomain" then
		SetState("Path", "To", "Specific", "Domain", Value)
	elseif  ...  then
		...
	end
end
void Reducer(GenericDictionary Action, void GetState, SetState)
{
	if Action.Type == L"DoSomethingInASpecificDomain"
	{
		SetState(L"Path", L"To", L"Specific", L"Domain", Action.Value);
	} elseif ...
	{
		...
	}
}

This can become very verbose. It would be much simpler if we could create a reducer that just deals with playerStats, and another reducer that just deals with some other domain.

To do this, you can use the CombineReducers() function. Let's say we put our main reducer in a module called "RootReducer", and nested reducers for playerStats underneath the root reducer:


RootReducer ModuleScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Helium = require(ReplicatedStorage.Helium)
local PlayerStats = require(script.PlayerStats)

local Reducer = Helium.CombineReducers({
	PlayerStats = PlayerStats.Reducer;
})

local InitialState = {
	PlayerStats = PlayerStats.InitialState;
}

return {
	Reducer = Reducer;
	InitialState = InitialState;
}

RootReducer.PlayerStats ModuleScript

local function Reducer(Action, GetState, SetState)
	if Action.Type == "AddCoins" then
		local Coins = GetState("Coins")
		SetState("Coins", Coins + Action.Amount)
	end
end

local InitialState = {Coins = 0;}
return {
	Reducer = Reducer;
	InitialState = InitialState;
}

If we wanted to, we could subdivide this even further by making a reducer for coins, and use CombineReducers() in the PlayerStats module instead. The "coins" module would then look something like this:

RootReducer.PlayerStats.Coins ModuleScript

local function Reducer(Action, GetState, SetState)
	if Action.Type == "AddCoins" then
		SetState(GetState() + Action.Amount)
	end
end

local InitialState = 0

return {
	Reducer = Reducer;
	InitialState = InitialState;
}

Now that we've separated the concerns of our reducers and actions, how do we actually create the store and have it interact with our application?

Helium uses the function Store.new(Reducer, InitialState) for this. Putting it all together, we can create a very simple store that reduces a single value of "Coins"

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Helium = require(ReplicatedStorage.Helium)

-- Typically this would be put in a separate module called "Actions"
local AddCoins = Helium.MakeActionCreator("AddCoins", function(Amount: number)
	return {
		Amount = Amount;
	}
end)

-- Typically this would be put in a separate module called "Reducer" or "RootReducer"
local function Reducer(Action, GetState, SetState)
	if Action.Type == AddCoins.ActionName then
		SetState(GetState() + Action.Amount)
	end
end

local InitialState = 0
local CoinsStore = Helium.Store.new(Reducer, InitialState) -- You can also do Helium.CreateStore(Reducer, InitialState)

print(CoinsStore:GetState()) -- 0

CoinsStore:Fire(AddCoins(10))
print(CoinsStore:GetState()) -- 10

CoinsStore:Dispatch(AddCoins(10)) -- Dispatch is also valid
print(CoinsStore:GetState()) -- 20
typedef GenericDictionary table<LuaString, any>;
RbxInstance ReplicatedStorage = game::GetService @ ReplicatedStorage;
GenericDictionary Helium = require(ReplicatedStorage.Helium);

const int INITIAL_STATE = 0;

// Typically this would be put in a separate module called "Actions"
GenericDictionary AddCoinsFunction(int Amount)
{
	return table(Amount: Amount);
}

void AddCoins = Helium.MakeActionCreator(L"AddCoins", AddCoinsFunction);

// Typically this would be put in a separate module called "Reducer" or "RootReducer"
void Reducer(GenericDictionary Action, void GetState, SetState)
{
	if Action.Type == AddCoins.ActionName
	{
		SetState(GetState() + Action.Amount);
	}
}

entry void Main(void)
{
	GenericDictionary CoinsStore = Helium.Store.new(Reducer, INITIAL_STATE); // You can also do Helium.CreateStore(Reducer, InitialState)
	print(CoinsStore::GetState()); // 0

	CoinsStore::Fire(AddCoins(10));
	print(CoinsStore::GetState()); // 10

	CoinsStore::Dispatch(AddCoins(10)); // Dispatch is also valid
	print(CoinsStore::GetState()); // 20
}

Functions#

Is#

Store.Is(
Object: any--

The object to check against.

) → boolean--

Whether or not the object is a Store.

Determines if the passed object is a Store.

print(Helium.Store.Is(Helium.Store.new(function() end, {}))) -- true
print(Helium.Store.Is({})) -- false
print(Helium.Store.Is(true)) -- false

new#

Store.new(
Reducer: ReducerFunction,--

The reducer function.

InitialState: NonNil--

The initial state.

) → Store

Creates a new Store object.

local Store = Helium.Store.new(function()
end, {ThisIsAStore = true})

ApplyMiddleware#

Store:ApplyMiddleware(
Middleware: (Store: Store) → (NextDispatch: (Action: BaseAction) → ()) → (Action: BaseAction) → ()--

The middleware function you are applying.

) → Store--

The self reference for chaining these calls.

Applies a Middleware to the Store. Middlware are simply functions that intercept actions upon being dispatched, and allow custom logic to be applied to them. The way middlewares intercept actions is by providing a bridge in between store.dispatch being called and the root reducer receiving those actions that were dispatched.

local SetValueA = Helium.MakeActionCreator("SetValueA", function(Value)
	return {Value = Value}
end)

local Store = Helium.Store.new(function(Action, _, SetState)
	if Action.Type == SetValueA.ActionName then
		SetState("ValueA", Action.Value)
	end
end, {
	ValueA = "A";
	ValueB = {ValueC = "C"};
})

Store:ApplyMiddleware(Helium.LoggerMiddleware):ApplyMiddleware(Helium.InspectorMiddleware)
Store:Fire(SetValueA("ValueA"))
--[[
	Prints:
		{
			["Value"] = "ValueA",
			["Type"] = "SetValueA"
		}
		SetValueA
]]

Connect#

Store:Connect(
StringKeyPath: string,--

The string path to run the function at. An empty string is equal to any changes made.

Function: () → ()--

The function you want to run when the state is updated.

) → () → ()--

A function that disconnects the connection.

Connects a function to the given string keypath.

local function SetValue(Value)
	return {Value = Value}
end

local SetValueA = Helium.MakeActionCreator("SetValueA", SetValue)
local SetValueC = Helium.MakeActionCreator("SetValueC", SetValue)
local SetValueD = Helium.MakeActionCreator("SetValueD", SetValue)

local Store = Helium.Store.new(function(Action, _, SetState)
	if Action.Type == SetValueA.ActionName then
		SetState("ValueA", Action.Value)
	elseif Action.Type == SetValueC.ActionName then
		SetState("ValueB", "ValueC", Action.Value)
	elseif Action.Type == SetValueD.ActionName then
		SetState("ValueD", Action.Value)
	end
end, {
	ValueA = "A";
	ValueB = {ValueC = "C"};
})

local Disconnect = Store:Connect("", function()
	print("The store was changed!", Store:GetState())
end)

Store:Connect("ValueA", function()
	print("ValueA was changed!", Store:GetState("ValueA"))
end)

Store:Connect("ValueB.ValueC", function()
	print("ValueB.ValueC was changed!", Store:GetState("ValueB", "ValueC"))
end)

Store:Fire(SetValueD("ValueD"))
--[[
	Prints:
		The store was changed! {
			["ValueA"] = "A",
			["ValueB"] = {
				["ValueC"] = "C"
			},
			["ValueD"] = "ValueD"
		}
]]

Disconnect()
Store:Fire(SetValueA("ValueA")) -- Prints: ValueA was changed! ValueA
Store:Fire(SetValueC("ValueC")) -- Prints: ValueB.ValueC was changed! ValueC

Dispatch#

Store:Dispatch(
Action: BaseAction--

The Action you are dispatching.

) → ()

Dispatches an Action to the Store.

local DispatchAction = Helium.MakeActionCreator("DispatchAction", function(Value)
	return {
		Value = Value;
	}
end)

Store:Dispatch(DispatchAction("Value"))
Store:Dispatch({
	Type = "AwesomeAction";
	AwesomeValue = true;
})

Fire#

Store:Fire(
Action: BaseAction--

The Action you are dispatching.

) → ()

Dispatches an Action to the Store.

local DispatchAction = Helium.MakeActionCreator("DispatchAction", function(Value)
	return {
		Value = Value;
	}
end)

Store:Fire(DispatchAction("Value"))
Store:Fire({
	Type = "AwesomeAction";
	AwesomeValue = true;
})

GetState#

Store:GetState(
...: string?--

The string path you want to get.

) → T--

The current Store state.

Gets the current Store state. If the value returned is a table, it is deep copied to prevent You can optionally provide a path.

local Store = Helium.Store.new(function() end, {
	ValueA = "A";
	ValueB = {ValueC = "C"};
})

print(Store:GetState()) -- The state.
print(Store:GetState("ValueA")) -- "A"
print(Store:GetState("ValueB", "ValueC")) -- "C"

InspectState#

Store:InspectState() → string--

The string representation of the Store's current state.

Returns a string representation of the Store's current state. This is useful for debugging things.

local Store = Helium.Store.new(function() end, {
	ValueA = "A";
	ValueB = {ValueC = "C"};
})

print(Store:InspectState())
--[[
	Prints:
		{
			["ValueA"] = "A",
			["ValueB"] = {
				["ValueC"] = "C"
			}
		}
]]

SetState#

Store:SetState(
...: string?,--

The path of the state to set.

Value: any--

The value you are setting. This is always required, think of it as setting a Url -> example.com/path/to/value == "path", "to", "value"

) → ()

Sets the current Store state. The varargs are the string paths you want to set the state of.

info

The path is totally optional and skipping it will just result in you editing the root of the state table.

warning

SetState overwrites the table, so if you want to preserve the original table, you should be using actions and the reducer function.

local Store = Helium.Store.new(function() end, {
	ValueA = "A";
})

print(Store:GetState("ValueA")) -- "A"
Store:SetState("ValueA", "ValueA")
print(Store:GetState("ValueA")) -- "ValueA"
local Store = Helium.Store.new(function() end, {
	ValueB = {ValueC = "C"};
})

print(Store:GetState("ValueB", "ValueC")) -- "C"
Store:SetState("ValueB", "ValueC", 3)
print(Store:GetState("ValueB", "ValueC")) -- "3"

Subscribe#

Store:Subscribe(
StringKeyPath: string,--

The string path to run the function at. An empty string is equal to any changes made.

Function: () → ()--

The function you want to run when the state is updated.

) → () → ()--

A function that disconnects the connection.

Connects a function to the given string keypath.

local function SetValue(Value)
	return {Value = Value}
end

local SetValueA = Helium.MakeActionCreator("SetValueA", SetValue)
local SetValueC = Helium.MakeActionCreator("SetValueC", SetValue)
local SetValueD = Helium.MakeActionCreator("SetValueD", SetValue)

local Store = Helium.Store.new(function(Action, _, SetState)
	if Action.Type == SetValueA.ActionName then
		SetState("ValueA", Action.Value)
	elseif Action.Type == SetValueC.ActionName then
		SetState("ValueB", "ValueC", Action.Value)
	elseif Action.Type == SetValueD.ActionName then
		SetState("ValueD", Action.Value)
	end
end, {
	ValueA = "A";
	ValueB = {ValueC = "C"};
})

local Disconnect = Store:Subscribe("", function()
	print("The store was changed!", Store:GetState())
end)

Store:Subscribe("ValueA", function()
	print("ValueA was changed!", Store:GetState("ValueA"))
end)

Store:Subscribe("ValueB.ValueC", function()
	print("ValueB.ValueC was changed!", Store:GetState("ValueB", "ValueC"))
end)

Store:Dispatch(SetValueD("ValueD"))
--[[
	Prints:
		The store was changed! {
			["ValueA"] = "A",
			["ValueB"] = {
				["ValueC"] = "C"
			},
			["ValueD"] = "ValueD"
		}
]]

Disconnect()
Store:Dispatch(SetValueA("ValueA")) -- Prints: ValueA was changed! ValueA
Store:Dispatch(SetValueC("ValueC")) -- Prints: ValueB.ValueC was changed! ValueC
Show raw api
{
    "functions": [
        {
            "name": "ApplyMiddleware",
            "desc": "Applies a Middleware to the Store. Middlware are simply functions that intercept actions upon being dispatched, and allow custom logic to be applied to them.\nThe way middlewares intercept actions is by providing a bridge in between store.dispatch being called and the root reducer receiving those actions that were dispatched.\n\n```lua\nlocal SetValueA = Helium.MakeActionCreator(\"SetValueA\", function(Value)\n\treturn {Value = Value}\nend)\n\nlocal Store = Helium.Store.new(function(Action, _, SetState)\n\tif Action.Type == SetValueA.ActionName then\n\t\tSetState(\"ValueA\", Action.Value)\n\tend\nend, {\n\tValueA = \"A\";\n\tValueB = {ValueC = \"C\"};\n})\n\nStore:ApplyMiddleware(Helium.LoggerMiddleware):ApplyMiddleware(Helium.InspectorMiddleware)\nStore:Fire(SetValueA(\"ValueA\"))\n--[[\n\tPrints:\n\t\t{\n\t\t\t[\"Value\"] = \"ValueA\",\n\t\t\t[\"Type\"] = \"SetValueA\"\n\t\t}\n\t\tSetValueA\n]]\n```",
            "params": [
                {
                    "name": "Middleware",
                    "desc": "The middleware function you are applying.",
                    "lua_type": "(Store: Store) -> (NextDispatch: (Action: BaseAction) -> ()) -> (Action: BaseAction) -> ()"
                }
            ],
            "returns": [
                {
                    "desc": "The self reference for chaining these calls.",
                    "lua_type": "Store"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 422,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "Fire",
            "desc": "Dispatches an Action to the Store.\n\n```lua\nlocal DispatchAction = Helium.MakeActionCreator(\"DispatchAction\", function(Value)\n\treturn {\n\t\tValue = Value;\n\t}\nend)\n\nStore:Fire(DispatchAction(\"Value\"))\nStore:Fire({\n\tType = \"AwesomeAction\";\n\tAwesomeValue = true;\n})\n```",
            "params": [
                {
                    "name": "Action",
                    "desc": "The Action you are dispatching.",
                    "lua_type": "BaseAction"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 461,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "Dispatch",
            "desc": "Dispatches an Action to the Store.\n\n```lua\nlocal DispatchAction = Helium.MakeActionCreator(\"DispatchAction\", function(Value)\n\treturn {\n\t\tValue = Value;\n\t}\nend)\n\nStore:Dispatch(DispatchAction(\"Value\"))\nStore:Dispatch({\n\tType = \"AwesomeAction\";\n\tAwesomeValue = true;\n})\n```",
            "params": [
                {
                    "name": "Action",
                    "desc": "The Action you are dispatching.",
                    "lua_type": "BaseAction"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 488,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "GetState",
            "desc": "Gets the current Store state. If the value returned is a table, it is deep copied to prevent  You can optionally provide a path.\n\n```lua\nlocal Store = Helium.Store.new(function() end, {\n\tValueA = \"A\";\n\tValueB = {ValueC = \"C\"};\n})\n\nprint(Store:GetState()) -- The state.\nprint(Store:GetState(\"ValueA\")) -- \"A\"\nprint(Store:GetState(\"ValueB\", \"ValueC\")) -- \"C\"\n```",
            "params": [
                {
                    "name": "...",
                    "desc": "The string path you want to get.",
                    "lua_type": "string?"
                }
            ],
            "returns": [
                {
                    "desc": "The current Store state.",
                    "lua_type": "T"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 513,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "SetState",
            "desc": "Sets the current Store state. The varargs are the string paths you want to set the state of.\n\n:::info\nThe path is totally optional and skipping it will just result in you editing the root of the state table.\n:::\n\n:::warning\n`SetState` overwrites the table, so if you want to preserve the original table, you should be using actions and the reducer function.\n:::\n\n```lua\nlocal Store = Helium.Store.new(function() end, {\n\tValueA = \"A\";\n})\n\nprint(Store:GetState(\"ValueA\")) -- \"A\"\nStore:SetState(\"ValueA\", \"ValueA\")\nprint(Store:GetState(\"ValueA\")) -- \"ValueA\"\n```\n\n```lua\nlocal Store = Helium.Store.new(function() end, {\n\tValueB = {ValueC = \"C\"};\n})\n\nprint(Store:GetState(\"ValueB\", \"ValueC\")) -- \"C\"\nStore:SetState(\"ValueB\", \"ValueC\", 3)\nprint(Store:GetState(\"ValueB\", \"ValueC\")) -- \"3\"\n```",
            "params": [
                {
                    "name": "...",
                    "desc": "The path of the state to set.",
                    "lua_type": "string?"
                },
                {
                    "name": "Value",
                    "desc": "The value you are setting. This is always required, think of it as setting a Url -> example.com/path/to/value == \"path\", \"to\", \"value\"",
                    "lua_type": "any"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 564,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "Connect",
            "desc": "Connects a function to the given string keypath.\n\n```lua\nlocal function SetValue(Value)\n\treturn {Value = Value}\nend\n\nlocal SetValueA = Helium.MakeActionCreator(\"SetValueA\", SetValue)\nlocal SetValueC = Helium.MakeActionCreator(\"SetValueC\", SetValue)\nlocal SetValueD = Helium.MakeActionCreator(\"SetValueD\", SetValue)\n\nlocal Store = Helium.Store.new(function(Action, _, SetState)\n\tif Action.Type == SetValueA.ActionName then\n\t\tSetState(\"ValueA\", Action.Value)\n\telseif Action.Type == SetValueC.ActionName then\n\t\tSetState(\"ValueB\", \"ValueC\", Action.Value)\n\telseif Action.Type == SetValueD.ActionName then\n\t\tSetState(\"ValueD\", Action.Value)\n\tend\nend, {\n\tValueA = \"A\";\n\tValueB = {ValueC = \"C\"};\n})\n\nlocal Disconnect = Store:Connect(\"\", function()\n\tprint(\"The store was changed!\", Store:GetState())\nend)\n\nStore:Connect(\"ValueA\", function()\n\tprint(\"ValueA was changed!\", Store:GetState(\"ValueA\"))\nend)\n\nStore:Connect(\"ValueB.ValueC\", function()\n\tprint(\"ValueB.ValueC was changed!\", Store:GetState(\"ValueB\", \"ValueC\"))\nend)\n\nStore:Fire(SetValueD(\"ValueD\"))\n--[[\n\tPrints:\n\t\tThe store was changed! {\n\t\t\t[\"ValueA\"] = \"A\",\n\t\t\t[\"ValueB\"] = {\n\t\t\t\t[\"ValueC\"] = \"C\"\n\t\t\t},\n\t\t\t[\"ValueD\"] = \"ValueD\"\n\t\t}\n]]\n\nDisconnect()\nStore:Fire(SetValueA(\"ValueA\")) -- Prints: ValueA was changed! ValueA\nStore:Fire(SetValueC(\"ValueC\")) -- Prints: ValueB.ValueC was changed! ValueC\n```",
            "params": [
                {
                    "name": "StringKeyPath",
                    "desc": "The string path to run the function at. An empty string is equal to any changes made.",
                    "lua_type": "string"
                },
                {
                    "name": "Function",
                    "desc": "The function you want to run when the state is updated.",
                    "lua_type": "() -> ()"
                }
            ],
            "returns": [
                {
                    "desc": "A function that disconnects the connection.",
                    "lua_type": "() -> ()"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 676,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "Subscribe",
            "desc": "Connects a function to the given string keypath.\n\n```lua\nlocal function SetValue(Value)\n\treturn {Value = Value}\nend\n\nlocal SetValueA = Helium.MakeActionCreator(\"SetValueA\", SetValue)\nlocal SetValueC = Helium.MakeActionCreator(\"SetValueC\", SetValue)\nlocal SetValueD = Helium.MakeActionCreator(\"SetValueD\", SetValue)\n\nlocal Store = Helium.Store.new(function(Action, _, SetState)\n\tif Action.Type == SetValueA.ActionName then\n\t\tSetState(\"ValueA\", Action.Value)\n\telseif Action.Type == SetValueC.ActionName then\n\t\tSetState(\"ValueB\", \"ValueC\", Action.Value)\n\telseif Action.Type == SetValueD.ActionName then\n\t\tSetState(\"ValueD\", Action.Value)\n\tend\nend, {\n\tValueA = \"A\";\n\tValueB = {ValueC = \"C\"};\n})\n\nlocal Disconnect = Store:Subscribe(\"\", function()\n\tprint(\"The store was changed!\", Store:GetState())\nend)\n\nStore:Subscribe(\"ValueA\", function()\n\tprint(\"ValueA was changed!\", Store:GetState(\"ValueA\"))\nend)\n\nStore:Subscribe(\"ValueB.ValueC\", function()\n\tprint(\"ValueB.ValueC was changed!\", Store:GetState(\"ValueB\", \"ValueC\"))\nend)\n\nStore:Dispatch(SetValueD(\"ValueD\"))\n--[[\n\tPrints:\n\t\tThe store was changed! {\n\t\t\t[\"ValueA\"] = \"A\",\n\t\t\t[\"ValueB\"] = {\n\t\t\t\t[\"ValueC\"] = \"C\"\n\t\t\t},\n\t\t\t[\"ValueD\"] = \"ValueD\"\n\t\t}\n]]\n\nDisconnect()\nStore:Dispatch(SetValueA(\"ValueA\")) -- Prints: ValueA was changed! ValueA\nStore:Dispatch(SetValueC(\"ValueC\")) -- Prints: ValueB.ValueC was changed! ValueC\n```",
            "params": [
                {
                    "name": "StringKeyPath",
                    "desc": "The string path to run the function at. An empty string is equal to any changes made.",
                    "lua_type": "string"
                },
                {
                    "name": "Function",
                    "desc": "The function you want to run when the state is updated.",
                    "lua_type": "() -> ()"
                }
            ],
            "returns": [
                {
                    "desc": "A function that disconnects the connection.",
                    "lua_type": "() -> ()"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 761,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "InspectState",
            "desc": "Returns a string representation of the Store's current state. This is useful for debugging things.\n\n```lua\nlocal Store = Helium.Store.new(function() end, {\n\tValueA = \"A\";\n\tValueB = {ValueC = \"C\"};\n})\n\nprint(Store:InspectState())\n--[[\n\tPrints:\n\t\t{\n\t\t\t[\"ValueA\"] = \"A\",\n\t\t\t[\"ValueB\"] = {\n\t\t\t\t[\"ValueC\"] = \"C\"\n\t\t\t}\n\t\t}\n]]\n```",
            "params": [],
            "returns": [
                {
                    "desc": "The string representation of the Store's current state.",
                    "lua_type": "string"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 811,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "new",
            "desc": "Creates a new Store object.\n\n```lua\nlocal Store = Helium.Store.new(function()\nend, {ThisIsAStore = true})\n```",
            "params": [
                {
                    "name": "Reducer",
                    "desc": "The reducer function.",
                    "lua_type": "ReducerFunction"
                },
                {
                    "name": "InitialState",
                    "desc": "The initial state.",
                    "lua_type": "NonNil"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Store"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 831,
                "path": "src/Store.lua"
            }
        },
        {
            "name": "Is",
            "desc": "Determines if the passed object is a Store.\n\n```lua\nprint(Helium.Store.Is(Helium.Store.new(function() end, {}))) -- true\nprint(Helium.Store.Is({})) -- false\nprint(Helium.Store.Is(true)) -- false\n```",
            "params": [
                {
                    "name": "Object",
                    "desc": "The object to check against.",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "Whether or not the object is a Store.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 862,
                "path": "src/Store.lua"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "Store",
    "desc": "The Store object is one of the API changes going from Rocrastinate to Helium, as it is no longer a function that returns a table and is instead a proper Lua object.\nThe Store object is inspired by [Redux](https://redux.js.org/), a state management library that is often used in JavaScript web applications. Like Redux, the Store\nis where we can centralize the state of our application. It uses \"Actions\" to update the application state, and \"Reducers\" to control how the state changes with a given action.\n\nHelium's Store is NOT equivalent to Redux or [Rodux](https://github.com/Roblox/rodux). Some major differences are as follows:\n* Helium stores must be passed as the first argument in the constructors of components that use Store objects. This means that the \"store\" a component is in is not determined by context, but by explicit argument.\n* Redux reduces actions by re-creating the entire application state. For the sake of optimization, enabled by the coupling of Helium Components with Store, Helium Store reducers are passed the functions `GetState` and `SetState`, which copy/mutate the application's state respectively.\n* With React/Redux (or Roact/Rodux), changes to the store will immediately re-render a component. In contrast, changes in a Helium store will immediately call `QueueUpdate()`, which defers rendering changes to the next frame binding.\n\n## Actions\n\n\"Actions\" are the *only* source of information for our Helium Store. They represent information needed to change some portion of the application state, and are represented as lua objects. They are sent using `Store:Fire(Action)` or `Store:Dispatch(Action)`.\n\nActions should typically be represented as tables, and have a `Type` property denoting what kind of action is being sent. For example:\n\n```lua\nlocal MyAction = {\n\tType = \"AddCoins\";\n\tAmount = 1;\n}\n\nStore:Fire(MyAction)\n```\n\n```cpp\ntypedef GenericDictionary table<LuaString, any>;\n\nGenericDictionary MyAction = table(\n\tType: L\"AddCoins\",\n\tAmount: 1,\n);\n\nStore::Fire(MyAction);\n```\n\n_________________\n\n## Action Creators\n\nTypically, instead of creating actions directly, we can use \"Action Creators\", which are simply functions that create actions for us with a given set of arguments. Note that these only **create the action**, and **do not dispatch them**:\n```lua\nlocal function AddCoins(Amount: number)\n\treturn {\n\t\tType = \"AddCoins\";\n\t\tAmount = Amount;\n\t}\nend\n\nStore:Fire(AddCoins(1))\n```\n\n```cpp\ntypedef GenericDictionary table<LuaString, any>;\n\nGenericDictionary AddCoins(int Amount)\n{\n\treturn table(\n\t\tType: L\"AddCoins\",\n\t\tAmount: Amount,\n\t);\n}\n\nStore::Fire(AddCoins(1));\n```\n\nActions can be dispatched at any time from anywhere in the application, including the middle of a Redraw().\n\nHelium actually has a built-in function used to creating this, called `MakeActionCreator`. This can be used as seen in the following code:\n\n```lua\nlocal AddCoins = Helium.MakeActionCreator(\"AddCoins\", function(Amount: number)\n\treturn {\n\t\tAmount = Amount;\n\t}\nend)\n\nStore:Fire(AddCoins(1))\n```\n\n```cpp\ntypedef GenericDictionary table<LuaString, any>;\n\nGenericDictionary AddCoinsAction(int Amount)\n{\n\treturn table(Amount: Amount);\n}\n\nvoid AddCoins = Helium.MakeActionCreator(L\"AddCoins\", AddCoinsAction);\n\nStore::Fire(AddCoins(1));\n```\n\n_________________\n\n## Responding to Actions\n\nLike Redux, Helium uses \"Reducers\", which are functions that respond to an action by modifying a certain portion of the store's state.\n\nReducers are given three arguments: `(Action, GetState, SetState)`.\n\n* `Action` is the action that was dispatched.\n* `GetState(...KeyPath)` is a function that gets a value in the store by nested keys.\n* `SetState(...KeyPath, Value)` is a function that sets a value in the store by nested keys.\n\nIf we want to set the value of `Coins` in the store whenever an `AddCoins` action is received, we can use the following code:\n\n```lua\nlocal function Reducer(Action, GetState, SetState)\n\tif Action.Type == \"AddCoins\" then\n\t\tlocal Coins = GetState(\"Coins\")\n\t\tSetState(\"Coins\", Coins + Action.Amount)\n\tend\nend\n```\n\nIf you're using the `MakeActionCreator` function, you can set it up like so:\n\n```lua\nlocal AddCoins = require(ReplicatedStorage.AddCoins)\n\nlocal function Reducer(Action, GetState, SetState)\n\tif Action.Type == AddCoins.ActionName then\n\t\tlocal Coins = GetState(\"Coins\")\n\t\tSetState(\"Coins\", Coins + Action.Amount)\n\tend\nend\n```\n\nThis code makes a few assumptions:\n\n1. There is already a value in the store named `Coins`, and that it is a number.\n2. That the action has a property named `Type`.\n3. That the action (which we've identified as an `AddCoins` action) has a property named `Amount`, and that it is a number.\n\nIt is generally best to centralize actions or action creators in an `Actions` module, so that these assumptions can be standardized. Additionally, we need to declare the initial state of our store somewhere:\n\n```lua\nlocal InitialState = {\n\tCoins = 0;\n}\n```\n\nThen, when we call\n\n```lua\nStore:Fire(AddCoins(1))\n```\n\nour store state should conceptually be mutated to look something like this table:\n\n```lua\n{\n\tCoins = 1;\n}\n```\n\nAdditionally, we can nest tables in our store structure:\n\n```lua\nlocal InitialState = {\n\tPlayerStats = {Coins = 0};\n}\n\nlocal function Reducer(Action, GetState, SetState)\n\tif Action.Type == AddCoins.ActionName then\n\t\tlocal Coins = GetState(\"PlayerStats\", \"Coins\")\n\t\tSetState(\"PlayerStats\", \"Coins\", Coins + Action.Amount)\n\tend\nend\n```\n\n```cpp\nGenericDictionary InitialState = table(PlayerStats: table(Coins: 0));\n\nvoid Reducer(GenericDictionary Action, void GetState, SetState)\n{\n\tif Action.Type == AddCoins.ActionName\n\t{\n\t\tint Coins = GetState(L\"PlayerStats\", L\"Coins\");\n\t\tSetState(L\"PlayerStats\", L\"Coins\", Coins + Action.Amount);\n\t}\n}\n```\n\nIn the above example, we provide an aditional argument to `GetState` and `SetState`. These are just strings representing the path of nested keys leading to the exact value we want to get/set in our store.\n\nIf we kept this all in the same module, we may run into a problem when our tree becomes more complex:\n\n```lua\nlocal function Reducer(Action, GetState, SetState)\n\tif Action.Type == \"DoSomethingInASpecificDomain\" then\n\t\tSetState(\"Path\", \"To\", \"Specific\", \"Domain\", Value)\n\telseif  ...  then\n\t\t...\n\tend\nend\n```\n\n```cpp\nvoid Reducer(GenericDictionary Action, void GetState, SetState)\n{\n\tif Action.Type == L\"DoSomethingInASpecificDomain\"\n\t{\n\t\tSetState(L\"Path\", L\"To\", L\"Specific\", L\"Domain\", Action.Value);\n\t} elseif ...\n\t{\n\t\t...\n\t}\n}\n```\n\nThis can become very verbose. It would be much simpler if we could create a reducer that just deals with playerStats, and another reducer that just deals with some other domain.\n\nTo do this, you can use the `CombineReducers()` function. Let's say we put our main reducer in a module called \"RootReducer\", and nested reducers for playerStats underneath the root reducer:\n\n_________________\n\n### RootReducer ModuleScript\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal Helium = require(ReplicatedStorage.Helium)\nlocal PlayerStats = require(script.PlayerStats)\n\nlocal Reducer = Helium.CombineReducers({\n\tPlayerStats = PlayerStats.Reducer;\n})\n\nlocal InitialState = {\n\tPlayerStats = PlayerStats.InitialState;\n}\n\nreturn {\n\tReducer = Reducer;\n\tInitialState = InitialState;\n}\n```\n\n### RootReducer.PlayerStats ModuleScript\n\n```lua\nlocal function Reducer(Action, GetState, SetState)\n\tif Action.Type == \"AddCoins\" then\n\t\tlocal Coins = GetState(\"Coins\")\n\t\tSetState(\"Coins\", Coins + Action.Amount)\n\tend\nend\n\nlocal InitialState = {Coins = 0;}\nreturn {\n\tReducer = Reducer;\n\tInitialState = InitialState;\n}\n```\n\nIf we wanted to, we could subdivide this even further by making a reducer for coins, and use `CombineReducers()` in the PlayerStats module instead. The \"coins\" module would then look something like this:\n\n### RootReducer.PlayerStats.Coins ModuleScript\n\n```lua\nlocal function Reducer(Action, GetState, SetState)\n\tif Action.Type == \"AddCoins\" then\n\t\tSetState(GetState() + Action.Amount)\n\tend\nend\n\nlocal InitialState = 0\n\nreturn {\n\tReducer = Reducer;\n\tInitialState = InitialState;\n}\n```\n\nNow that we've separated the concerns of our reducers and actions, how do we actually create the store and have it interact with our application?\n\nHelium uses the function `Store.new(Reducer, InitialState)` for this.\nPutting it all together, we can create a very simple store that reduces a single value of \"Coins\"\n\n```lua\nlocal ReplicatedStorage = game:GetService(\"ReplicatedStorage\")\nlocal Helium = require(ReplicatedStorage.Helium)\n\n-- Typically this would be put in a separate module called \"Actions\"\nlocal AddCoins = Helium.MakeActionCreator(\"AddCoins\", function(Amount: number)\n\treturn {\n\t\tAmount = Amount;\n\t}\nend)\n\n-- Typically this would be put in a separate module called \"Reducer\" or \"RootReducer\"\nlocal function Reducer(Action, GetState, SetState)\n\tif Action.Type == AddCoins.ActionName then\n\t\tSetState(GetState() + Action.Amount)\n\tend\nend\n\nlocal InitialState = 0\nlocal CoinsStore = Helium.Store.new(Reducer, InitialState) -- You can also do Helium.CreateStore(Reducer, InitialState)\n\nprint(CoinsStore:GetState()) -- 0\n\nCoinsStore:Fire(AddCoins(10))\nprint(CoinsStore:GetState()) -- 10\n\nCoinsStore:Dispatch(AddCoins(10)) -- Dispatch is also valid\nprint(CoinsStore:GetState()) -- 20\n```\n\n```cpp\ntypedef GenericDictionary table<LuaString, any>;\nRbxInstance ReplicatedStorage = game::GetService @ ReplicatedStorage;\nGenericDictionary Helium = require(ReplicatedStorage.Helium);\n\nconst int INITIAL_STATE = 0;\n\n// Typically this would be put in a separate module called \"Actions\"\nGenericDictionary AddCoinsFunction(int Amount)\n{\n\treturn table(Amount: Amount);\n}\n\nvoid AddCoins = Helium.MakeActionCreator(L\"AddCoins\", AddCoinsFunction);\n\n// Typically this would be put in a separate module called \"Reducer\" or \"RootReducer\"\nvoid Reducer(GenericDictionary Action, void GetState, SetState)\n{\n\tif Action.Type == AddCoins.ActionName\n\t{\n\t\tSetState(GetState() + Action.Amount);\n\t}\n}\n\nentry void Main(void)\n{\n\tGenericDictionary CoinsStore = Helium.Store.new(Reducer, INITIAL_STATE); // You can also do Helium.CreateStore(Reducer, InitialState)\n\tprint(CoinsStore::GetState()); // 0\n\n\tCoinsStore::Fire(AddCoins(10));\n\tprint(CoinsStore::GetState()); // 10\n\n\tCoinsStore::Dispatch(AddCoins(10)); // Dispatch is also valid\n\tprint(CoinsStore::GetState()); // 20\n}\n```",
    "source": {
        "line": 357,
        "path": "src/Store.lua"
    }
}