Skip to main content
Version: v3

Working with Functional Components

Functional components are quite different to normal Stencil web components because they are a part of Stencil's JSX compiler. A functional component is basically a function that takes an object of props and turns it into JSX.

const Hello = props => <h1>Hello, {props.name}!</h1>;

When the JSX transpiler encounters such a component, it will take its attributes, pass them into the function as the props object, and replace the component with the JSX that is returned by the function.

<Hello name="World" />

Functional components also accept a second argument children.

const Hello = (props, children) => [
<h1>Hello, {props.name}</h1>,
children
];

The JSX transpiler passes all child elements of the component as an array into the function's children argument.

<Hello name="World">
<p>I'm a child element.</p>
</Hello>

Stencil provides a FunctionalComponent generic type that allows to specify an interface for the component's properties.

// Hello.tsx

import { FunctionalComponent, h } from '@stencil/core';

interface HelloProps {
name: string;
}

export const Hello: FunctionalComponent<HelloProps> = ({ name }) => (
<h1>Hello, {name}!</h1>
);

Working with children

The second argument of a functional component receives the passed children, but in order to work with them, the FunctionalComponent provides an utils object that exposes a map() method to transform the children, and forEach() to read them. Reading the children array is not recommended since the stencil compiler can rename the vNode properties in prod mode.

export interface FunctionalUtilities {
forEach: (children: VNode[], cb: (vnode: ChildNode, index: number, array: ChildNode[]) => void) => void;
map: (children: VNode[], cb: (vnode: ChildNode, index: number, array: ChildNode[]) => ChildNode) => VNode[];
}
export interface ChildNode {
vtag?: string | number | Function;
vkey?: string | number;
vtext?: string;
vchildren?: VNode[];
vattrs?: any;
vname?: string;
}

Example:

export const AddClass: FunctionalComponent = (_, children, utils) => (
utils.map(children, child => ({
...child,
vattrs: {
...child.vattrs,
class: `${child.vattrs.class} add-class`
}
}
))
);
note

When using a functional component in JSX, its name must start with a capital letter. Therefore it makes sense to export it as such.

Disclaimer

There are a few major differences between functional components and class components. Since functional components are just syntactic sugar within JSX, they...

  • aren't compiled into web components,
  • don't create a DOM node,
  • don't have a Shadow DOM or scoped styles,
  • don't have lifecycle hooks,
  • are stateless.

When deciding whether to use functional components, one concept to keep in mind is that often the UI of your application can be a function of its state, i. e., given the same state, it always renders the same UI. If a component has to hold state, deal with events, etc, it should probably be a class component. If a component's purpose is to simply encapsulate some markup so it can be reused across your app, it can probably be a functional component (especially if you're using a component library and thus don't need to style it).

caution

Stencil does not support re-exporting a functional component from a "barrel file" and dynamically rendering it in another component. This is a known limitation within Stencil. Instead, either use class components and remove the import or import the functional component directly.