---
title: ComponentActor
description: An actor whose only state is a React function, from @motionactor/primitives. It draws from the frame number.
---

`ComponentActor` holds a render function as a signal. It has a box like any
[`LayoutActor`](/docs/actors/layout-actor), so a scene can spawn it, move it and fade it, and the
DOM backend calls the function on every frame with the frame number and the box size.

```tsx twoslash title="ticker.tsx"
// @jsx: react-jsx
// ---cut---
import { createScene } from "@motionactor/core";
import { ComponentActor } from "@motionactor/primitives";

const Ticker = ComponentActor.withRender(({ frame, fps, width }) => (
  <div style={{ width, fontVariantNumeric: "tabular-nums" }}>
    {(frame / fps).toFixed(2)}s
  </div>
));

createScene(function* (ctx) {
  const ticker = ctx.spawn(Ticker, {
    layout: { x: 40, y: 40, width: 240, height: 60 },
  });
  yield* ticker.appearance.opacity.tween(0.4, 1);
}).build({ viewport: { width: 1920, height: 1080 }, fps: 30 });
```

`withRender` returns a subclass with that function as its default, so each use gets its own
class. Without it, pass the function as the `render` override when spawning.

The function receives `frame`, `fps`, `width` and `height`, plus `durationInFrames` when the
host knows it. It has no access to the actor's signals, because `render` is the only one.

## Swapping the view

`*setRender(render, duration = 0)` replaces the function at the cursor and then waits
`duration` seconds:

```tsx twoslash
// @jsx: react-jsx
// ---cut---
import { createScene } from "@motionactor/core";
import { ComponentActor } from "@motionactor/primitives";

createScene(function* (ctx) {
  const panel = ctx.spawn(ComponentActor, {
    render: () => <p>Loading</p>,
  });
  yield* panel.setRender(() => <p>Ready</p>, 2);
}).build({ viewport: { width: 1920, height: 1080 }, fps: 30 });
```

## Limits

`ComponentActor` sets `static frameDriven = true`, because its function may draw anything
from `frame`. Export therefore renders every one of its frames and never reuses a frame that
looks unchanged. See [Capture and export](/docs/rendering/capture-and-export#export).

Its renderer is DOM only. On a Pixi or Three.js layer it draws nothing.

It suits a one-off surface or a sketch of something that has no actor yet. Once the drawing
depends on values the scene should animate, such as a progress number or a label, those
values belong in signals on an actor of its own, with a [renderer](/docs/rendering/renderers) that
reads them. Otherwise the timeline cannot tween them, and an editor cannot show them.
