name: create-mixins description: Create @remix-run/component mixins using createMixin with lifecycle-first semantics. Use when adding new mixins with correct runtime behavior and type flow.
Creating Mixins (@remix-run/component)
Use this skill when authoring new mixins in packages/component.
The key principle: model the real runtime contract first, then write the smallest code that matches it.
Core Runtime Semantics
Treat these as constraints, not suggestions:
- A mixin handle is tied to one mounted host node lifecycle.
-
insertis the host-node availability point for imperative setup. -
removeis teardown for that same lifecycle. -
queueTaskruns post-commit and receives(node, signal)for mixins. - Mixin render functions should stay pure; side effects belong in
insert,remove, or queued work.
createMixin<NodeType>((handle) => {
// Setup runs once per handle lifecycle.
handle.addEventListener('insert', (event) => {
// event.node is the mounted host node for this lifecycle.
// Attach imperative effects here.
})
handle.addEventListener('remove', () => {
// Teardown for the same lifecycle.
// Remove listeners, abort work, release resources.
})
return (props) => {
// Render stays pure: derive props/JSX only.
// Post-commit work goes in queueTask when needed.
handle.queueTask((node) => {
// Runs after commit with the concrete host node.
})
return <handle.element {...props} />
}
})
If your implementation assumes semantics that do not exist (node swapping, repeated insert for the same handle, etc.), remove that logic.
Authoring Rules
- Start with lifecycle truth:
- Use
insertfor attach/setup. - Use
removefor detach/cleanup.
- Use
- Keep state minimal and intentional:
- Do not keep mutable state "just in case" if runtime guarantees make it unnecessary.
- Be precise with defensive checks:
- Use
invariant(...)when a condition is guaranteed by runtime and violation means framework bug. - Use soft guards only when nullability is genuinely part of valid runtime flow.
- Use
- Use
queueTask((node, signal) => ...)for post-commit DOM work.- In most mixins, only
nodeis needed. - Reach for
signalonly when work is async or cancellation-sensitive.
- In most mixins, only
- Do not add
signal.abortedchecks for purely synchronous work. - Favor function expression for helpers in scope.
- Avoid speculative runtime assumptions.
Preferred Patterns
1) Pure prop transform mixin
let withTitle = createMixin((handle) => (title: string, props: { title?: string }) => (
<handle.element {...props} title={title} />
))
2) Lifecycle-managed imperative listener
let withFocus = createMixin<HTMLElement>((handle) => {
handle.addEventListener('insert', (event) => {
event.node.focus()
})
return (props) => <handle.element {...props} />
})
3) Post-commit rebind with node provided by queueTask
handle.queueTask((node) => {
node.removeEventListener(prevType, stableHandler, prevCapture)
node.addEventListener(nextType, stableHandler, nextCapture)
})
Anti-Patterns
- Adding state for hypothetical runtime scenarios.
- Broad defensive null checks where runtime guarantees presence.
- Mixing setup/cleanup side effects into render-only code paths.
- Using
signal.abortedin synchronous non-racy code as boilerplate. - Hiding semantic uncertainty with casts instead of fixing types/contracts.
Mixin Creation Checklist
- Runtime assumptions are stated and match reconciler behavior.
- Lifecycle wiring uses
insert/removedirectly. - State is minimal; no "might change later" scaffolding.
-
queueTaskused only where post-commit timing is required. - Type flow from
createMixin<ThisType>is preserved. - Tests cover ordering, teardown, and type inference contracts.
chat Comments (0)
Sign in to join the discussion and leave a comment.
Skill Details
GitHub Stars
32.5k
GitHub Forks
2.7k
Created
Mar 2026
Last Updated
2 months ago
development
development full stack
Related Skills
Build your own?
Join 12,000+ developers contributing to the Claude ecosystem.
No comments yet. Be the first to share your thoughts!