Skip to main content
Dev Note

StencilJS Cheatsheet

Dev Notes Disclaimer: Each artile in the Dev Notes section of my webiste may or may not be unfinished work. I don't always have time to write a full post. If you see something that looks like a half-baked idea, it probably is! If you have any questions or suggestions, feel free to reach out.

This guide walks through JavaScript concepts that are essential to understanding and writing effective Stencil.js Web Components, with examples and usage in context.

Classes, Class Fields & Decorators

JavaScript Concept:

  • class syntax introduces blueprint-based object creation in ES6.
  • Fields declared directly inside the class (not in the constructor) are called class fields.
  • In TypeScript, you can use decorators to annotate class members with metadata or functionality.

Stencil-Specific:

Stencil uses decorators provided by @stencil/core to add Web Component behavior.

Common decorators:

import { Component, Prop, State, Event, h } from '@stencil/core';

@Component({
  tag: 'my-card',
  styleUrl: 'my-card.css',
  shadow: true,
})
export class MyCard {
  @Prop() title: string;
  @State() internalState = 'loading';
  @Event() clicked: EventEmitter<void>;

  private logTitle() {
    console.log(this.title);
  }
}

Decorators are not native JavaScript yet. They're enabled via the TypeScript compiler and Stencil's tooling.

Modules: import / export

JavaScript Concept:

  • ES Modules (import/export) allow you to split and reuse logic across files.
  • default and named exports provide flexible import patterns.

Stencil-Specific:

Stencil relies heavily on module imports to bring in decorators, JSX helpers, and utility functions.

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

Arrow Functions & this Context

JavaScript Concept:

Arrow functions do not bind their own this. They inherit from the enclosing scope. This avoids this binding bugs.

Stencil-Specific:

Use arrow functions for event handlers or callbacks to preserve component context.

handleClick = () => {
  console.log(this.title); // works without .bind(this)
}

Destructuring

JavaScript Concept:

Pull out values from arrays or objects directly into variables.

Stencil-Specific:

Useful in render() to simplify code when referencing @Prop() or @State() values.

render() {
  const { title, subtitle } = this;
  return <div>{title} - {subtitle}</div>;
}

Rest & Spread Syntax

JavaScript Concept:

... syntax is used to gather (rest) or distribute (spread) values.

Stencil-Specific:

Helps with immutability, prop merging, or passing down attributes.

const newUser = { ...this.user, isActive: true };

JSX + Conditional Logic

JavaScript Concept:

JSX is syntactic sugar for DOM construction, powered by functions.

Stencil-Specific:

Stencil uses JSX for rendering templates in render(). You can use logic like &&, ternaries, or .map() inside templates.

render() {
  return (
    <div>
      {this.items.length > 0 && this.items.map(item => <p>{item}</p>)}
      {this.error ? <span>Error!</span> : null}
    </div>
  );
}

Async / Await

JavaScript Concept:

async/await simplifies handling of asynchronous operations (e.g. API calls).

Stencil-Specific:

Use in lifecycle methods like componentWillLoad() to fetch data before initial render.

async componentWillLoad() {
  this.data = await fetchUserData();
}

Custom Events

JavaScript Concept:

CustomEvent lets you create custom DOM events that bubble up and carry data.

Stencil-Specific:

Stencil uses @Event() and EventEmitter to create custom events in a declarative way.

@Event() userClicked: EventEmitter<string>;

handleClick() {
  this.userClicked.emit('Josh');
}

Lifecycle Hooks

componentDidLoad() {
  this.trackAnalyticsView();
}

Watchers

JavaScript Concept:

Reacts to changes in values or props.

Stencil-Specific:

Use @Watch() to run logic when a @Prop or @State changes.

@Prop() count: number;

@Watch('count')
handleCountChange(newVal: number, oldVal: number) {
  console.log(`Count changed from ${oldVal} to ${newVal}`);
}

Array Methods: .map(), .filter()

JavaScript Concept:

  • .map() transforms arrays.
  • .filter() filters based on condition.

Stencil-Specific:

Use to loop over and render lists dynamically inside JSX.

<ul>
  {this.items.map(item => (
    <li>{item.name}</li>
  ))}
</ul>

Truthy/Falsy & Ternary Operators

JavaScript Concept:

Evaluate conditions inline using logical short-circuiting or ternaries.

Stencil-Specific:

Common in render() logic.

{this.isLoading ? <p>Loading...</p> : <p>Content ready!</p>}
{this.hasError && <p>Something went wrong.</p>}

Closures, Factory Methods & Dynamic Handlers

JavaScript Concept:

  • Closures are functions that "remember" variables from the scope where they were created.
  • Factory methods return new functions or objects, often customized for specific use.
  • Dynamic handlers are functions generated on-the-fly for context-specific behavior.

Stencil-Specific:

Closures and factory functions help create reusable logic for event handlers or conditional behavior.

Closure example:

function logOnClick(message: string) {
  return () => console.log(message);
}

In a Stencil component:

render() {
  return (
    <button onClick={this.getClickHandler('Save clicked')}>Save</button>
  );
}

getClickHandler(msg: string) {
  return () => console.log(msg);
}

This is powerful for rendering lists where each button needs a unique handler.

TypeScript Essentials

Stencil uses TypeScript by default. Know these basics:

  • Primitive types: string, number, boolean, etc.
  • Union types: 'sm' | 'md' | 'lg'
  • Interfaces: Define shape of props or data models.
  • Optional props: @Prop() label?: string
@Prop() variant: 'primary' | 'secondary' = 'primary';

interface User {
  id: number;
  name: string;
}

DOM APIs & Focus Management

Stencil gives you access to native DOM APIs via @Element().

Use cases:

  • Setting focus
  • Managing keyboard interaction
  • Querying child elements
@Element() host: HTMLElement;

focusInput() {
  this.host.querySelector('input')?.focus();
}

Common Gotchas & Fixes