-
Notifications
You must be signed in to change notification settings - Fork 0
Hooks
github-actions[bot] edited this page Aug 13, 2026
·
1 revision
@nrbx/react provides the full React 17 hook set, plus a React 19-style polyfill layer for modern hook APIs. It is designed for Roblox TypeScript and follows the same mental model as React, while acknowledging a few runtime caveats specific to the Lua-based Roblox environment.
Important caveat: functional state updates such as
setState((prev) => prev + 1)may not be fully supported by the underlying Lua runtime. When necessary, prefer refs or direct values instead of relying on the functional updater form.
- Signature:
useState<T>(initial: T | (() => T)): [T, Dispatch<SetStateAction<T>>] - Description: Creates a state value and a setter for that value. The setter can be called with either a direct value or a state updater function when the runtime supports it.
- Example:
const Counter = () => {
const [count, setCount] = useState(0);
return (
<button
Event={{ Activated: () => setCount(count + 1) }}
>
Count: {count}
</button>
);
};- Roblox caveats:
- Functional form like
setCount((prev) => prev + 1)may not work reliably in the Lua runtime. - Prefer
setCount(count + 1)or keep the next value in a ref if you need to derive it from stale values.
- Functional form like
- Signature:
useEffect(effect: () => (void | (() => void)), deps?: Array<any>) - Description: Runs after render and can optionally return a cleanup function for subscriptions, connections, or timers.
- Example:
const PlayerStats = () => {
const [health, setHealth] = useState(100);
useEffect(() => {
const connection = Players.LocalPlayer.CharacterAdded.Connect(() => {
setHealth(100);
});
return () => connection.Disconnect();
}, []);
return <textlabel Text={`Health: ${health}`} />;
};- Roblox caveats:
- Use
Disconnect()when you connect to Roblox events likeRunService.Heartbeat,CollectionService, orRemoteEventcallbacks. - Keep dependency arrays minimal to avoid reconnecting on every render.
- Use
- Signature:
useContext<T>(context: Context<T>): T - Description: Reads the current value from the closest matching provider in the component tree.
- Example:
const ThemeContext = createContext({ accent: Color3.fromRGB(64, 128, 255) });
const ThemedButton = () => {
const theme = useContext(ThemeContext);
return <textbutton Text="Click me" BackgroundColor3={theme.accent} />;
};- Roblox caveats:
- Context is useful for shared configuration, theming, and runtime services.
- Keep provider trees shallow for easier debugging inside Roblox UI composition.
- Signature:
useReducer<R, I>(reducer, initialArg, init?) - Description: Manages more complex state transitions using a reducer function, similar to Redux-style updates.
- Example:
interface State {
count: number;
}
interface Action {
type: "increment" | "decrement";
}
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<textlabel Text={`Count: ${state.count}`} />
<button Event={{ Activated: () => dispatch({ type: "increment" }) }}>+1</button>
</>
);
};- Roblox caveats:
- Reducers are ideal for typed game state that changes in response to discrete actions.
- If your reducer logic depends on prior state in a callback, keep the action payload explicit and avoid hidden mutation.
- Signature:
useCallback<T>(fn: T, deps?: Array<any>): T - Description: Returns a memoized callback whose identity stays stable until the dependency list changes.
- Example:
const ButtonRow = () => {
const onPress = useCallback(() => {
print("Pressed");
}, []);
return <button Event={{ Activated: onPress }}>Press</button>;
};- Roblox caveats:
- Use this for Roblox event handlers passed into UI elements to avoid unnecessary re-renders.
- Keep deps accurate; stale closures are common in imperative game code.
- Signature:
useMemo<T>(factory: () => T, deps?: Array<any>): T - Description: Memoizes the result of a computed value and recomputes only when dependencies change.
- Example:
const itemSummary = useMemo(() => {
return `${count} items ready`;
}, [count]);- Roblox caveats:
- Useful for expensive derived values, but do not use it as a substitute for state.
- If the derived result is small and cheap, plain expressions are usually clearer.
- Signature:
useRef<T>(initial?: T): MutableRefObject<T> - Description: Creates a persistent ref that survives across renders without triggering re-renders when its
.currentvalue changes. - Example:
const textBoxRef = useRef<TextBox>();
const FocusInput = () => {
useEffect(() => {
textBoxRef.current?.CaptureFocus();
}, []);
return <textbox Ref={textBoxRef} />;
};- Roblox caveats:
- This is the standard way to access Roblox instances and persistent values from event handlers.
- Use it when you need mutable state that should not trigger rendering.
- Signature:
useImperativeHandle(ref, createHandle, deps?) - Description: Customizes the instance value exposed to a parent via a ref. Useful for forwarding a more ergonomic API.
- Example:
interface InputHandle {
focus: () => void;
}
const Input = forwardRef<InputHandle>((props, ref) => {
const innerRef = useRef<TextBox>();
useImperativeHandle(ref, () => ({
focus: () => innerRef.current?.CaptureFocus(),
}), []);
return <textbox Ref={innerRef} />;
});- Roblox caveats:
- Roblox UI refs are often instance refs, so this hook is useful when constructing a cleaner public API around nested components.
- Keep the handle object stable and avoid returning new objects on every render unless dependencies change.
- Signature:
useLayoutEffect(effect, deps?) - Description: Runs synchronously after Roblox mutations and before the browser/graphics paint cycle completes. It behaves like
useEffect, but with synchronous timing. - Example:
const Panel = () => {
const ref = useRef<Frame>();
useLayoutEffect(() => {
if (ref.current) {
ref.current.Size = UDim2.fromScale(0.5, 0.5);
}
}, []);
return <frame Ref={ref} />;
};- Roblox caveats:
- Use this when measuring or mutating GUI layout immediately after a render.
- Overusing it can lead to layout thrash; prefer
useEffectfor side effects that do not require synchronous DOM/GUI mutation timing.
- Signature:
useDebugValue(value, format?) - Description: Labels values in React DevTools for easier inspection during debugging.
- Example:
const usePlayerState = (player: Player) => {
const [ready, setReady] = useState(false);
useDebugValue(ready ? "ready" : "waiting");
return { ready, setReady };
};- Roblox caveats:
- This is mainly a debugging tool; it does not affect runtime behavior.
- In Roblox tools or custom dev environments, use concise labels to keep logs readable.
- Signature:
useId(): string - Description: Generates a stable, unique ID for a given component instance. Useful for ARIA-like attributes and form associations.
- Example:
const fieldId = useId();
return <textbox Key={fieldId} />;- Roblox caveats:
- The ID is stable across re-renders, but not globally unique beyond the component instance.
- Good for linking labels, inputs, and debug metadata in UI components.
- Signature:
useTransition(): [boolean, (callback: () => void) => void] - Description: Marks updates as low-priority transitions and provides a pending flag while the transition is in flight.
- Example:
const [isPending, startTransition] = useTransition();
const onFilterChange = (nextQuery: string) => {
startTransition(() => {
setQuery(nextQuery);
});
};- Roblox caveats:
- In Roblox, transition scheduling can be less meaningful than in web DOM, but it is still useful for deferring expensive UI updates.
- Do not assume the callback runs synchronously; treat it as deferred work.
- Signature:
useDeferredValue<T>(value: T): T - Description: Returns a deferred version of a value that updates more slowly than the source value to keep the UI responsive.
- Example:
const search = useDeferredValue(query);- Roblox caveats:
- Useful for large list filtering or expensive derived UI updates.
- This should be considered a responsiveness aid, not a data store or game state synchronization tool.
- Signature:
useSyncExternalStore<T>(subscribe, getSnapshot, getServerSnapshot?): T - Description: Subscribes to an external data source and returns the current snapshot while keeping it in sync.
- Example:
const health = useSyncExternalStore(
(callback) => {
const conn = RunService.Heartbeat.Connect(callback);
return () => conn.Disconnect();
},
() => Players.LocalPlayer.Character?.PrimaryPart?.Position ?? Vector3.zero,
);- Roblox caveats:
- This is particularly useful for data that exists outside React state, such as Roblox services, player state, or custom store objects.
- Always return a stable snapshot and clean up subscriptions.
- Signature:
useInsertionEffect(effect, deps?) - Description: Runs before layout effects and is intended for CSS-in-JS insertion timing.
- Example:
useInsertionEffect(() => {
print("Insert before layout work");
}, []);- Roblox caveats:
- In Roblox, there is no browser-style CSS insertion pipeline, so this hook is primarily a compatibility hook and may be equivalent to
useLayoutEffectin practice. - Use it only when you specifically need the insertion phase semantics.
- In Roblox, there is no browser-style CSS insertion pipeline, so this hook is primarily a compatibility hook and may be equivalent to
- Signature:
useOptimistic<T>(state: T, updateFn: (state: T, value: any) => T): [T, (value: any) => void] - Description: Lets a component optimistically update local UI before server confirmation arrives.
- Example:
const [optimisticMessages, setOptimisticMessage] = useOptimistic(
messages,
(current, value) => [...current, value],
);
const onSend = () => {
setOptimisticMessage({ id: "new", text: "Sending..." });
};- Roblox caveats:
- Useful for client-side responsiveness in UIs that communicate with remote services or datastore writes.
- The optimistic state should be reconciled with the server result once the real data returns.
useActionState<S, P>(fn: (prevState: S, payload: P) => Promise<S>, initialState: S, permalink?: string): [S, (payload: P) => void, boolean]
- Signature:
useActionState<S, P>(fn: (prevState: S, payload: P) => Promise<S>, initialState: S, permalink?: string): [S, (payload: P) => void, boolean] - Description: Tracks async state for form- or command-like actions, including a pending flag.
- Example:
const [status, submit, isPending] = useActionState(
async (prevState, payload: { message: string }) => {
const result = await SendChatMessage(payload.message);
return { ...prevState, lastMessage: result };
},
{ lastMessage: "" },
);
const onSubmit = () => submit({ message: "Hello" });- Roblox caveats:
- This is most useful for async mutation workflows like chat, profile updates, or remote commands.
-
isPendingis helpful for disabling buttons or showing loading state in the UI.
- Signature:
useSpring(target, config?): Motion<number> - Description: Creates a spring-driven animation value that smoothly animates to the target value.
- Example:
const x = useSpring(0, { type: "spring", tension: 180, friction: 12 });
useEffect(() => {
x.set(100);
}, []);- Roblox caveats:
- This is useful for smooth UI interpolation, such as tweening a
Frameposition or a value used in layout calculations. - Use the motion object’s
set()method to drive the value updates.
- This is useful for smooth UI interpolation, such as tweening a
- Signature:
useMotion(target, config?): Motion<MotionValue> - Description: Animates multi-value objects, such as position, scale, or combined physical motion values.
- Example:
const motion = useMotion({ x: 0, y: 0 }, {
type: "spring",
mass: 1,
tension: 200,
friction: 20,
});
useEffect(() => {
motion.set({ x: 200, y: 120 });
}, []);- Roblox caveats:
- Use this for complex motion state, especially when working with multiple values in parallel.
- Keep config values conservative if you are animating many objects simultaneously on low-end Roblox devices.
- Signature:
Motion<T> - Description: A motion object that wraps a value and exposes methods for reading and updating it over time.
- Members:
-
.current(alpha?): Reads the current motion value, optionally with interpolation alpha. -
.set(value): Sets the target value for the motion.
-
- Example:
const value = useSpring(0);
const current = value.current();
value.set(50);- Roblox caveats:
- The
.current()method is often used to read the current animation value for GUI layout or rendering. - Prefer
set()over direct mutation for animation-driven values.
- The
- Signature:
type SpringConfig = { type?: "spring"; mass?: number; tension?: number; friction?: number; velocity?: number; } - Description: Configuration used by spring-based motion. These values adjust stiffness, damping, and inertia.
- Example:
const config: SpringConfig = {
type: "spring",
mass: 1,
tension: 170,
friction: 26,
velocity: 0,
};- Roblox caveats:
- Tension and friction are the most important tuning knobs for smooth Roblox UI animation.
- Avoid very high tension or very low friction when animating many GUI elements at once.
- Signature:
useMemoCache(size: number): Array<any> - Description: Provides a fixed-size memory cache array for React Compiler (Forget) compatibility. It is primarily used by compiler-generated code paths.
- Example:
const memoCache = useMemoCache(8);
memoCache[0] = expensiveComputation();- Roblox caveats:
- This hook is mainly for compiler integration and should not usually be used directly in handwritten component code.
- If you are not targeting compiler-generated code, regular
useMemois the more idiomatic option.
- Prefer
useReffor instance access and mutable values that should not trigger a render. - Use
useEffectanduseLayoutEffectcarefully for event connections and GUI layout updates. - Be aware that the underlying runtime may not support every React 19 edge case exactly as the browser implementation does.
- Functional updates in
setStateare a known caveat; if the runtime does not support them consistently, store the next value explicitly or use refs. - For animations,
useSpringanduseMotionare designed to keep Roblox UI motion smooth while minimizing manual tween boilerplate.
This page covers the primary hooks available in @nrbx/react. For more details about provider patterns, lifecycle behavior, and Roblox UI composition, see the rest of the package documentation.