Errors
The library throws in six places, reports manifest defects as diagnostics,
and logs in two. Every message starts with [create-slot], so a search for
that prefix finds all of them.
Most of these mistakes are caught by TypeScript first. Where that is true, the type error is shown next to the runtime one.
Thrown errors
'defineSlot' requires a non-empty name
The Resolution keys contributions by slot name. An empty name is two slots quietly sharing one bucket, so it is refused at the call.
// The Resolution keys contributions by slot name, so an empty one is two
// slots quietly sharing a bucket.
export const = ("") // throws
export const = ("deal-actions")Use a stable, application-wide string, and prefix names that a library exports.
'definePlugin' requires a non-empty id
The id namespaces every contribution id and React key, so it has to identify the plugin.
// The id namespaces every contribution id and React key.
export const = ({ : "" }) // throws
export const = ({ : "pricing" })A contribution's id, by contrast, is validated by the resolver: a plugin's typo surfaces as a diagnostic, not a throw at import time.
'SlotHost' rendered outside of 'SlotProvider'
SlotHost reads the Resolution from context. No provider means the application
never mounted the registry, which is a mistake worth reporting rather than an
empty slot.
Mount SlotProvider above every host:
// `SlotHost` reads the Resolution from context, so the provider has to be
// above it.
export function () {
return (
< ={([])}>
< ={} />
</>
)
}createSlot's host is the exception. The façade never had a provider, so its
host renders its factory's fills — or its placeholder — anywhere.
A fill expects a single React element as its child
A façade fill clones its child to stamp a stable React key on it. A string, a number, an array or a fragment of siblings cannot carry one.
// Two children: throws at render.
export function () {
return (
<> < />
< />
</>
)
}
// One element that contains both.
export function () {
return (
<>
<>
< />
< />
</>
</>
)
}Text is the same case: wrap it in an element.
'Slot' without children rendered
createSlot's Slot is the contributor, so a Slot with nothing to contribute
is always a mistake. TypeScript rejects it first:
export function () {
return < ={10} />}To contribute nothing, do not render the Slot at all — installing a runtime
feature is mounting it, so uninstalling it is &&.
'useContribution' called outside of a plugin contribution
useContribution reads the context ContributionBoundary provides around each
declared contribution. It throws anywhere else, including inside a façade fill,
which belongs to no plugin.
// Works: the host renders this component as a declared contribution, and the
// context that carries the identity is the one its boundary provides.
function () {
const { , } = ()
return < ={} ={} />
}
export const = ({
: "reporting",
: [.("card", { : })],
})That throw is what makes the hook usable as a store key: it cannot silently return the wrong plugin.
Diagnostics
The resolver never throws over a manifest defect — a typo in one plugin must
not take the application down. Problems come back on
resolution.diagnostics, and in development the provider prints each set once
per content change with console.error.
// The resolver never throws over a manifest defect: it reports. Assert on the
// list in a test, and the provider prints it once per content change in
// development.
export const { } = ([
({ : "pricing" }),
({ : "pricing" }), // duplicate-plugin-id
])| code | meaning |
|---|---|
duplicate-plugin-id | Two plugins share an id, so every contribution id they carry collides. |
duplicate-contribution-id | Two contributions resolve to one full id. The first declaration wins; the second is dropped. |
invalid-contribution-id | A contribution id is empty or contains /. The contribution is dropped. |
unknown-disable-target | disable names a plugin or contribution that nothing in the list carries. |
unknown-override-target | An override targets a full id that nothing in the list carries. |
override-slot-mismatch | An override created by one slot targets a contribution of another. The patch is ignored. |
Disabling is the integrator's intent, not a defect — a disabled target that exists produces no diagnostic. Names, versions, capabilities and routes are your own manifest fields, so they are yours to check — see Recipes.
Logged warnings
The host for "x" was given both renderEntries and children
[create-slot] The host for "toolbar" was given both 'renderEntries' and children; children are ignored while 'renderEntries' is set.
Logged with console.error in development only. renderEntries owns the empty
state, so the children could never render — one of the two has to go.
Type errors
These never reach runtime. They are listed because the message names a structural type, and the cause is easier to see in an example.
A missing or mismatched props bag
props is an explicit bag typed by the slot. It is required as soon as the
slot declares props, and its values are checked against them.
export function () {
return < ={} />}
export function () {
return < ={} ={{ : 42 }} />}Declare the props on the slot, since they are the host's:
// The type parameter is the host's props. Declare them on the slot.
export const = <{ : string }>("status-bar")A contribution that asks for props the slot does not give
A contribution receives the host's props as its own, so its parameter type must accept them.
function ({ }: { : string }) {
return <>{}</>
}
const = .("nav-item", { : })A contribution may accept fewer props than the host provides — that is ordinary assignability — but never different ones.
useSlotProps() is possibly null
useSlotProps returns Props | null, because nothing guarantees a host above
the caller.
function () {
const = ()
return <>{.}</>}Handle the null case:
// `useSlotProps` is nullable, because no host is guaranteed above the caller.
export function () {
const = ()
if (!) {
return null
}
return <>{.}</>
}createSlot's useProps is not nullable: a Slot's children only ever render
inside a host.
React errors you may see through this library
Hydration mismatch
The server and the client resolved from different inputs, or the same inputs in a different order. Matching inputs are the library's only SSR requirement — see the SSR contract.
Send the enabled ids with the HTML instead of recomputing them on the client — or resolve on the server and send the Resolution itself.
This Suspense boundary received an update before it finished hydrating
An urgent state update reached a streamed boundary that had not hydrated yet, so React discarded the streamed HTML and re-rendered it on the client.
The usual source is a setup loop that registers commands from an effect. Wrap
the registration in startTransition — see
Plugin state.
A missing key warning
Not from this library. Every host keys declared contributions with the full id
pluginId/contributionId, and a façade fill gets a key stamped on the cloned
element when it mounts. A key warning points at a list of your own — including
wrappers you build in renderEntries, which should be keyed with entry.key.