Skip to content

Contexts

Because component functions return plain DOM elements, you cannot call methods on them after they are created. To let components interact, domstatejsx attaches contexts to the DOM itself. We will look at two scenarios:

  • A child component wants to invoke an action on a parent component (upwards)
  • A parent component wants to invoke an action on a child component (downwards)

The simplest way to interact with a parent is to pass a callback as a prop:

export default function App() {
const refs = useRefProxy();
const [, setCount] = useIntContent(refs.span);
function increment() {
setCount((prev) => prev + 1);
}
return (
<>
<span ref={refs.span}>0</span>
<ButtonContainer onClick={increment} />
</>
);
}
function ButtonContainer({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}

Alternatively, a parent can expose its functionality through context, and a child can look it up by walking up the DOM tree.

export default function App() {
const refs = useRefProxy();
const [, setCount] = useIntContent(refs.span);
function increment() {
setCount((prev) => prev + 1);
}
return (
<App.Context.Provider value={{ increment }}>
<span ref={refs.span}>0</span>
<ButtonContainer />
</App.Context.Provider>
);
}
App.Context = createContext();
function ButtonContainer() {
const refs = useRefProxy();
function handleClick() {
const { increment } = useContextUp(refs.head.current, App.Context);
increment();
}
return <button onClick={handleClick} ref={refs.head}>Click me</button>;
}

The context is attached to the DOM. useContextUp uses a DOM element as the starting point and walks up until it finds a node with an App.Context associated with it, then returns its value.

Downwards, accessing context through a ref

Section titled “Downwards, accessing context through a ref”

To let a parent call methods on a child, the child exposes its functionality through context, and the parent attaches a ref to the child component. The ref’s .context property contains all the contexts that were attached to that element:

function Counter() {
const refs = useRefProxy();
const [, setSpan] = useIntContent(refs.span);
function increment() {
setSpan((prev) => prev + 1);
}
return (
<Counter.Context.Provider value={{ increment }}>
<span ref={refs.span}>0</span>
</Counter.Context.Provider>
);
}
Counter.Context = createContext();
export default function App() {
const refs = useRefProxy();
function handleClick() {
refs.counter.context.increment();
}
return (
<>
<Counter ref={refs.counter} />
<button onClick={handleClick}>Click me</button>
</>
);
}

To affect many child components at once, search for context downwards:

export default function App() {
const refs = useRefProxy();
function handleClick() {
useContextDown(refs.head.current, Counter.Context).forEach(
({ increment }) => increment(),
);
}
return (
<div ref={refs.head}>
<Counter />
<Counter />
<Counter />
<button onClick={handleClick}>Click me</button>
</div>
);
}

useContextDown searches the DOM under the starting node and returns a list of all matching contexts.

Creates a new context object.

A component that receives a value and attaches it to the DOM so that it can be found by context lookups. It must wrap a single element — wrapping a fragment-returning component throws an error.

<App.Context.Provider value={{ increment }}>
<div>...</div>
</App.Context.Provider>

Walks up from node looking for a provider for context. Returns the value or null.

Searches the subtree under node for providers for context. Returns a list of values.

First walks up from node until it finds a provider for upContext, then searches downwards from there for providers for context. Useful for reaching “sibling” components: find the common ancestor first, then search its children.

Like useContextUp but returns the provider’s DOM element (or null) instead of its value.