honojs/hono · error · Error
Can only set one of `children` or `props.dangerouslySetInner
Error message
Can only set one of `children` or `props.dangerouslySetInnerHTML`.
What it means
In hono/jsx server rendering, setting dangerouslySetInnerHTML replaces the element's children with raw HTML. To avoid ambiguous output, Hono throws if the same element also has children when serializing to a string buffer.
Source
Thrown at src/jsx/base.ts:252
})
buffer[0] += ' style="'
escapeToBuffer(styleStr, buffer)
buffer[0] += '"'
} else if (typeof v === 'string') {
buffer[0] += ` ${key}="`
escapeToBuffer(v, buffer)
buffer[0] += '"'
} else if (v === null || v === undefined) {
// Do nothing
} else if (typeof v === 'number' || (v as HtmlEscaped).isEscaped) {
buffer[0] += ` ${key}="${v}"`
} else if (typeof v === 'boolean' && booleanAttributes.includes(key)) {
if (v) {
buffer[0] += ` ${key}=""`
}
} else if (key === 'dangerouslySetInnerHTML') {
if (children.length > 0) {
throw new Error('Can only set one of `children` or `props.dangerouslySetInnerHTML`.')
}
children = [raw(v.__html)]
} else if (v instanceof Promise) {
buffer[0] += ` ${key}="`
buffer.unshift('"', v)
} else if (typeof v === 'function') {
if (!key.startsWith('on') && key !== 'ref') {
throw new Error(`Invalid prop '${key}' of type 'function' supplied to '${tag}'.`)
}
// maybe event handler for client components, just ignore in server components
} else {
buffer[0] += ` ${key}="`
escapeToBuffer(v.toString(), buffer)
buffer[0] += '"'
}
}
View on GitHub (pinned to e2740d5a1b)
Solutions
- Remove the children from the element that uses dangerouslySetInnerHTML
- If you need conditional behavior, choose one branch: render children XOR set dangerouslySetInnerHTML, never both
- Wrap the injected HTML in a dedicated element with no children
Example fix
// before
<div dangerouslySetInnerHTML={{ __html: html }}>
<p>Fallback</p>
</div>
// after
<div dangerouslySetInnerHTML={{ __html: html }}></div> Defensive patterns
Strategy: validation
Validate before calling
function SafeHtml({ html, children }: { html?: string; children?: Child[] }) {
if (html && children && children.length > 0) {
throw new TypeError('Pass either html or children, not both')
}
return <div {...(html ? { dangerouslySetInnerHTML: { __html: html } } : {})}>{children}</div>
} Type guard
const hasBoth = (p: { children?: unknown[]; dangerouslySetInnerHTML?: unknown }) =>
Boolean(p.dangerouslySetInnerHTML && p.children && p.children.length > 0) Try / catch
null
Prevention
- Treat dangerouslySetInnerHTML as mutually exclusive with children by convention
- Codemod/lint against JSX elements that have both an inner element and dangerouslySetInnerHTML
- Encapsulate raw HTML injection in a dedicated <Raw html={...}/> component
When it happens
Trigger: JSX like `<div dangerouslySetInnerHTML={{ __html: rawHtml }}>{'text'}</div>` or `<div dangerouslySetInnerHTML={{__html}}><span>x</span></div>` — any element where both children and dangerouslySetInnerHTML are present at render time.
Common situations: Copy-pasting React DOM patterns that silently prefer dangerouslySetInnerHTML; adding fallback children for empty HTML then forgetting to remove them; conditionally injecting HTML via dangerouslySetInnerHTML in a component that always renders children.
Related errors
- Invalid prop '${key}' of type 'function' supplied to '${tag}
- style sheet not found
- Async component is not supported in renderToString
- getServerSnapshot is required for server side rendering
- Invalid JSX tag name: ${tag}
AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28).
Data as JSON: /api/errors/bd630e9b83efdb34.
Report an issue: GitHub.