Skip to content

Commands

Send commands from a parent component to a specific child using createCommand():

import { createCommand } from 'sygnal'
const playerCmd = createCommand()
function App({ state }) {
return (
<div>
<button className="play-btn">Play</button>
<button className="seek-btn">Seek</button>
<VideoPlayer commands={playerCmd} />
</div>
)
}
App.intent = ({ DOM }) => ({
PLAY: DOM.select('.play-btn').events('click'),
SEEK: DOM.select('.seek-btn').events('click'),
})
App.model = {
'PLAY | EFFECT': () => playerCmd.send('play'),
'SEEK | EFFECT': () => playerCmd.send('seek', { time: 30 }),
}

The child receives commands through the commands$ source, which is automatically available when a Command object is passed as a prop:

function VideoPlayer({ state }) {
return <video className="player" src={state.src} />
}
VideoPlayer.intent = ({ commands$ }) => ({
PLAY: commands$.select('play'),
SEEK: commands$.select('seek'),
})
VideoPlayer.model = {
PLAY: (state) => ({ ...state, playing: true }),
SEEK: (state, { time }) => ({ ...state, currentTime: time }),
}

createCommand() returns an object the parent uses to send commands:

  • send(type, data?) — Send a named command with optional data

When a Command is passed as any prop to a child component, Sygnal automatically provides a commands$ source in the child’s intent:

  • commands$.select(type) — Returns a stream that emits the data argument each time a command of that type is sent

The prop name doesn’t matter — you can call it commands, cmd, actions, or anything. Sygnal detects the Command object by its internal marker.

The second argument to send() is extracted by select():

// Parent sends
cmd.send('set-color', '#ff0000')
cmd.send('set-size', { width: 100, height: 50 })
// Child receives
Widget.intent = ({ commands$ }) => ({
SET_COLOR: commands$.select('set-color'), // emits '#ff0000'
SET_SIZE: commands$.select('set-size'), // emits { width: 100, height: 50 }
})

If send() is called without a data argument, select() emits undefined. This works fine for signal-style commands like 'play' or 'reset' where you only care that the command fired.

When the parent only needs to forward a command without updating its own state, use the EFFECT sink. This is the most common pattern for commands — the parent is just a pass-through:

App.model = {
'PLAY | EFFECT': () => playerCmd.send('play'),
}

EFFECT runs the function for its side effects only — no state change, no re-render. The model shorthand ('ACTION | EFFECT') keeps it concise. The equivalent longhand form works too:

App.model = {
PLAY: {
EFFECT: () => playerCmd.send('play'),
},
}

See Effect Handlers for more details on the EFFECT sink.

PatternDirectionUse Case
createCommand()Parent → ChildImperative actions: play, pause, reset, scroll-to
PARENT / CHILDChild → ParentData and events flowing upward
EVENTS driverAny → AnyGlobal broadcast (notifications, theme changes)
contextParent → DescendantsShared reactive data (theme, user, locale)

Commands are the right choice when a parent needs to tell a specific child to do something, rather than passing it data to render.