Skip to content
LogoLogo

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 (
    <>
This JSX tag's 'children' prop expects a single child of type 'ReactElement<unknown, string | JSXElementConstructor<any>>', but multiple children were provided.
< /> < /> </> ) } // 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} />
Property 'children' is missing in type '{ order: number; }' but required in type '{ children: ReactElement<unknown, string | JSXElementConstructor<any>>; order?: number | undefined; }'.
}

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
])
codemeaning
duplicate-plugin-idTwo plugins share an id, so every contribution id they carry collides.
duplicate-contribution-idTwo contributions resolve to one full id. The first declaration wins; the second is dropped.
invalid-contribution-idA contribution id is empty or contains /. The contribution is dropped.
unknown-disable-targetdisable names a plugin or contribution that nothing in the list carries.
unknown-override-targetAn override targets a full id that nothing in the list carries.
override-slot-mismatchAn 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 < ={} />
Property 'props' is missing in type '{ slot: Slot<{ current: string; }>; }' but required in type '{ props: NoInfer<{ current: string; }>; }'.
} export function () { return < ={} ={{ : 42 }} />
Type 'number' is not assignable to type 'string'.
}

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", { :  })
Type '({ href }: { href: string; }) => Element' is not assignable to type 'ComponentType<{ current: string; }>'. Type '({ href }: { href: string; }) => Element' is not assignable to type 'FunctionComponent<{ current: string; }>'. Types of parameters '__0' and 'props' are incompatible. Property 'href' is missing in type '{ current: string; }' but required in type '{ href: string; }'.

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 <>{.}</>
'props' is possibly 'null'.
}

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.

Next

  • API — every export, with the types twoslash infers
  • FAQs — the questions that come up in a real application