-
Notifications
You must be signed in to change notification settings - Fork 0
Custom Hooks
github-actions[bot] edited this page Aug 13, 2026
·
1 revision
Custom hooks are the best way to package Roblox-specific logic into reusable behavior. Do not put RunService or TweenService logic directly in multiple components when a hook can hide that complexity.
import React, { useEffect, useState } from "@nrbx/react";
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const update = () => {
const viewport = game.GetService("Workspace").CurrentCamera.ViewportSize;
setSize({
width: viewport.X,
height: viewport.Y,
});
};
update();
const connection = game.GetService("Workspace").CurrentCamera.Changed.Connect(update);
return () => connection.Disconnect();
}, []);
return size;
}This kind of hook can be reused across HUD, minimaps, and menus without duplicating event setup.
import React, { useEffect, useMemo, useState } from "@nrbx/react";
function useRunService(eventName: "Heartbeat" | "RenderStepped") {
const [time, setTime] = useState(0);
useEffect(() => {
const connection = game.GetService("RunService")[eventName].Connect((dt) => {
setTime((value) => value + dt);
});
return () => connection.Disconnect();
}, [eventName]);
return time;
}
function useTween(target: Instance, goal: TweenInfo, properties: Record<string, unknown>) {
const [tween, setTween] = useState<Instance | null>(null);
useEffect(() => {
const t = game.GetService("TweenService").Create(target, goal, properties);
setTween(t);
t.Play();
return () => {
t.Cancel();
};
}, [target, goal, properties]);
return tween;
}
function useDebounce<T>(value: T, delayMs: number) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const handle = task.delay(delayMs, () => setDebounced(value));
return () => task.cancel(handle);
}, [value, delayMs]);
return debounced;
}The key is to compose small hooks into bigger behaviors instead of implementing app logic at the component level.
function SearchBox() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 200);
const elapsed = useRunService("Heartbeat");
const filteredResults = useMemo(() => {
if (debouncedQuery === "") return [];
return ["sword", "shield", "potion"].filter((item) => item.includes(debouncedQuery));
}, [debouncedQuery]);
return (
<frame>
<textbox Event={{ FocusLost: (instance) => setQuery(instance.Text) }} />
<textlabel Text={`${filteredResults.length} matches`} />
<textlabel Text={`time: ${elapsed.toFixed(2)}`} />
</frame>
);
}This is a practical way to build UI that responds to input while staying cheap to compute.