emotion-js/emotion · error · Error

cx can only be used during render

Error message

cx can only be used during render

What it means

Like css(), the cx() helper from the ClassNames render prop is only valid during render. After the component has rendered, invoking cx() in development throws, since merging registered class names must happen while emotion can still attach the result to the rendered output.

Source

Thrown at packages/react/src/class-names.tsx:157

export const ClassNames = /* #__PURE__ */ withEmotionCache<ClassNamesProps>(
  (props, cache) => {
    let hasRendered = false
    let serializedArr: SerializedStyles[] = []

    let css: ClassNamesContent['css'] = (...args) => {
      if (hasRendered && isDevelopment) {
        throw new Error('css can only be used during render')
      }

      let serialized = serializeStyles(args, cache.registered)
      serializedArr.push(serialized)
      // registration has to happen here as the result of this might get consumed by `cx`
      registerStyles(cache, serialized, false)
      return `${cache.key}-${serialized.name}`
    }
    let cx = (...args: Array<ClassNamesArg>) => {
      if (hasRendered && isDevelopment) {
        throw new Error('cx can only be used during render')
      }
      return merge(cache.registered, css, classnames(args))
    }
    let content = {
      css,
      cx,
      theme: React.useContext(ThemeContext)
    }
    let ele = props.children(content)
    hasRendered = true

    return (
      <>
        <Insertion cache={cache} serializedArr={serializedArr} />
        {ele}
      </>
    )
  }

View on GitHub (pinned to b882bcba85)

Solutions

  1. Call cx only inside the render-prop body during render
  2. Compute the merged class string during render and reuse the string in handlers/effects
  3. Use document/classList or state-driven class switching outside render instead of cx
  4. Restructure so conditional class logic is derived during render from state/props

Example fix

// before
<ClassNames>{({ cx }) => {
  const onClick = () => el.className = cx('a', 'b')
  return <div onClick={onClick} />
}}</ClassNames>

// after
<ClassNames>{({ cx }) => {
  const cls = cx('a', 'b')
  return <div className={cls} />
}}</ClassNames>
Defensive patterns

Strategy: validation

Validate before calling

// compute merged classes during render only
const merged = cx('a', 'b') // inside render body
// later: reuse `merged`, never call cx again

Try / catch

try {
  const cls = cxFn(args)
} catch (e) {
  if (e.message === 'cx can only be used during render') {
    // fall back to plain className concatenation
  } else throw e
}

Prevention

When it happens

Trigger: Calling cx inside event handlers, useEffect/useLayoutEffect, setTimeout, or async callbacks after the ClassNames render has completed.

Common situations: Toggling classes on click via cx after mount; class composition in response to fetched data; storing cx in a ref/handler used later.

Related errors


AI-assisted analysis of emotion-js/emotion@b882bcba85 (2026-09-02). Data as JSON: /api/errors/b341f97ff0a71849. Report an issue: GitHub.