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:
- Do not store Matter.js engine or body instances in
createSignalorcreateStore. Solid's reactivity adds proxy overhead and tracking dependencies that degrade continuous physics calculations. Store these references as standard JavaScript variables. - Do not read reactive signals inside Matter.js update loops
without
untrack(). If an engine callback (likebeforeUpdate) reads a Solid signal, wrap that access inuntrackto prevent unintended effect re-triggers.
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.