Custom Formula Functions: Sync & Async
This demo defines two custom formula functions and compares React, Vue, and Svelte using live GitHub data:
GH_REPO(repo)— async (BaseFunctionAsync). Fetches stars, forks, open issues, size, and subscribers from the GitHub API and spills them across columns B–F. Cells show a loading animation while the request is in flight.REPO_OWNER(repo)— sync (BaseFunction). Extracts the owner from anowner/reposlug in column G, instantly.
The Total row uses SUM; it stays pending until every async cell resolves.
Debugger (inspect async evaluation & caching)
💡 GH_REPO(repo) (async) makes one API call per row and spills [[stars, forks, issues, size, subscribers]] across B–F — 3 calls instead of 15. Results are cached for 1 minute. REPO_OWNER(repo) (sync) in column G resolves instantly. Edit a repository name in column A to refetch; the Total row stays pending until every async cell resolves.
Implementation Guide
Section titled “Implementation Guide”📄 View Source Code
Sync vs Async
Section titled “Sync vs Async”- Use
BaseFunctionwhen the result can be computed immediately from the arguments (string/number transforms, validation, lookups in memory).mainreturns the value directly. - Use
BaseFunctionAsyncwhen the result needs I/O (HTTP requests, IndexedDB, workers).mainreturns aPromise; GridSheet tracks the pending state and re-renders the cell when it resolves.
Sync Function with BaseFunction
Section titled “Sync Function with BaseFunction”import { BaseFunction, ensureString } from '@gridsheet/react-core';
class RepoOwnerFunction extends BaseFunction { example = 'REPO_OWNER("facebook/react")'; defs = [{ name: 'repo', description: '"owner/repo" or a github.com URL', acceptedTypes: ['string'] }];
main(repo: string) { const slug = ensureString(repo) .trim() .replace(/^https?:\/\/github\.com\//, ''); const owner = slug.split('/')[0]; if (!owner) { throw new Error(`Invalid repository: "${repo}"`); } return owner; }}A thrown error is shown as an error in the cell.
Async Function with BaseFunctionAsync
Section titled “Async Function with BaseFunctionAsync”import { BaseFunctionAsync, ensureString } from '@gridsheet/react-core';
class GhRepoFunction extends BaseFunctionAsync { example = 'GH_REPO("facebook/react")'; defs = [{ name: 'repo', description: '"owner/repo" format', acceptedTypes: ['string'] }]; ttlMilliseconds = 60 * 1000; // cache result for 1 minute protected autoSpilling = true; // spill the returned 2D array protected broadcastDisabled = true; // treat the argument as a single value
async main(repo: string) { const r = ensureString(repo).trim(); const resp = await fetch(`https://api.github.com/repos/${encodeURI(r)}`, { headers: { Accept: 'application/vnd.github.v3+json' }, }); if (!resp.ok) { throw new Error(`GitHub API error for ${r}: ${resp.status}`); } const data = await resp.json(); return [[data.stargazers_count, data.forks_count, data.open_issues, data.size, data.subscribers_count]]; }}Registering Functions
Section titled “Registering Functions”Register both via additionalFunctions in useSpellbook. The formula name is the key uppercased:
const book = useSpellbook({ additionalFunctions: { gh_repo: GhRepoFunction, // =GH_REPO(A1) repo_owner: RepoOwnerFunction, // =REPO_OWNER(A1) },});Spilling Results Across Multiple Columns
Section titled “Spilling Results Across Multiple Columns”With autoSpilling = true, the 2D array returned by main (or resolved by its Promise) is wrapped in a Spilling automatically and overflows into adjacent cells. Without the flag, you can return new Spilling(matrix) yourself (Spilling is exported from @gridsheet/react-core) — but for an async function prefer autoSpilling, which wraps the resolved matrix.
In this demo, =GH_REPO(A1) in B1 fills B1:F1 with a single API call — 5 values per row, 3 repos, so 3 API calls instead of 15.
Pending State Propagation
Section titled “Pending State Propagation”When an async cell is still resolving, GridSheet marks it as Pending. Any formula that depends on a Pending cell (e.g., =SUM(B1:B3)) automatically waits and displays the loading animation until all upstream cells settle.
In this demo, the Total row uses =SUM(B1:B3) for each column. It stays pending until all three GH_REPO calls complete.
Built-in Result Caching via ttlMilliseconds
Section titled “Built-in Result Caching via ttlMilliseconds”Set ttlMilliseconds on the function class to enable automatic result caching. The same arguments will return the cached result without making a new network request until the TTL expires.
ttlMilliseconds = 60 * 1000; // cache for 60 secondsThis prevents redundant API calls when a formula is re-evaluated due to an unrelated cell change.
Making Cells Editable to Trigger Re-fetches
Section titled “Making Cells Editable to Trigger Re-fetches”Because the repo slug in column A is a plain string value, the user can edit it directly in the grid. When the value changes, both =GH_REPO(A1) and =REPO_OWNER(A1) are re-evaluated — the sync one immediately, the async one after its fetch.
The Debugger Component
Section titled “The Debugger Component”@gridsheet/react-dev exports a Debugger component that visualizes the current book state — pending cells, resolved values, and formula dependency graphs. It is useful during development to inspect async resolution.
import { Debugger } from '@gridsheet/react-dev';
<GridSheet book={book} ... /><Debugger book={book} />