This module provides a set of hooks and utilities that allow for efficient state management in Lua applications, mimicking the popular React hooks API.
It’s designed to bring the power and flexibility of React-style state management
to Lua environments, making it easier to build and maintain complex, stateful applications.
Features
constructor (.new)
Creates a new State instance
@return State A new State instance
local State = require("State")
local state = State.new()
-- Now you can use state to manage your application's state
local getCount, setCount = state:useState(0)
print(getCount()) -- Output: 0
state:useEffect(function()
print("Count changed to:", getCount())
end, {getCount})
setCount(1) -- This will trigger the effect
:useState
Creates a state variable and returns its value and a setter function
@param initialValue The initial value of the state variable
@return function A getter function to retrieve the current value of the state variable
@return function A setter function to update the state variable
local state = State.new()
-- Create a state variable for user's name
local getName, setName = state:useState("John Doe")
-- Use the getter
print("Current name:", getName()) -- Output: Current name: John Doe
-- Use the setter
setName("Jane Doe")
print("Updated name:", getName()) -- Output: Updated name: Jane Doe
-- Use a function to update based on previous state
setName(function(prevName)
return prevName .. " Jr."
end)
print("Final name:", getName()) -- Output: Final name: Jane Doe Jr.
:useEffect
Runs an effect function when dependencies change. The effect function is executed immediately upon creation and then re-executed whenever any of its dependencies change.
@param effect The effect function to run. This function can optionally return a cleanup function.
@param dependencies An optional table of dependencies that trigger the effect when changed. If omitted, the effect runs on every update.
The effect function is called with no arguments. If it returns a function, that function will be used as a cleanup function and will be called before the effect runs again or when the component is unmounted.
If dependencies are provided, the effect will only re-run if any of the dependencies have changed since the last render. Comparing dependencies is done using reference equality.
local State = require("State")
local myState = State.new()
-- Effect without dependencies (runs on every update)
myState:useEffect(function()
print("This effect runs on every update")
end)
-- Effect with empty dependencies (runs only once, on mount)
myState:useEffect(function()
print("This effect runs only once, on mount")
return function()
print("This cleanup runs when the component unmounts")
end
end, {})
-- Effect with dependencies
local getCount, setCount = myState:useState(0)
myState:useEffect(function()
print("Count changed to: " .. getCount())
-- Optional cleanup function
return function()
print("Cleaning up previous effect for count: " .. getCount())
end
end, {getCount})
-- Effect with cleanup (simulating a timer)
local getIsActive, setIsActive = myState:useState(true)
myState:useEffect(function()
if getIsActive() then
local timerId = setTimer(function()
print("Timer fired!")
setIsActive(false)
end, 5000)
-- Cleanup function to clear the timer
return function()
clearTimer(timerId)
print("Timer cleared")
end
end
end, {getIsActive})
-- Simulating state changes
setCount(1) -- This will trigger the effect with count dependency
setIsActive(false) -- This will trigger the effect with isActive dependency and run the cleanup
:useContext
Returns the value of a context
@param context The context object
@return The value of the context
local State = require("State")
local myState = State.new()
-- Create a context
local ThemeContext = {value = "light"}
-- Use the context
local function Component()
local theme = myState:useContext(ThemeContext)
print("Current theme:", theme) -- Output: Current theme: light
-- You can use the theme value to conditionally render or style your component
if theme == "light" then
print("Rendering light theme")
else
print("Rendering dark theme")
end
end
-- Simulate changing the context
ThemeContext.value = "dark"
Component() -- This will now use the updated context value
-- You can also use context with more complex values
local UserContext = {value = {name = "John", role = "Admin"}}
local function UserInfo()
local user = myState:useContext(UserContext)
print("User:", user.name, "Role:", user.role)
end
UserInfo() -- Output: User: John Role: Admin
:useReducer
Creates a reducer state and returns its value and a dispatch function
@param reducer The reducer function
@param initialState The initial state
@return function A getter function to retrieve the current state
@return function A dispatch function to update the state
local State = require("State")
local myState = State.new()
-- Define a reducer function
local function counterReducer(state, action)
if action == "INCREMENT" then
return state + 1
elseif action == "DECREMENT" then
return state - 1
elseif action == "RESET" then
return 0
else
return state
end
end
-- Use the reducer
local getCount, dispatch = myState:useReducer(counterReducer, 0)
-- Use the state
print("Initial count:", getCount()) -- Output: Initial count: 0
-- Dispatch actions
dispatch("INCREMENT")
print("After increment:", getCount()) -- Output: After increment: 1
dispatch("INCREMENT")
print("After another increment:", getCount()) -- Output: After another increment: 2
dispatch("DECREMENT")
print("After decrement:", getCount()) -- Output: After decrement: 1
dispatch("RESET")
print("After reset:", getCount()) -- Output: After reset: 0
-- You can also use more complex state and actions
local function todoReducer(state, action)
if action.type == "ADD_TODO" then
return table.insert(state, {text = action.payload, completed = false})
elseif action.type == "TOGGLE_TODO" then
state[action.payload].completed = not state[action.payload].completed
return state
else
return state
end
end
local getTodos, dispatchTodo = myState:useReducer(todoReducer, {})
dispatchTodo({type = "ADD_TODO", payload = "Learn Lua"})
dispatchTodo({type = "ADD_TODO", payload = "Master State Management"})
dispatchTodo({type = "TOGGLE_TODO", payload = 1})
for i, todo in ipairs(getTodos()) do
print(i, todo.text, todo.completed)
end
-- Output:
-- 1 Learn Lua true
-- 2 Master State Management false
:useCallback
Returns a memoized callback function
@param callback The callback function to memoize
@param dependencies A table of dependencies
@return function The memoized callback function
local State = require("State")
local myState = State.new()
-- Create some state
local getName, setName = myState:useState("John")
local getGreeting, setGreeting = myState:useState("Hello")
-- Create a memoized callback
local greet = myState:useCallback(function()
print(getGreeting() .. ", " .. getName() .. "!")
end, {getName, getGreeting})
-- Use the memoized callback
greet() -- Output: Hello, John!
-- Change the name
setName("Jane")
greet() -- Output: Hello, Jane!
-- Change the greeting
setGreeting("Hi")
greet() -- Output: Hi, Jane!
-- Example with parameters
local multiply = myState:useCallback(function(a, b)
return a * b
end, {})
print(multiply(5, 3)) -- Output: 15
-- The callback will only be recreated if the dependencies change
local getCount, setCount = myState:useState(0)
local logCount = myState:useCallback(function()
print("Current count:", getCount())
end, {getCount})
logCount() -- Output: Current count: 0
setCount(5)
logCount() -- Output: Current count: 5
:useMemo
Returns a memoized value
@param create A function that creates the value to be memoized
@param dependencies A table of dependencies
@return The memoized value
@usage
local State = require("State")
local myState = State.new()
-- Create some state
local getWidth, setWidth = myState:useState(1920)
local getHeight, setHeight = myState:useState(1080)
-- Use useMemo to compute an expensive calculation
local getArea = myState:useMemo(function()
print("Computing area...") -- This will only print when width or height changes
return getWidth() * getHeight()
end, {getWidth, getHeight})
print("Area:", getArea()) -- Output: Computing area... \n Area: 2073600
print("Area:", getArea()) -- Output: Area: 2073600 (no recomputation)
setWidth(3840)
print("Area:", getArea()) -- Output: Computing area... \n Area: 4147200
-- Example with more complex computation
local getNums, setNums = myState:useState({1, 2, 3, 4, 5})
local getSum = myState:useMemo(function()
print("Computing sum...")
local sum = 0
for _, num in ipairs(getNums()) do
sum = sum + num
end
return sum
end, {getNums})
print("Sum:", getSum()) -- Output: Computing sum... \n Sum: 15
print("Sum:", getSum()) -- Output: Sum: 15 (no recomputation)
setNums({1, 2, 3, 4, 5, 6})
print("Sum:", getSum()) -- Output: Computing sum... \n Sum: 21
:useRef
Creates a mutable ref object that persists for the full lifetime of the component.
@param initialValue The initial value of the ref. Can be of any type.
@return table A ref object with a ‘current’ property that can be read from or assigned to.
@description
The useRef function is useful for keeping any mutable value around similar to how you’d use instance fields in classes.
This could be used to store a reference to a DOM element, to keep track of interval IDs, or any other mutable data
that you want to persist without causing a re-render when it’s changed.
The returned object will persist for the full lifetime of the component. It’s important to note that changing the
‘current’ property doesn’t cause a re-render.
local State = require("State")
local state = State.new()
-- Create a ref to store a DOM element
local inputRef = state:useRef(nil)
-- In your render function, you might use it like this:
-- <input ref={inputRef} />
-- Later in your code, you can access or modify the ref
local function handleClick()
inputRef.current:focus() -- Assuming 'current' is set to a DOM element that has a focus method
end
-- You can also use it to store any mutable value
local intervalRef = state:useRef(nil)
state:useEffect(function()
intervalRef.current = setInterval(function()
print("Interval triggered")
end, 1000)
return function()
clearInterval(intervalRef.current)
end
end, {})
State.rbxm (7.6 KB)