Skip to main content

The useKlint Pattern

The useKlint hook connects a React sketch to Klint's input and resource hooks. A plain <Klint draw={draw} /> works without it, but interactive React sketches will usually use this pattern.

Basic Pattern

Every Klint sketch follows this pattern:

import { useKlint, Klint } from '@shopify/klint';

function MySketch() {
const { context } = useKlint();

const draw = (K) => {
// Your drawing code here
};

return (
<div style={{ width: '100vw', height: '100vh' }}>
<Klint context={context} draw={draw} />
</div>
);
}

Klint fills its container — always wrap it in a div with explicit dimensions.

What useKlint Provides

The useKlint hook returns several utilities:

const {
context, // Stable bridge passed to the Klint component
KlintMouse, // Mouse tracking hook
KlintKeyboard, // Keyboard tracking hook
KlintImage, // Image loading hook
KlintWindow, // Window events hook
KlintScroll, // Scroll handling hook
KlintGesture, // Touch gesture hook
useDev, // Opt into editor/HMR drawing refreshes
togglePlay, // Play/pause control
} = useKlint();

Each of these is documented in detail in Klint Hooks.

The Context Object

context is a stable bridge. Pass it to <Klint> whenever you use one of the hook factories:

const { context } = useKlint();
return <Klint context={context} draw={draw} />;

This pattern ensures:

  • Canvas is properly initialized
  • Drawing functions have access to the enhanced context
  • Performance optimizations are applied
  • No unnecessary React re-renders

Mouse Tracking

Use KlintMouse to track mouse position and state:

function MouseSketch() {
const { context, KlintMouse } = useKlint();
const { mouse } = KlintMouse();

const draw = (K) => {
K.background('#000');
K.fillColor(mouse.isPressed ? 'red' : 'white');
K.circle(mouse.x, mouse.y, 30);
};

return (
<div style={{ width: '100vw', height: '100vh' }}>
<Klint context={context} draw={draw} />
</div>
);
}

The mouse object provides x, y, px, py (previous position), vx, vy (velocity), isPressed, isHover, and angle. See Mouse and pointer input for the full API.

State Without Re-renders

The useStorage hook provides state that doesn't trigger React re-renders. It uses get(key) and set(key, value) methods:

import { useKlint, Klint, useStorage } from '@shopify/klint';

function ParticleSketch() {
const { context } = useKlint();

const storage = useStorage({
particles: [],
count: 0
});

const setup = (K) => {
const pts = Array(100).fill(null).map(() => ({
x: Math.random() * K.width,
y: Math.random() * K.height,
vx: Math.random() * 2 - 1,
vy: Math.random() * 2 - 1
}));
storage.set('particles', pts);
};

const draw = (K) => {
K.background('#000');
const particles = storage.get('particles');

for (const p of particles) {
p.x += p.vx;
p.y += p.vy;
K.fillColor('white');
K.circle(p.x, p.y, 2);
}

storage.set('count', storage.get('count') + 1);
};

return (
<div style={{ width: '100vw', height: '100vh' }}>
<Klint context={context} setup={setup} draw={draw} />
</div>
);
}

Key Takeaways

  1. Use useKlint() for React integrations — Basic drawing does not require it
  2. Pass context to <Klint> when using hook factories — This connects listeners and resources to the current canvas
  3. Wrap in a sized container — Klint fills its parent element
  4. Use provided hooks — KlintMouse, KlintKeyboard, etc. for interactivity
  5. Prefer useStorage — For state that changes frequently without needing React re-renders
  6. Functions run outside React — Your draw/setup functions execute in a separate animation loop

Next Steps