Components are the building blocks of Preact applications. They let you split your UI into independent, reusable pieces.
Component types
Preact supports two types of components:
Functional components
Class components
Functional components are simple JavaScript functions that accept props and return JSX. function Greeting ({ name }) {
return < h1 > Hello, { name } ! </ h1 > ;
}
This is the recommended approach for most components. They are simpler, easier to test, and work seamlessly with hooks. Class components extend from Preact’s Component base class and have additional features like lifecycle methods and local state. import { Component } from 'preact' ;
class Greeting extends Component {
render () {
return < h1 > Hello, { this . props . name } ! </ h1 > ;
}
}
The Component class
Preact exports a BaseComponent class (aliased as Component) that provides core functionality for class-based components. Here’s how it’s defined in src/component.js:19:
export function BaseComponent ( props , context ) {
this . props = props ;
this . context = context ;
this . _bits = 0 ;
}
The Component class provides two key methods:
setState()
Updates component state and schedules a re-render:
BaseComponent . prototype . setState = function ( update , callback ) {
let s ;
if ( this . _nextState != NULL && this . _nextState != this . state ) {
s = this . _nextState ;
} else {
s = this . _nextState = assign ({}, this . state );
}
if ( typeof update == 'function' ) {
update = update ( assign ({}, s ), this . props );
}
if ( update ) {
assign ( s , update );
} else {
return ;
}
if ( this . _vnode ) {
if ( callback ) {
this . _stateCallbacks . push ( callback );
}
enqueueRender ( this );
}
};
forceUpdate()
Immediately triggers a re-render, bypassing shouldComponentUpdate():
BaseComponent . prototype . forceUpdate = function ( callback ) {
if ( this . _vnode ) {
this . _bits |= COMPONENT_FORCE ;
if ( callback ) this . _renderCallbacks . push ( callback );
enqueueRender ( this );
}
};
Props
Props (short for “properties”) are arguments passed to components. They are read-only and flow down from parent to child.
function Welcome ({ name , age }) {
return (
< div >
< p > Name: { name } </ p >
< p > Age: { age } </ p >
</ div >
);
}
// Usage
< Welcome name = "Alice" age = { 30 } />
Props in class components
In class components, props are available via this.props:
import { Component } from 'preact' ;
class Welcome extends Component {
render () {
return (
< div >
< p > Name: { this . props . name } </ p >
< p > Age: { this . props . age } </ p >
</ div >
);
}
}
State
State is private data managed within a component. When state changes, the component re-renders.
State in class components
Here’s a real example from Preact’s demo code (demo/todo.jsx:5):
import { Component } from 'preact' ;
class TodoList extends Component {
state = { todos: [], text: '' };
setText = e => {
this . setState ({ text: e . target . value });
};
addTodo = () => {
let { todos , text } = this . state ;
todos = todos . concat ({ text , id: ++ counter });
this . setState ({ todos , text: '' });
};
render ({}, { todos , text }) {
return (
< form onSubmit = { this . addTodo } action = "javascript:" >
< input value = { text } onInput = { this . setText } />
< button type = "submit" > Add </ button >
< ul >
{ todos . map ( todo => (
< li key = { todo . id } > { todo . text } </ li >
)) }
</ ul >
</ form >
);
}
}
The render() method can destructure both props and state from its arguments for cleaner code.
Updating state
You can pass an object or a function to setState():
Object update
Function update
this . setState ({ count: 5 });
Never modify state directly. Always use setState() to ensure the component re-renders correctly.
Functional vs class components
Here’s the same component implemented both ways:
Functional (with hooks)
Class-based
import { useState } from 'preact/hooks' ;
function Counter () {
const [ count , setCount ] = useState ( 0 );
return (
< div >
< p > Count: { count } </ p >
< button onClick = { () => setCount ( count + 1 ) } >
Increment
</ button >
</ div >
);
}
import { Component } from 'preact' ;
class Counter extends Component {
state = { count: 0 };
increment = () => {
this . setState ({ count: this . state . count + 1 });
};
render () {
return (
< div >
< p > Count: { this . state . count } </ p >
< button onClick = { this . increment } >
Increment
</ button >
</ div >
);
}
}
Real-world example
Here’s a complete example from Preact’s demo showing both component types working together (demo/context.jsx:5):
import { Component , createContext } from 'preact' ;
const { Provider , Consumer } = createContext ();
class ThemeProvider extends Component {
state = {
value: this . props . value
};
onClick = () => {
this . setState ( prev => ({
value: prev . value === this . props . value
? this . props . next
: this . props . value
}));
};
render () {
return (
< div >
< button onClick = { this . onClick } > Toggle </ button >
< Provider value = { this . state . value } >
{ this . props . children }
</ Provider >
</ div >
);
}
}
class Child extends Component {
shouldComponentUpdate () {
return false ;
}
render () {
return (
<>
< p > (blocked update) </ p >
{ this . props . children }
</>
);
}
}
This example demonstrates prop passing, state management, lifecycle methods, and component composition.
Component render queue
When you call setState() or forceUpdate(), Preact doesn’t immediately re-render the component. Instead, it adds the component to a render queue (src/component.js:185):
let rerenderQueue = [];
export function enqueueRender ( c ) {
if (
( ! ( c . _bits & COMPONENT_DIRTY ) &&
( c . _bits |= COMPONENT_DIRTY ) &&
rerenderQueue . push ( c ) &&
! rerenderCount ++ ) ||
prevDebounce != options . debounceRendering
) {
prevDebounce = options . debounceRendering ;
( prevDebounce || queueMicrotask )( process );
}
}
This batching mechanism ensures efficient rendering by processing multiple updates together.