Context
Contexts can be used to implicitly pass static key value pairs along the call stack.
Contexts automatically work with synchronous code & all rvx APIs.
- The
inject
function runs a callback and provides a single key value pair. - The
extract
function gets a value from the current context.
import { inject, extract } from "rvx";
inject("message", "Hello World!", () => {
extract("message"); // "Hello World!"
extract("something else"); // undefined
});
import { inject, extract } from "./rvx.js";
inject("message", "Hello World!", () => {
extract("message"); // "Hello World!"
extract("something else"); // undefined
});
To inject multiple keys or to delete keys from a context, use deriveContext
:
import { deriveContext, extract } from "rvx";
deriveContext(ctx => {
ctx.set("message", "Hello World!");
ctx.delete("something else");
extract("message"); // "Hello World!"
});
import { deriveContext, extract } from "./rvx.js";
deriveContext(ctx => {
ctx.set("message", "Hello World!");
ctx.delete("something else");
extract("message"); // "Hello World!"
});
Components
When rendering content, you can use the <Inject>
and <DeriveContext>
components with JSX or the functions specified above:
import { Inject, DeriveContext, extract } from "rvx";
<Inject key="value" value={42}>
{() => <>Value: {extract("value")}</>}
</Inject>
<DeriveContext>
{ctx => {
ctx.set("value", 42);
return <>Value: {extract("value")}</>;
}}
</DeriveContext>
import { inject, deriveContext, extract } from "./rvx.js";
inject("value", 42, () => {
return ["Value: ", extract("value")];
})
deriveContext(ctx => {
ctx.set("value", 42);
return ["Value: ", extract("value")];
})
Typed Keys
Context values are typed as unknown
by default.
You can use symbols in combination with the ContextKey
type as keys:
import { ContextKey, inject, extract } from "rvx";
const MESSAGE = Symbol("message") as ContextKey<string>;
inject(MESSAGE, "Hello World!", () => {
extract(MESSAGE); // Type: string | undefined
});
// This is now a compiler error:
inject(MESSAGE, 42, () => { ... });
import { inject, extract } from "./rvx.js";
/** @type {import("./rvx.js").ContextKey<string>} */
const MESSAGE = Symbol("message");
inject(MESSAGE, "Hello World!", () => {
extract(MESSAGE); // Type: string | undefined
});
// This would now be a compiler error when using ts-check:
inject(MESSAGE, 42, () => { ... });
Async Code
Since contexts rely on the synchronous call stack, they only work partially with async code:
import { inject, extract } from "rvx";
inject("message", "Hello World!", async () => {
extract("message"); // "Hello World!"
await something;
extract("message"); // undefined
});
import { inject, extract } from "./rvx.js";
inject("message", "Hello World!", async () => {
extract("message"); // "Hello World!"
await something;
extract("message"); // undefined
});
You can manually pass contexts to somewhere else to fix this:
import { inject, extract, getContext, runInContext } from "rvx";
inject("message", "Hello World!", async () => {
// Get a reference to the current context:
const context = getContext();
await something;
// Run a function within the context from above:
runInContext(context, () => {
extract("message"); // "Hello World!"
});
});
import { inject, extract, getContext, runInContext } from "./rvx.js";
inject("message", "Hello World!", async () => {
// Get a reference to the current context:
const context = getContext();
await something;
// Run a function within the context from above:
runInContext(context, () => {
extract("message"); // "Hello World!"
});
});
Troubleshooting
Context Key Typos
Ensure that the key
argument is the same everywhere.
inject("message", "Hello World!", () => {
// There is a typo here:
extract("nessage");
});
To avoid this, you can use typed context keys:
const MESSAGE = Symbol.for("example-message") as ContextKey<string>;
inject(MESSAGE, "Hello World!", () => {
// This typo is now a compiler error:
extract(NESSAGE);
});
Extract Running Too Late
extract
must be called synchronously while the callback passed to inject
or deriveContext
is running.
inject(MESSAGE, "Hello World!", () => {
queueMicrotask(() => {
// This runs after the inject call has already ended:
extract(MESSAGE); // undefined
});
});
To solve this, you can forward the context as follows:
inject(MESSAGE, "Hello World!", () => {
// Bind the current context to your callback:
queueMicrotask(wrapContext(() => {
extract(MESSAGE); // "Hello World!"
}));
// Or manually pass the context to somewhere else:
const context = getContext();
queueMicrotask(() => {
runInContext(context, () => {
extract(MESSAGE); // "Hello World!"
});
});
});
Extract Running Too Early
When using deriveContext
, the context must be modified before extract
is called.
deriveContext(ctx => {
// This doesn't work:
extract(MESSAGE); // undefined
ctx.set(MESSAGE, "Hello World!");
// This works:
extract(MESSAGE); // "Hello World!"
});