Render Types
Some component libraries or design systems may want to restrict how components may be composed. For example, a Menu should only ever render MenuItems as children. Render types are a built-in way to support these constraints while still affording users rich flexibility in how they use those components.
Basic Behavior
A component can declare what it renders using the renders keyword:
import * as React from 'react';
component Header(size: string, color: string) { return <div /> }
component LargeHeader(color: string) renders Header {
return <Header size="large" color={color} />; // Ok!
} When you declare that your component renders some specific element, you can return any component that eventually renders that component in its renders chain:
import * as React from 'react';
component Header(size: string, color: string) { return <div /> }
component LargeHeader(color: string) renders Header {
return <Header size="large" color={color} />;
}
component LargeBlueHeader() renders Header {
// You could also use `renders LargeHeader` above
return <LargeHeader color="blue" />;
} Components can specify props that render specific elements. You can pass an element of Header, or of any component that renders Header, to such a prop, but not a component that does not render a Header:
import * as React from 'react';
component Header(size: string, color: string, message: string) {
return <h1 style={{color}}>{message}</h1>;
}
// Renders a Header in its renders chain.
component LargeBlueHeader() renders Header {
return <Header size="large" color="blue" message="Hello" />;
}
// Does not render a Header.
component Footer() {
return <footer />;
}
// The header prop only accepts elements that render a Header.
component Layout(header: renders Header) {
return (
<div>
{header}
<section>Hi</section>
</div>
);
}
<Layout header={<Header size="large" color="blue" message="Hi" />} />; // OK
<Layout header={<LargeBlueHeader />} />; // OK: LargeBlueHeader renders Header
<Layout header={<Footer />} />; // Error: Footer does not render Header Integrating with a design system
Render types are designed to make integrating with a design system simple. If a prop in the design system component expects a render type, you can copy/paste that type onto your component to integrate with the design system:
import * as React from 'react';
component Header() {
return <h1>Header!</h1>;
}
component Layout(header: renders Header) {
return <div>{header}</div>;
}
// Copy-paste the header props' type!
component ProductHeader() renders Header {
// We must return a value that renders a Header to satisfy the signature
return <Header />;
}
// And now you can integrate with the design system!
<Layout header={<ProductHeader />} />; // OK! Rendering Optional Elements
You may want to describe a component that can take a child that may eventually render an element or nothing. You can use a specialized render type variant renders? to achieve this:
import * as React from 'react';
component DesignSystemCardFooter() {
return <div>Footer Content</div>;
}
component DesignSystemCard(
children: React.Node,
footer: renders? DesignSystemCardFooter,
) {
return <div>{children}{footer}</div>;
}
// With these definitions, all of the following work:
<DesignSystemCard footer={<DesignSystemCardFooter />}>Card</DesignSystemCard>;
<DesignSystemCard footer={null}>Card</DesignSystemCard>;
<DesignSystemCard footer={undefined}>Card</DesignSystemCard>;
<DesignSystemCard footer={false}>Card</DesignSystemCard>;
component ProductFooter(hasFooter?: boolean) renders? DesignSystemCardFooter {
return hasFooter && <DesignSystemCardFooter />;
}
<DesignSystemCard footer={<ProductFooter />}>Card</DesignSystemCard>; Rendering Lists
You may want to describe a component that can take any amount of children that render a specific element as props. You can use a specialized render type variant renders* to achieve this:
import * as React from 'react';
component DesignSystemMenuItem() {
return <li>Menu Item</li>;
}
component DesignSystemMenu(
children: renders* DesignSystemMenuItem,
) {
return <ul>{children}</ul>
}
// With these definitions, all of the following work:
const menu1 = (
<DesignSystemMenu>
<DesignSystemMenuItem />
</DesignSystemMenu>
);
const menu2 = (
<DesignSystemMenu>
<DesignSystemMenuItem />
<DesignSystemMenuItem />
</DesignSystemMenu>
);
const menu3 = (
<DesignSystemMenu>
{[
<DesignSystemMenuItem />,
<DesignSystemMenuItem />,
]}
<DesignSystemMenuItem />
</DesignSystemMenu>
);
component ProductMenuItem() renders DesignSystemMenuItem {
return <DesignSystemMenuItem />;
}
const menu4 = (
<DesignSystemMenu>
{[
<ProductMenuItem />,
<DesignSystemMenuItem />,
]}
<DesignSystemMenuItem />
</DesignSystemMenu>
); Transparent Components
Components can be "transparent":
import * as React from 'react';
component TransparentComponent<T extends React.Node>(children: T) renders T {
// .. do something
return children;
}
component Header(text: string) {
return <h1>{text}</h1>;
}
component InstagramHeader() renders Header {
return <Header text="Instagram" />;
}
component Layout(
header: renders Header,
) {
return <div>{header}</div>;
}
component Page() {
const wrappedHeader = <TransparentComponent><InstagramHeader /></TransparentComponent>
return <Layout header={wrappedHeader} />; // Ok!
} Interop with non-Component-Syntax components
You can use renders to annotate function components as well:
import * as React from 'react';
component Header(text: string) {
return <h1>{text}</h1>;
}
component InstagramHeader() renders Header {
return <Header text="Instagram" />;
}
component Layout(
header: renders Header,
) {
return <div>{header}</div>;
}
function FunctionHeader(): renders Header {
return <InstagramHeader />;
}
function InstagramPage() {
return <Layout header={<FunctionHeader />} />; // OK!
} Subtyping Behavior
All render types are subtypes of both React.Node and React.MixedElement.
import * as React from 'react';
component Header() {
return <h1>Hello Header!</h1>;
}
declare const rendersHeader: renders Header;
declare const rendersMaybeHeader: renders? Header;
declare const rendersHeaderList: renders* Header;
rendersHeader as React.Node;
rendersMaybeHeader as React.Node;
rendersHeaderList as React.Node;
rendersHeader as React.MixedElement;
rendersMaybeHeader as React.MixedElement;
rendersHeaderList as React.MixedElement; renders Foo is a subtype of renders? Foo, and renders? Foo is a subtype of renders* Foo.
import * as React from 'react';
component Header() {
return <h1>Hello Header!</h1>;
}
declare const rendersHeader: renders Header;
declare const rendersMaybeHeader: renders? Header;
declare const rendersHeaderList: renders* Header;
rendersHeader as renders? Header;
rendersHeader as renders* Header;
rendersMaybeHeader as renders* Header;
rendersMaybeHeader as renders Header; // ERROR
rendersHeaderList as renders Header; // ERROR
rendersHeaderList as renders? Header; // ERROR Common Issues
Render types are opaque at runtime
Render types are a type-only concept — you cannot inspect or compare component types at runtime. For example, you cannot check which component was passed to a render-typed prop:
// This does NOT work — you can't compare component identity at runtime
if (icon === FancyIcon) { ... } If you need to distinguish between different rendered components at runtime, use a disjoint union wrapper instead:
import * as React from 'react';
component FancyIcon() { return <span />; }
component BasicIcon() { return <span />; }
type IconProp =
| {type: 'fancy', node: renders FancyIcon}
| {type: 'basic', node: renders BasicIcon};
component Layout(icon: IconProp) {
if (icon.type === 'fancy') {
icon.node as renders FancyIcon; // Works!
}
return <div>{icon.node}</div>;
} See Also
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/react/render-types