Type-Safety
Type-safety is the most straightforward and powerful feature of react-slots
.
You're likely accustomed to annotating your children
as ReactNode
, but in
react-slots
, you should use two special types SlotChildren
and Slot
to
annotate your children
, and everything else will be automatically inferred.
Implementing a Type-Safe Component
Let's see how to create a fully type-safe component in react-slots
and delve
into the details later. This type-safe Dialog
component has two slots:
trigger
: A label for the button that opens the dialog.default
: Content that becomes visible after the user clicks the trigger button. It passes two props to its parent,isOpen: boolean
andclose: () => void
. A parent can access these props with adefault
template.
import {
SlotChildren,
Slot,
CreateTemplate,
useSlot,
template,
} from "@beqa/react-slots";
type Props = {
children: SlotChildren<
Slot<"trigger"> | Slot<{ isOpen: boolean; close: () => void }>
>;
};
function Dialog({ children }: Props) {
const { slot } = useSlot(children); // Inferred automatically
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>
<slot.trigger>Trigger Dialog</slot.trigger>
</button>
{isOpen && (
<slot.default isOpen={isOpen} close={() => setIsOpen(false)} />
)}
</>
);
}
// Create type-safe template specifically for dialog component
const dialogTemplate = template as CreateTemplate<Props["children"]>;
// Usage example
<Dialog>
<dialogTemplate.trigger>Open Dialog</dialogTemplate.trigger>
{/* No need to wrap in dialogTemplate.default, it's still type-safe */}
{(props) => (
<div>
<h2>This is a dialog that opens after you click "Open Dialog"</h2>
<button onClick={props.close}>Close</button>
</div>
)}
</Dialog>;
The SlotChildren
type
type SlotChildren
is a generic type that expects a union of Slot
types
as its only type parameter. It enforces that the children
of this component
should be nodes of the type described by the Slot
types. Note that Slot
types, independent of SlotChildren
, have no purpose.
The Slot
type
type Slot
is a generic type used to specify the slot name and props. It
comes in multiple forms:
- Two type parameters:
Slot<"foo", { bar: number }>
. The first parameter specifies the slot name, and the second specifies the slot props. - String as the only type parameter:
Slot<"foo">
. This is a shorthand forSlot<"foo", {}>
, useful when you only want to specify the slot name. - Object as the only type parameter:
Slot<{ baz: boolean }>
. This is a shorthand forSlot<"default", { baz: boolean }>
, used to specify props for thedefault
slot. - No type parameters:
Slot
. This is a shorthand forSlot<"default", {}>
, used for specifying thedefault
slot with no props.
The children
type annotation for the above Dialog
component states that it
expects at least one node as a child, which can be:
- An element or component with the
slot-name="trigger"
attribute or atemplate.trigger
element. - Any node, including strings, numbers, elements or components without a
slot-name
attribute or with aslot-name: "default"
attribute, or atemplate.default
element that receives theisOpen
andclose
props.
The CreateTemplate
Type
type CreateTemplate<T extends SlotChildren>
is a utility type that makes
templates type-safe. It expects the same children
type as the component it
belongs to. Template objects typed with CreateTemplate
only let you to use
predefined slot names and automatically provide type hints for props.
You can also create type-safe templates using the createTemplate
function:
import { createTemplate } from "@beqa/react-slots";
const fooTemplate = createTemplate<FooProps["children"]>();
Recommendation #1: Define and export type-safe templates in the same file as their components.
Recommendation #2: Be cautious when changing slot names during refactoring. TypeScript won't catch if you specify the wrong name with the slot-name attribute, but it will catch type-related errors when type-safe templates are used.