Skip to content

Instantly share code, notes, and snippets.

@RadicalZephyr
Last active September 7, 2025 10:23
Show Gist options
  • Select an option

  • Save RadicalZephyr/8b799935b7c04290a13f92c359bcdabc to your computer and use it in GitHub Desktop.

Select an option

Save RadicalZephyr/8b799935b7c04290a13f92c359bcdabc to your computer and use it in GitHub Desktop.
My custom React Hooks for creating Bitburner React Components
/**
* Create a FUID (fairly unique identifier)
*
* FUIDs have 3 components, the pid of the current process, the time
* in milliseconds since the epoch start (Jan. 1st, 1970), and a 6
* digit random number.
*
* @param {NS} ns - Netscript API instance
* @returns {string} A string representation of the FUID
*/
export function makeFuid(ns) {
const pid = ns.pid;
const ts = Date.now();
const r = Math.floor(Math.random() * 1e6);
return `${pid}-${ts}-${r}`;
}
/**
* Get an updating state value derived from polling the given function.
*
* @template T
* @param {number} interval - Milliseconds between polling `pollFn`
* @param {() => T} pollFn - Function to poll state changes
* @returns {T} Reactive state produced by `pollFn`
*/
export function usePoll(ns, interval, pollFn) {
const [data, setData] = React.useState(pollFn());
React.useEffect(() => {
let id;
const clearInterval = () => {
if (id != null) globalThis.clearInterval(id);
id = null;
};
id = globalThis.setInterval(() => {
try {
setData(pollFn());
} catch (err) {
console.error(err);
clearInterval();
}
}, interval);
const exitHandlerName = 'usePoll-' + makeFuid(ns);
ns.atExit(clearInterval, exitHandlerName);
return () => {
ns.atExit(() => null, exitHandlerName);
clearInterval();
};
}, [ns, interval, pollFn]);
return data;
}
/**
* Get an updating state value derived from polling the Netscript API
* with an update function.
*
* @template T
* @param {NS} ns - Netscript API instance
* @param {number} interval - Milliseconds between polling `updateFn`
* @param {(ns: NS) => T} updateFn - Function to poll state from Netscript APIs
* @returns {T} Reactive state produced by `updateFn`
*/
export function useNsUpdate(ns, interval, updateFn) {
const [data, setData] = React.useState(updateFn(ns));
React.useEffect(() => {
let id;
const clearInterval = () => {
if (id != null) globalThis.clearInterval(id);
id = null;
};
id = globalThis.setInterval(() => {
try {
setData(updateFn(ns));
} catch (err) {
console.error(err);
clearInterval();
}
}, interval);
const exitHandlerName = 'useNsUpdate-' + makeFuid(ns);
ns.atExit(clearInterval, exitHandlerName);
return () => {
ns.atExit(() => null, exitHandlerName);
clearInterval();
};
}, [ns, interval, updateFn]);
return data;
}
/**
* Keep a UserInterfaceTheme updated by polling `ns.ui.getTheme()`.
*
* @param {NS} ns - Netscript API instance
* @param {number} interval - Milliseconds between theme refreshes
* @returns {UserInterfaceTheme} The current theme from the UI
*/
export function useTheme(ns, interval = 200) {
return useNsUpdate(ns, interval, getTheme);
}
/**
* Return the current UI theme.
*
* @param {NS} ns
* @returns {UserInterfaceTheme}
*/
function getTheme(ns) {
return ns.ui.getTheme();
}
/**
* Create a FUID (fairly unique identifier)
*
* FUIDs have 3 components, the pid of the current process, the time
* in milliseconds since the epoch start (Jan. 1st, 1970), and a 6
* digit random number.
*
* @param {NS} ns - Netscript API instance
* @returns {string} A string representation of the FUID
*/
export function makeFuid(ns: NS): string {
const pid = ns.pid;
const ts = Date.now();
const r = Math.floor(Math.random() * 1e6);
return `${pid}-${ts}-${r}`;
}
/**
* Get an updating state value derived from polling the given function.
*
* @template T
* @param {number} interval - Milliseconds between polling `pollFn`
* @param {() => T} pollFn - Function to poll state changes
* @returns {T} Reactive state produced by `pollFn`
*/
export function usePoll<T>(ns: NS, interval: number, pollFn: () => T): T {
const [data, setData] = React.useState(pollFn());
React.useEffect(() => {
let id: number | null;
const clearInterval = () => {
if (id != null) globalThis.clearInterval(id);
id = null;
};
id = globalThis.setInterval(() => {
try {
setData(pollFn());
} catch (err) {
console.error(err);
clearInterval();
}
}, interval);
const exitHandlerName = 'usePoll-' + makeFuid(ns);
ns.atExit(clearInterval, exitHandlerName);
return () => {
ns.atExit(() => null, exitHandlerName);
clearInterval();
};
}, [ns, interval, pollFn]);
return data;
}
/**
* Get an updating state value derived from polling the Netscript API
* with an update function.
*
* @template T
* @param {NS} ns - Netscript API instance
* @param {number} interval - Milliseconds between polling `updateFn`
* @param {(ns: NS) => T} updateFn - Function to poll state from Netscript APIs
* @returns {T} Reactive state produced by `updateFn`
*/
export function useNsUpdate<T>(
ns: NS,
interval: number,
updateFn: (ns: NS) => T,
): T {
const [data, setData] = React.useState(updateFn(ns));
React.useEffect(() => {
let id: number | null;
const clearInterval = () => {
if (id != null) globalThis.clearInterval(id);
id = null;
};
id = globalThis.setInterval(() => {
try {
setData(updateFn(ns));
} catch (err) {
console.error(err);
clearInterval();
}
}, interval);
const exitHandlerName = 'useNsUpdate-' + makeFuid(ns);
ns.atExit(clearInterval, exitHandlerName);
return () => {
ns.atExit(() => null, exitHandlerName);
clearInterval();
};
}, [ns, interval, updateFn]);
return data;
}
/**
* Keep a UserInterfaceTheme updated by polling `ns.ui.getTheme()`.
*
* @param {NS} ns - Netscript API instance
* @param {number} interval - Milliseconds between theme refreshes
* @returns {UserInterfaceTheme} The current theme from the UI
*/
export function useTheme(ns: NS, interval = 200): UserInterfaceTheme {
return useNsUpdate(ns, interval, getTheme);
}
/**
* Return the current UI theme.
*
* @param {NS} ns
* @returns {UserInterfaceTheme}
*/
function getTheme(ns: NS): UserInterfaceTheme {
return ns.ui.getTheme();
}
import { useNsUpdate } from 'util/hooks';
import { STATUS_WINDOW_WIDTH, STATUS_WINDOW_HEIGHT, KARMA_HEIGHT, } from 'util/ui';
export async function main(ns) {
ns.disableLog('ALL');
ns.ui.openTail();
ns.ui.setTailTitle('Karma');
ns.ui.setTailFontSize(500);
ns.ui.resizeTail(STATUS_WINDOW_WIDTH, KARMA_HEIGHT);
const cellStyle = {
padding: '0 0.5em',
textAlign: 'left',
};
ns.clearLog();
ns.printRaw(React.createElement(Karma, { ns: ns, cellStyle: cellStyle }));
ns.ui.renderTail();
while (true) {
const [ww] = ns.ui.windowSize();
ns.ui.moveTail(ww - STATUS_WINDOW_WIDTH, STATUS_WINDOW_HEIGHT);
await ns.asleep(1000);
}
}
function Karma({ ns, cellStyle }) {
const karmaStats = useNsUpdate(ns, 100, getKarma);
return (React.createElement(React.Fragment, null,
React.createElement("table", null,
React.createElement("tbody", null,
React.createElement("tr", null,
React.createElement("td", { style: cellStyle }, "Karma: "),
React.createElement("td", { style: cellStyle }, ns.formatNumber(karmaStats.karma))),
React.createElement("tr", null,
React.createElement("td", { style: cellStyle }, "Victims: "),
React.createElement("td", { style: cellStyle }, karmaStats.numKilled))))));
}
function getKarma(ns) {
const player = ns.getPlayer();
const karma = player.karma;
const numKilled = player.numPeopleKilled;
return { karma, numKilled };
}
import { useNsUpdate } from 'hooks';
import {
STATUS_WINDOW_WIDTH,
STATUS_WINDOW_HEIGHT,
KARMA_HEIGHT,
} from 'util/ui';
export async function main(ns: NS) {
ns.disableLog('ALL');
ns.ui.openTail();
ns.ui.setTailTitle('Karma');
ns.ui.setTailFontSize(500);
ns.ui.resizeTail(STATUS_WINDOW_WIDTH, KARMA_HEIGHT);
const cellStyle = {
padding: '0 0.5em',
textAlign: 'left',
} as const;
ns.clearLog();
ns.printRaw(<Karma ns={ns} cellStyle={cellStyle} />);
ns.ui.renderTail();
while (true) {
const [ww] = ns.ui.windowSize();
ns.ui.moveTail(ww - STATUS_WINDOW_WIDTH, STATUS_WINDOW_HEIGHT);
await ns.asleep(1000);
}
}
interface KarmaProps {
ns: NS;
cellStyle: object;
}
function Karma({ ns, cellStyle }: KarmaProps) {
const karmaStats = useNsUpdate(ns, 100, getKarma);
return (
<>
<table>
<tbody>
<tr>
<td style={cellStyle}>Karma: </td>
<td style={cellStyle}>
{ns.formatNumber(karmaStats.karma)}
</td>
</tr>
<tr>
<td style={cellStyle}>Victims: </td>
<td style={cellStyle}>{karmaStats.numKilled}</td>
</tr>
</tbody>
</table>
</>
);
}
interface KarmaStats {
karma: number;
numKilled: number;
}
function getKarma(ns: NS): KarmaStats {
const player = ns.getPlayer();
const karma = player.karma;
const numKilled = player.numPeopleKilled;
return { karma, numKilled };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment