remix-run/remix · error · Error
${name} must return a render function, received ${typeof res
Error message
${name} must return a render function, received ${typeof result} What it means
Remix UI components follow a two-phase contract: the component function runs once as setup and must RETURN a render function; that returned function is then called for the initial render and every update. On first render, ComponentRuntime calls the component with `handle` and validates the result with `isRenderFn`; anything else (JSX element, undefined, object) throws.
Source
Thrown at packages/ui/src/runtime/component.ts:313
render = (nextProps: ElementProps): [RemixNode, Array<() => void>] => {
if (this.#removed) {
console.warn('render called after component was removed, potential application memory leak')
return [null, []]
}
this.#abortRenderSignal()
syncProps(this.#props, nextProps)
let renderFn = this.#renderFn
if (renderFn === undefined) {
let initialize = this.#config.type as unknown as (handle: Handle<ElementProps, C>) => unknown
let result = initialize(this.#handle)
if (!isRenderFn(result)) {
let name = this.#config.type.name || 'Anonymous'
throw new Error(`${name} must return a render function, received ${typeof result}`)
}
renderFn = result
this.#renderFn = renderFn
}
return [renderFn(), this.#dequeueTasks()]
}
remove = (): Array<() => void> => {
if (this.#removed) return EMPTY_TASKS
this.#removed = true
this.#connectedController?.abort()
this.#abortRenderSignal()
if (this.#tasks.length === 0) return EMPTY_TASKS
return this.#dequeueTasks((sharedAbortedSignal ??= AbortSignal.abort()))
}
View on GitHub (pinned to 9696913134)
Solutions
- Wrap the JSX in an arrow function: `return () => (<div>…</div>)`
- Make sure every code path in the component body ends with `return () => …` — including early returns in setup logic
- Keep setup-only work (state init, subscriptions) above the returned render closure, not instead of it
Example fix
// before
function Greeting(handle: Handle<{ name: string }>) {
return <div>Hello {handle.props.name}!</div>
}
// after
function Greeting(handle: Handle<{ name: string }>) {
return () => <div>Hello {handle.props.name}!</div>
} Defensive patterns
Strategy: type-guard
Type guard
// Remix UI components must return a function; verify in tests:
function returnsRenderFn(component: (handle: any) => unknown): boolean {
return typeof component({ props: {}, update() {}, queueTask() {}, signal: new AbortSignal(), id: 'x', context: { get() { throw new Error('no ctx') }, set() {} } }) === 'function'
} Prevention
- Always write components as `function X(handle) { …; return () => (…) }`
- Every early return in setup must still return a render function
- Unit-test each component renders via createRoot().flush() to catch contract violations
When it happens
Trigger: A component that returns JSX directly (`return <div/>` instead of `return () => <div/>`), returns nothing (missing return), or returns a non-function value like an object or promise. The error message reports the received typeof, e.g. 'object' or 'undefined'.
Common situations: Newcomers writing React-style components that return JSX from the component body; refactoring a render closure away so the outer function returns undefined; early `return` statements in the setup phase that skip the render-function return.
Related errors
- ${optionPath} values must be strings
- Expected a request handler function or action object with a
- popover.surface() requires a registered anchor before openin
- clientEntry() requires an entry ID
- ${errors.map((error) => error.message).join('\n')}
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/9be274eaa46e0170.
Report an issue: GitHub.