Skip to content

Instantly share code, notes, and snippets.

@ankushKun
Created August 31, 2025 14:46
Show Gist options
  • Select an option

  • Save ankushKun/21e317679a19c0cdcbfc39ef5d3ab0ca to your computer and use it in GitHub Desktop.

Select an option

Save ankushKun/21e317679a19c0cdcbfc39ef5d3ab0ca to your computer and use it in GitHub Desktop.
serving react like webapps from ao processes using LuaX
-- This app can be found on https://workshop.forward.computer/8miMYZuKvlFrVR-GIOpSybDT59PFK04gmKGIFUY8ZYI/now/app
local luax = require("luax")
-- LuaX: https://apm_betteridea.ar.io/pkg?id=@apm/luax
-- Reactive useState function that auto-rerenders when state changes
local function useState(initialValue, varName, Component)
local state = { initialValue }
local function getValue()
return state[1]
end
local function setValue(newValue)
-- Update the state
if type(newValue) == "function" then
state[1] = newValue(state[1])
else
state[1] = newValue
end
-- Re-render the component and update the global variable
if Component and varName then
_G[varName] = luax.render(Component())
print("Re-rendered " .. varName .. " with new value:", state[1])
end
end
return getValue, setValue
end
-- Example usage
num, setNum = useState(num and num() or 0, "app", function() return App() end)
-- JavaScript functions for frontend interaction
local jsScript = script {} { [[
// Function to disable all buttons
function disableAllButtons() {
const buttons = document.querySelectorAll('button');
buttons.forEach(button => {
button.disabled = true;
button.style.opacity = '0.6';
button.style.cursor = 'not-allowed';
});
}
// Function to show loading state
function showLoadingState(buttonElement, originalText) {
buttonElement.textContent = 'Loading...';
buttonElement.style.backgroundColor = '#6c757d';
}
async function makeRequest(action, buttonElement) {
const processId = '8miMYZuKvlFrVR-GIOpSybDT59PFK04gmKGIFUY8ZYI';
const url = `https://workshop.forward.computer/${processId}[email protected]/push&action=${action}&!`;
// Get original button text and show loading state
const originalText = buttonElement.textContent;
showLoadingState(buttonElement, originalText);
// Disable all buttons immediately
disableAllButtons();
try {
await fetch(url, { method: 'GET', mode: 'no-cors' });
console.log(`${action} action sent successfully`);
// Show success feedback
buttonElement.textContent = 'Success!';
buttonElement.style.backgroundColor = '#28a745';
// Reload page after a short delay
setTimeout(() => location.reload(), 500);
} catch (error) {
console.error(`Failed to send ${action} action:`, error);
// Show error feedback
buttonElement.textContent = 'Error!';
buttonElement.style.backgroundColor = '#dc3545';
// Still reload after error (in case the request actually went through)
setTimeout(() => location.reload(), 1000);
}
}
function incrementNumber() {
const button = event.target;
makeRequest('increment', button);
}
function decrementNumber() {
const button = event.target;
makeRequest('decrement', button);
}
function resetNumber() {
const button = event.target;
makeRequest('reset', button);
}
]] }
local styles = style {} { [[
button {
padding: 10px 20px;
margin: 5px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
}
button:hover:not(:disabled) {
background-color: #0056b3;
transform: translateY(-1px);
}
button:disabled {
cursor: not-allowed !important;
transform: none !important;
}
h1 {
color: #333;
text-align: center;
}
div {
text-align: center;
font-family: Arial, sans-serif;
margin: 10px 0;
}
.counter-display {
font-size: 24px;
font-weight: bold;
margin: 20px 0;
color: #007bff;
}
]] }
function App()
print("Rendering App with num:", num())
return div {} {
styles,
h1 {} { "Reactive Counter App" },
div { class = "counter-display" } { "Current Number: " .. tostring(num()) },
div {} {
button { onclick = "incrementNumber()" } { "Increment (+1)" },
button { onclick = "decrementNumber()" } { "Decrement (-1)" },
button { onclick = "resetNumber()" } { "Reset (0)" }
},
div {style="margin-top:10px;"} {
p{}{"The counter variable is stored in the hyper aos process, and gets served with the HTML of this page when you refresh it. SSR!?"},
p{}{"When you click any of the above buttons, a message gets sent to the process, which updates the variable and re-renders the HTML"},
p{}{"The frontend then refreshes to load the latest HTML from the process."},
},
jsScript
}
end
-- Message handlers that trigger state changes and auto re-render
Handlers.add("increment", function(msg)
setNum(num() + 1)
end)
Handlers.add("decrement", function(msg)
setNum(function(prev) return math.max(prev - 1, 0) end)
end)
Handlers.add("reset", function(msg)
setNum(0)
end)
-- Initial render
app = luax.render(App())
print("Initial app rendered with num =", num())
-- LuaX Module
local luax = {}
luax.elements = {}
-- Safe element creation with error handling
local function safeCreateElement(name, attrs, children)
local success, result = pcall(function()
return {
type = "html",
name = name,
atts = attrs or {},
children = children or {}
}
end)
if not success then
error("Failed to create element '" .. tostring(name) .. "': " .. tostring(result))
end
return result
end
-- Create HTML elements with a more elegant syntax
function luax.element(name, attrs, children)
if type(name) ~= "string" then
error("Element name must be a string, got " .. type(name))
end
return safeCreateElement(name, attrs, children)
end
-- Create text nodes with error handling
function luax.text(text)
local success, result = pcall(function()
return {
type = "text",
text = tostring(text or "")
}
end)
if not success then
error("Failed to create text node: " .. tostring(result))
end
return result
end
-- Create a component with error handling
function luax.component(render)
if type(render) ~= "function" then
error("Component render must be a function, got " .. type(render))
end
return function(props, children)
local success, result = pcall(function()
-- If first argument is not a table, it's children
if type(props) ~= "table" or props.type then
children = props
props = {}
end
-- Call the render function with props and children
return render(props, children)
end)
if not success then
error("Component render failed: " .. tostring(result))
end
return result
end
end
-- Helper function to create elements with the new syntax
function luax.create(name)
if type(name) ~= "string" then
error("Element name must be a string, got " .. type(name))
end
-- Return a function that handles both attributes and children
return function(attrs)
-- If attrs is a table of elements (children), treat it as children
if type(attrs) == "table" then
local isChildren = false
for _, v in pairs(attrs) do
if type(v) == "table" and v.type then
isChildren = true
break
end
end
if isChildren then
return safeCreateElement(name, {}, attrs)
end
end
-- Return a function that takes children
return function(children)
if type(children) == "table" then
-- If children is a table of elements, use it directly
local isElementList = true
for _, v in pairs(children) do
if type(v) ~= "table" or not v.type then
isElementList = false
break
end
end
if isElementList then
return safeCreateElement(name, attrs, children)
end
-- If children is a table of strings, convert to text nodes
local textChildren = {}
for _, child in pairs(children) do
if type(child) == "string" then
table.insert(textChildren, luax.text(child))
else
table.insert(textChildren, child)
end
end
return safeCreateElement(name, attrs, textChildren)
end
-- If children is a string, convert to text node
return safeCreateElement(name, attrs, { luax.text(children) })
end
end
end
-- Function to render HTML elements to string with error handling
function luax.render(element)
local success, result = pcall(function()
if type(element) ~= "table" then
return tostring(element or "")
end
if element.type == "text" then
return tostring(element.text or "")
end
if not element.name then
return ""
end
local attrs = ""
if element.atts then
for k, v in pairs(element.atts) do
if v ~= nil then
attrs = attrs .. string.format(' %s="%s"', k, tostring(v))
end
end
end
if not element.children or #element.children == 0 then
return string.format("<%s%s/>", element.name, attrs)
end
local children = ""
for _, child in ipairs(element.children) do
if child then
children = children .. luax.render(child)
end
end
return string.format("<%s%s>%s</%s>", element.name, attrs, children, element.name)
end)
if not success then
error("Failed to render element: " .. tostring(result))
end
return result
end
-- Initialize global element functions with a more elegant syntax
function luax.init()
local elements = {
"div", "span", "p", "h1", "h2", "h3", "h4", "h5", "h6",
"a", "img", "input", "button", "form", "label",
"option", "textarea", "ul", "ol", "li", "tr",
"td", "th", "thead", "tbody", "header", "footer", "nav",
"main", "section", "strong", "em",
"br", "hr", "meta", "link", "script", "style",
"iframe", "summary", "details", "article", "time", "aside", "dialog"
}
local reserved_names = { "table", "select" }
-- Create elements with error handling
for _, name in ipairs(elements) do
local success, result = pcall(function()
local element_func = luax.create(name)
luax.elements[name] = element_func
if not _G[name] then
_G[name] = element_func
else
-- print(name .. " already exists in global scope")
end
end)
if not success then
error("Failed to create element '" .. name .. "': " .. tostring(result))
end
end
-- Handle reserved names separately
for _, name in ipairs(reserved_names) do
local success, result = pcall(function()
local element_func = luax.create(name)
luax.elements["html_" .. name] = element_func
if not _G["html_" .. name] then
_G["html_" .. name] = element_func
else
-- print("html_" .. name .. " already exists in global scope")
end
end)
if not success then
error("Failed to create element '" .. name .. "': " .. tostring(result))
end
end
-- luax.elements = elements
end
luax.init()
_G.package.loaded["luax"] = luax
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment