How to Use Matter.js in SolidJS Safely

Integrating Matter.js into SolidJS requires bridging an imperative, continuous 2D physics engine with a declarative, fine-grained reactive framework. Because Matter.js relies on direct canvas manipulation and a persistent animation loop, running it safely inside SolidJS requires properly scoping the engine within Solid's lifecycle methods, keeping high-frequency physics computations outside the reactive tracking graph, synchronizing state selectively, and strictly tearing down resources to prevent memory leaks.

Mounting the Engine Inside onMount

Matter.js needs access to a rendered DOM node to attach its canvas renderer. In SolidJS, DOM elements are created during initial execution, but references bound via ref are only guaranteed to exist when the component mounts.

Always instantiate the Matter.js Engine, Render, Runner, and physics bodies inside onMount. Never instantiate them in the component's root body, as this can cause server-side rendering (SSR) errors or attempt to bind to undefined DOM elements.

import { onMount, onCleanup } from "solid-js";
import Matter from "matter-js";

export function PhysicsView() {
  let sceneRef;

  onMount(() => {
    const { Engine, Render, Runner, Bodies, Composite } = Matter;

    const engine = Engine.create();
    const render = Render.create({
      element: sceneRef,
      engine: engine,
      options: {
        width: 800,
        height: 600,
        wireframes: false,
      },
    });

    const box = Bodies.rectangle(400, 200, 80, 80);
    const ground = Bodies.rectangle(400, 590, 810, 60, { isStatic: true });

    Composite.add(engine.world, [box, ground]);

    Render.run(render);
    const runner = Runner.create();
    Runner.run(runner, engine);
  });

  return <div ref={sceneRef} />;
}

Preventing Memory Leaks with onCleanup

Matter.js runs an active requestAnimationFrame loop via its Runner and Render modules. If a SolidJS component unmounts without explicitly halting these processes, the loop continues in the background, creating phantom calculations and memory leaks.

Register a cleanup handler using onCleanup inside onMount (or directly within the component scope) to tear down the world, stop the engine, and clear the canvas:

onCleanup(() => {
  Matter.Render.stop(render);
  Matter.Runner.stop(runner);
  Matter.Composite.clear(engine.world, false);
  Matter.Engine.clear(engine);
  render.canvas.remove();
  render.textures = {};
});

Isolating Physics State from Reactive Tracking

SolidJS tracks signal reads inside reactive contexts (createEffect, JSX templates, and memos). Matter.js mutates body positions, velocities, and angles approximately 60 times per second.

To maintain high frame rates:

Synchronizing SolidJS Signals with Matter.js

When you need reactive state (such as a UI slider controlling gravity or a button spawning an entity) to affect the physics world, drive updates into Matter.js using createEffect.

Directing State from SolidJS to Matter.js

Use fine-grained effects to mutate the physics world directly without restarting the engine:

import { createSignal, createEffect } from "solid-js";

// Inside the component
const [gravityY, setGravityY] = createSignal(1);

// Inside onMount after engine initialization
createEffect(() => {
  engine.gravity.y = gravityY();
});

Directing State from Matter.js to SolidJS

If the standard Solid DOM needs to mirror physics positions (for example, positioning an HTML overlay over a physics body), read coordinates using Matter's afterUpdate event and update a signal. To prevent overwhelming Solid's scheduler, update signals only for necessary UI elements rather than every simulated entity:

Matter.Events.on(engine, "afterUpdate", () => {
  const position = box.position;
  // Update state only when relevant to the UI layer
  setBoxCoords({ x: position.x, y: position.y });
});

By keeping the simulation loop isolated within lifecycle primitives and passing data across the framework boundary imperatively, Matter.js and SolidJS run concurrently at native performance without reactive conflicts.