Preact JS - Documentation

What is Preact?

Preact is a fast 3kB alternative to React with the same ES6 API. It’s a lightweight library that allows you to build user interfaces with a component-based architecture, similar to React. Preact’s core strength lies in its incredibly small bundle size, making it ideal for projects where performance and download speed are paramount, such as mobile apps or websites with limited bandwidth. Despite its small size, Preact offers a nearly identical API to React, meaning that most React code will work seamlessly with Preact with minimal, if any, changes.

Why use Preact?

Several compelling reasons exist for choosing Preact:

Preact vs. React

Feature Preact React
Size ~3kB Significantly larger (~50kB+)
API Compatibility Nearly identical N/A
Performance Generally faster due to smaller size Good, but larger size impacts performance
Ecosystem Smaller than React’s Vast and extensive ecosystem
Learning Curve Easy for React developers Steeper learning curve initially

Setting up a Preact Project

The easiest way to set up a Preact project is using the preact-cli:

  1. Install Node.js and npm (or yarn): Ensure you have Node.js and npm (Node Package Manager) installed on your system. Yarn is also a viable package manager alternative.

  2. Create a new project: Open your terminal and run: npx create-preact-app my-preact-app (Replace my-preact-app with your desired project name).

  3. Navigate to the project directory: cd my-preact-app

  4. Start the development server: npm start (or yarn start). This will start a local development server and open the application in your browser.

Hello World Example

Here’s a simple “Hello, World!” example using Preact:

import { h, render } from 'preact';

function App() {
  return (
    <div>
      <h1>Hello, World!</h1>
    </div>
  );
}

render(<App />, document.getElementById('root'));

This code imports the necessary functions from preact, defines a simple functional component App, and renders it into the DOM element with the ID “root”. Remember to include a <div id="root"></div> in your HTML file. This example demonstrates the fundamental structure of a Preact application.

Core Concepts

JSX

JSX (JavaScript XML) is a syntax extension to JavaScript. It allows you to write HTML-like code within your JavaScript files. In Preact, JSX is used to describe the user interface. It’s not directly understood by JavaScript engines; instead, it’s transformed into regular JavaScript functions during the build process using a tool like Babel. This transformation makes it easier to build and manage complex UI structures in a more readable and maintainable way. JSX elements are often referred to as “virtual nodes” because they’re representations of actual DOM nodes before they’re rendered.

Virtual DOM

Preact utilizes a Virtual DOM (VDOM) for efficient updates. The VDOM is a lightweight in-memory representation of the actual DOM. When changes occur in your application (e.g., updating component state), Preact updates the VDOM first. It then compares the updated VDOM with the previous VDOM using a highly optimized diffing algorithm. Only the necessary changes are applied to the real DOM, minimizing direct manipulations and improving performance significantly. This process avoids expensive DOM manipulations, making updates much faster.

Components

Components are reusable building blocks of Preact applications. They encapsulate UI elements and their behavior, promoting modularity and maintainability. Preact supports two primary types of components: functional components and class components.

State Management

State refers to internal data within a component that can change over time, causing the UI to re-render. In Preact, state is typically managed using the useState hook (for functional components) or the this.state object (for class components). When the state changes, Preact’s efficient diffing mechanism ensures only the necessary parts of the UI are updated, preventing unnecessary re-renders. For more complex state management needs, consider using dedicated libraries such as Zustand, Jotai, or Redux (though usually overkill for simpler Preact apps).

Props

Props are immutable input data passed to a component from its parent component. They are used to customize the behavior and appearance of components. Props are read-only within the child component – you cannot modify them directly. Props are passed as attributes in the JSX.

Lifecycle Methods

Lifecycle methods are functions that are called at specific points in a component’s existence. These methods allow you to perform actions at different stages, such as component mounting, updating, or unmounting. Class components offer several lifecycle methods (e.g., componentDidMount, componentDidUpdate, componentWillUnmount), whereas functional components primarily leverage hooks to achieve similar functionality.

Events

Preact handles events similarly to React. Events are handled using JavaScript event handlers attached to JSX elements. Event handlers are functions that are executed when a specific event occurs on an element (e.g., a click, mouseover, or keypress). The event object provides details about the event that occurred. Event handlers are written within JSX using the on prefix (e.g., onClick, onMouseOver, onChange).

Working with Components

Creating Components

Preact components are the fundamental building blocks of your application’s UI. They encapsulate logic and rendering, promoting reusability and maintainability. The simplest way to create a component is by defining a function that returns JSX. More complex components can leverage class components for managing internal state and lifecycle methods. Regardless of the approach, components follow a consistent pattern: they receive data via props and render JSX based on that data and their internal state.

Functional Components

Functional components are the most concise way to create components in Preact. They are plain JavaScript functions that take props as an argument and return JSX. They are stateless by default, meaning they don’t manage internal state. However, using the useState hook, they can be made stateful. Functional components are often preferred for their simplicity and readability.

function MyComponent(props) {
  return (
    <div>
      <h1>{props.title}</h1>
      <p>{props.content}</p>
    </div>
  );
}

Class Components

Class components offer more capabilities, especially for managing internal state and lifecycle methods. They are defined as JavaScript classes that extend React.Component (or, in Preact, it’s usually just Component). They have access to this.state for managing state and lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. While functional components are often preferred for simplicity, class components remain useful for handling more complex scenarios needing explicit lifecycle control.

import { Component } from 'preact';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>Increment</button>
      </div>
    );
  }
}

Component Composition

Component composition is a powerful technique where you combine smaller, simpler components to create more complex ones. This promotes reusability and improves code organization. Instead of creating a monolithic component, break down the UI into smaller, manageable pieces and nest them together. This leads to better maintainability and understanding.

Higher-Order Components (HOCs)

Higher-Order Components (HOCs) are advanced techniques for reusing component logic. An HOC is a function that takes a component as an argument and returns a new enhanced component. HOCs are useful for adding functionality to components without modifying their core logic, such as adding logging, authentication, or data fetching capabilities.

Render Props

Render props are a technique for sharing code between React components using a prop whose value is a function. A component using the render props pattern accepts a render prop, which is a function that receives data and returns JSX. This approach allows the component using the render prop to customize its output based on the data provided.

Forwarding Refs

Refs provide a way to access DOM elements or component instances directly. However, they don’t work seamlessly across component boundaries. Forwarding refs allows you to pass a ref from a parent component to a child component, enabling access to the child component’s underlying DOM element or instance from the parent. This is essential for scenarios where you need to directly interact with a child component’s DOM (for example, focusing an input). Preact provides forwardRef to handle this.

Advanced Concepts

Context API

Preact’s Context API provides a way to pass data through the component tree without having to pass props down manually at every level. This is particularly useful for sharing global state or configuration data that many components need to access. A Context object is created using createContext, and values are provided using the Provider component. Components can then subscribe to the context using the useContext hook (in functional components) or by consuming the context directly (in class components). This avoids “prop drilling,” making your code cleaner and more maintainable.

Hooks

Hooks are functions that let you “hook into” React state and lifecycle features from within functional components. Preact supports most of React’s hooks, including useState, useEffect, useContext, useReducer, useCallback, useMemo, and useRef. Hooks allow functional components to manage state and side effects, making them a powerful alternative to class components in many scenarios. They promote cleaner and more readable code, particularly for managing complex logic within functional components.

Suspense

Suspense allows you to declaratively handle loading states in your application. It is primarily used with asynchronous operations like data fetching or image loading. The <Suspense> component renders a fallback UI while the asynchronous operation is in progress, providing a smoother user experience. This prevents the UI from appearing blank or broken while waiting for data to load. Preact’s support for Suspense might differ slightly from React’s depending on the version. Check the Preact documentation for the most up-to-date details.

Portals

Portals allow you to render a child component’s output into a different part of the DOM tree than where the parent component is rendered. This is useful for creating modals, tooltips, or other UI elements that need to be placed outside the usual component hierarchy. Preact’s createPortal function facilitates this. It takes the child component’s JSX and the target DOM element as arguments. This enables more flexibility in UI placement.

Error Boundaries

Error boundaries are components that catch JavaScript errors in their child component tree and display a fallback UI instead of crashing the entire application. This helps to gracefully handle errors and prevent unexpected application crashes. Preact allows you to define a component as an error boundary by implementing the componentDidCatch lifecycle method. This method takes the error and information about the component where the error occurred as arguments. The fallback UI is then rendered in place of the component that threw the error.

Fragments

Fragments allow you to group multiple elements together without adding extra nodes to the DOM. They’re useful when you need to return multiple elements from a component but don’t want an extra unnecessary <div> or other container element in the rendered output. In JSX, you can represent a fragment using empty tags <> </> or <React.Fragment></React.Fragment>.

Memoization

Memoization is an optimization technique that caches the result of an expensive function call and returns the cached result when the same inputs are provided again. In Preact, useMemo is a hook that provides memoization capabilities. It takes a function and an array of dependencies as arguments. The function is only re-executed when one of the dependencies changes, preventing unnecessary calculations and enhancing performance. This is especially beneficial for expensive calculations or rendering logic that doesn’t need to re-run on every render.

Data Fetching and Handling

Fetching Data with Fetch API

The fetch API is a common way to retrieve data from external sources in Preact applications. It’s a simple and powerful method for making HTTP requests. fetch returns a promise that resolves with the response from the server. You can then access the response data using methods like .json() to parse JSON responses. Because fetch returns a promise, you’ll typically use .then() to handle the response and .catch() to handle any errors.

useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await fetch('/api/data');
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      setError(error);
    }
  };

  fetchData();
}, []);

Asynchronous Operations

Data fetching is inherently asynchronous. This means it takes time and doesn’t block the execution of other code. In Preact, asynchronous operations are often handled using promises and async/await. Promises provide a way to represent the eventual result of an asynchronous operation, and async/await makes asynchronous code easier to read and write. The useEffect hook is frequently used to manage asynchronous operations, ensuring they run after the component mounts and cleanup any resources when the component unmounts.

Data Loading States

When fetching data, it’s important to manage the loading state to provide feedback to the user. This usually involves using state variables to track whether data is loading, has loaded successfully, or has failed to load. A loading indicator (e.g., a spinner) can be displayed while data is being fetched, and error messages can be shown if there’s a problem.

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  // ... fetch data ...
  setLoading(false);
}, []);

if (loading) {
  return <p>Loading...</p>;
}

if (error) {
  return <p>Error: {error.message}</p>;
}

return (
  // ... render data ...
);

Error Handling

Robust error handling is essential for a reliable application. When fetching data, errors can occur due to network problems, server errors, or incorrect data formats. It’s good practice to use try...catch blocks to handle potential errors during the fetching process. Displaying informative error messages to the user can significantly improve the user experience. Consider logging errors for debugging purposes.

Data Caching

Caching data can improve performance by avoiding repeated fetches of the same data. Preact doesn’t have built-in caching mechanisms; however, you can implement caching using libraries or by managing a local cache within your application’s state. Techniques include storing data in browser storage (localStorage or sessionStorage) or using a dedicated caching library. Consider the data’s expiration time and update frequency when implementing caching. You might also want to check for stale data or implement update strategies.

Routing and Navigation

Introduction to Preact Router

Preact Router is a popular routing library for Preact applications. It provides a declarative way to define routes and handle navigation within your application. It allows you to map URLs to components, making it easy to build single-page applications (SPAs) with multiple views. Preact Router is lightweight and efficient, aligning well with Preact’s performance-focused philosophy. You’ll typically install it via npm or yarn (npm install preact-router).

Basic Routing

Basic routing involves mapping URLs to components. You define routes using the <Route> component, specifying the path and the component to render for that path. The <Router> component wraps your routes, acting as the central point for routing within your application.

import { Router, Route } from 'preact-router';
import Home from './Home';
import About from './About';

const App = () => (
  <Router>
    <Route path="/" component={Home} />
    <Route path="/about" component={About} />
  </Router>
);

Nested Routing

Nested routing allows you to create a hierarchy of routes. You can nest <Router> components within other routes to create more complex routing structures. This is useful for organizing larger applications and creating more manageable route definitions.

import { Router, Route } from 'preact-router';
import Home from './Home';
import About from './About';
import Products from './Products';
import ProductDetails from './ProductDetails';

const App = () => (
  <Router>
    <Route path="/" component={Home} />
    <Route path="/about" component={About} />
    <Route path="/products">
      <Route path="/" component={Products} />
      <Route path="/:productId" component={ProductDetails} />
    </Route>
  </Router>
);

Route Parameters

Route parameters allow you to pass dynamic values into your routes. These parameters are accessed within the component using the params property. Parameters are defined in the path using colons (:paramName).

import { Router, Route } from 'preact-router';
import ProductDetails from './ProductDetails';

const App = () => (
  <Router>
    <Route path="/products/:productId" component={ProductDetails} />
  </Router>
);

// In ProductDetails component:
function ProductDetails({ params }) {
  const productId = params.productId;
  // ... use productId ...
}

Programmatic Navigation

Programmatic navigation allows you to change the route programmatically, typically triggered by user interactions or other events. You use the navigate function provided by the useRouter hook.

import { useRouter } from 'preact-router';

const MyComponent = () => {
  const router = useRouter();

  const handleClick = () => {
    router.navigate('/about');
  };

  return <button onClick={handleClick}>Go to About</button>;
};

Preact Router allows you to create custom link components, providing greater control over the styling and behavior of links within your application. This customization allows for seamless integration with your design system and enables the addition of features like active state indicators. You typically create a custom component that uses the <Link> component internally, adding your styling and additional properties as needed.

Testing

Unit Testing

Unit testing involves testing individual components or functions in isolation. The goal is to verify that each unit of code works correctly on its own. In Preact, you can use various testing libraries to write unit tests. These tests typically focus on verifying the output of a component for given inputs, checking for state updates, or testing the functionality of individual helper functions. Mocking dependencies is often necessary to isolate the unit under test.

import { render, screen } from '@testing-library/preact';
import MyComponent from './MyComponent';

test('renders a heading', () => {
  render(<MyComponent title="Test Title" />);
  const headingElement = screen.getByRole('heading', { name: /test title/i });
  expect(headingElement).toBeInTheDocument();
});

Integration Testing

Integration testing verifies how different components interact with each other. It focuses on testing the flow of data and functionality between multiple components, ensuring they work together correctly. Integration tests are more complex than unit tests as they involve setting up and coordinating multiple components. You might use tools similar to those employed in unit testing but with a focus on the interaction between components rather than isolated unit functionality.

End-to-End Testing

End-to-end (E2E) testing simulates a user’s interaction with the application. It tests the entire application flow, from user input to final output. E2E tests are typically run in a browser environment and aim to verify the application’s functionality as a whole. They are valuable for ensuring that the application works correctly as a complete system. Popular E2E testing frameworks like Cypress or Playwright can be integrated with Preact applications. These frameworks allow you to simulate user actions such as clicks, typing, and navigation to validate the overall functionality of the application.

Testing Libraries and Frameworks

Several libraries and frameworks are suitable for testing Preact applications:

The choice of testing library and framework will depend on your project’s requirements and your preferred testing style. @testing-library/preact is generally recommended for its focus on user-centric testing and its emphasis on testing components from the perspective of a real user. Jest is a powerful and flexible testing framework that can be used in conjunction with various testing libraries.

Optimization and Performance

Improving Render Performance

Preact’s virtual DOM already provides significant performance benefits. However, further optimization can be achieved by focusing on minimizing unnecessary re-renders. Techniques include:

Minimizing DOM Updates

Direct DOM manipulation is expensive. Preact’s virtual DOM minimizes this, but you can further optimize by:

Code Splitting

Code splitting involves dividing your application’s code into smaller chunks that are loaded on demand. This improves initial load times by loading only the necessary code for the initial view. Tools like Webpack or Rollup can be configured for code splitting. This allows users to interact with parts of the app immediately, rather than waiting for the entire bundle to download.

Lazy Loading

Lazy loading is a technique to load components or other resources only when they are needed. This prevents unnecessary downloads and improves the initial load time of your application. Preact’s built-in Suspense component can be effectively used for lazy-loading components, particularly useful for fetching data before rendering a component. This helps to avoid rendering a blank screen while waiting for data and enhances user experience.

Performance Measurement and Profiling

Measuring and profiling your application’s performance is crucial for identifying performance bottlenecks. Tools like:

By using these tools and techniques, you can pinpoint the performance bottlenecks and effectively optimize your Preact application for speed and responsiveness. Remember that optimization should be a continuous process; measure performance regularly and prioritize optimizations based on actual impact.

Deployment

Building for Production

Before deploying your Preact application, you need to build it for production. This process optimizes your code for size and performance, making it suitable for deployment to a production environment. The preact-cli simplifies this process. Typically, you would run a command like npm run build (or yarn build), which will create an optimized build in a directory like build. This build contains minimized and bundled JavaScript files ready for deployment. The specific command might depend on your project setup and build tools.

Deployment to Static Hosting

Many hosting providers support static website hosting. This is a straightforward way to deploy Preact applications because the optimized build is essentially a set of static files (HTML, CSS, JavaScript). Popular static hosting providers include:

Deployment usually involves uploading the contents of your build directory to your chosen hosting provider. Most services provide clear instructions and integrations to streamline this process.

Deployment to Serverless Functions

Serverless functions are a good choice for handling backend logic in your Preact application. This architecture allows you to scale your backend automatically based on demand and only pay for the resources consumed. Popular serverless platforms include:

With serverless functions, you would typically deploy your backend code (API endpoints, data processing logic, etc.) as individual functions. Your Preact frontend would then make API calls to these functions to interact with your backend. The exact implementation depends on the chosen serverless platform and its integrations with Preact. You will need to configure your serverless provider to handle API requests and integrate with your Preact frontend. Remember to handle authentication and security appropriately when deploying to serverless functions.

Preact Ecosystem

Community and Resources

Preact boasts a vibrant and helpful community. Numerous resources are available to assist developers:

Preact Plugins and Add-ons

While Preact itself is minimal, its ecosystem includes several plugins and add-ons that extend its capabilities. These tools can enhance functionality and address specific development needs. Examples might include:

It’s important to check the Preact community resources for up-to-date information on available plugins and add-ons. Choose those that best match your application’s requirements and always verify their compatibility with your Preact version.

Third-party Libraries

Many third-party libraries are compatible with Preact and seamlessly integrate into your projects. Because Preact uses the same API as React, many React libraries work well with Preact, often requiring minimal or no changes. When choosing third-party libraries, ensure they’re compatible with Preact and align with your project’s needs and performance requirements. Consider the library’s size, active maintenance, and community support.

Contributing to Preact

Preact’s open-source nature allows community contributions. If you have the skills and desire to contribute, consider:

Before contributing, familiarize yourself with the project’s guidelines and coding style to ensure your contribution is accepted smoothly. The contribution guidelines will help you understand the process, and adhering to the coding style will ensure consistency within the project.

Appendix

Glossary of Terms

Common Errors and Troubleshooting

If you encounter an error not listed here, consult the official Preact documentation, search the community resources, or provide details about the error message and context on a relevant forum for assistance. Thorough error messages, code snippets, and descriptions of the steps you have already tried are essential for effective troubleshooting.