StencilJS Component Not Re-rendering After @Prop or @State Change
Diagnose why StencilJS components skip re-renders after @Prop or @State updates, covering reference mutation, mutable props, async batching, and attribute reflection.
29 Mar 2026, 05:16 UTC

The symptom: a property changed, but the component did not update
You assign a new value to a @Prop from a parent component, or reassign a @State value inside the component, and the browser still shows the previous output. StencilJS did not re-render the component.
Useful takeaway: most missing re-renders are caused by in-place mutation of arrays or objects, by treating a mutable prop as two-way bound, or by reading the DOM before Stencil finishes its asynchronous, batched update cycle.
Recognizable conditions
- A parent passes a new array or object to a
@Prop, and the template still renders the old data. - A component calls
items.push()on a@Statearray, but the list does not grow in the UI. - A component edits its own
@Prop({ mutable: true })copy and expects the parent to see the change. - Code reads a DOM element immediately after a property assignment and sees pre-update content.
- Changing a JavaScript property does not update the corresponding HTML attribute.
Cause and diagnostic table
| Observed condition | Likely cause | Quick confirmation |
|---|---|---|
New object or array assigned to @Prop, template unchanged | Reference was not replaced, or the prop is not read in the template | Add console.log inside render() or @Watch; update the prop from the parent |
@State array mutated with push, splice, or object property changed in place | Stencil does not detect in-place mutation; it tracks reference identity | Compare items.push(value) against items = [...items, value] in a test component |
Component changes @Prop({ mutable: true }), parent does not see it | Mutable props create a local copy; changes do not propagate upward | Check whether the parent binds the prop with one-way data flow and listens for a custom event |
| DOM read immediately after assignment shows old output | Property updates are batched and asynchronous | Read the DOM in @Watch or after a microtask instead of synchronously |
| JavaScript property changes but HTML attribute stays the same | reflect is not enabled on the prop | Inspect the rendered element in browser dev tools; check prop decorators and stencil.config.ts |
Ordered checks
- Confirm the prop or state is used in the template. Stencil only re-renders when the property value is read in
render()or watched with@Watch. If the value is only used in a side effect, the component may not re-render. - Check reference identity. For arrays and objects assigned to
@Stateor@Prop, verify that you are replacing the reference, not mutating the existing instance. - Check prop direction. If the component writes to
@Prop({ mutable: true }), confirm whether the parent actually needs to know. If it does, emit a custom event instead. - Check timing. If you read the DOM right after setting state, move the read into
@Watchor await the next render cycle. - Check attribute reflection. If your styling or tests depend on HTML attributes, add
{ reflect: true }to the prop decorator. - Verify the installed Stencil major version. Behavior changed across major versions; read the release notes for your version before applying version-specific flags.
Fixes matched to findings
Fix 1: Replace object or array references instead of mutating in place
Stencil detects reassignment of a @State property, but not in-place changes to the contents of an array or object.
@State() items: string[] = [];
// Does not trigger re-render
this.items.push('new item');
// Triggers re-render
this.items = [...this.items, 'new item'];
Run this check in your component's src/components directory. No special permissions are required. The risk is introducing a new reference every render cycle if you replace the array unconditionally; guard the assignment with a condition so you do not loop.
Fix 2: Use events for mutable prop changes
@Prop({ mutable: true }) lets the component edit its own copy, but the parent never receives the update. If the parent needs the new value, emit an event.
@Prop({ mutable: true }) value: string;
handleChange() {
this.value = 'updated';
this.valueChanged.emit(this.value);
}
Place this in the component class. The parent listens with <my-component onValueChanged={handler}>. Do not expect the parent binding to update automatically.
Fix 3: Read the DOM after the render cycle
Stencil batches property updates. A synchronous DOM read immediately after assignment may return pre-update markup.
@Watch('items') itemsChanged() {
// Safe: runs after the render triggered by the items change
const list = this.el.querySelector('ul');
console.log(list);
}
Avoid reading this.el directly in the same synchronous block where you set @State.
Fix 4: Enable attribute reflection when needed
Setting a JavaScript property does not automatically write the HTML attribute unless the prop is configured to reflect.
@Prop({ reflect: true }) active = false;
Run npm run build from the project root and inspect dist/custom-elements to verify generated element definitions include the reflected attribute. This command requires normal user permissions and write access to the dist directory. A failed build usually indicates TypeScript errors, not a runtime problem.
Escalation criteria
Escalate to a minimal reproduction or Stencil issue tracker if:
- None of the checks above explain the missing re-render.
- Server-rendered HTML and client hydration disagree on initial state.
- The behavior changed after upgrading Stencil and the release notes describe a breaking change in reactivity.
Before escalating, run npm run build and confirm the output in dist matches your source. Include the Stencil major version from package.json in the report.
Limitations and verification
@Watchruns after the render triggered by the watched prop. Side effects in@Watchthat update other state can create loops; guard them with a previous-value comparison.- Boolean props and attribute reflection require explicit configuration; setting the JS property alone is not enough.
- Reactivity behavior differs across Stencil major versions. Verify against your installed version's release notes before relying on version-specific flags.
- This guide does not cover server-side rendering hydration mismatches in detail; ensure deterministic initialization on both server and client.
Practical verification: add console.log inside render() and inside @Watch('propName'), then update the prop from the parent. Confirm the log appears after the render. Separately, compare in-place array mutation against reference replacement in a @State-bound template and observe whether the UI updates.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.