facebook/react · error · Error

379

379

Error message

Refs cannot be used in Server Components, nor passed to Client Components.

What it means

While serializing RSC output, React found a non-null ref on an element rendered by a Server Component or passed toward a Client Component. Refs are live, mutable handles bound to client-side instances, so there is no wire format for them across the RSC boundary. React hard-throws instead of silently dropping the ref, because silent loss would corrupt any code that relies on it.

Source

Thrown at packages/react-server/src/ReactFlightServer.js:2337

  }
}

function renderElement(
  request: Request,
  task: Task,
  type: any,
  key: ReactKey,
  ref: mixed,
  props: any,
  validated: number, // DEV only
): ReactJSONValue {
  if (ref !== null && ref !== undefined) {
    // When the ref moves to the regular props object this will implicitly
    // throw for functions. We could probably relax it to a DEV warning for other
    // cases.
    // TODO: `ref` is now just a prop when `enableRefAsProp` is on. Should we
    // do what the above comment says?
    throw new Error(
      'Refs cannot be used in Server Components, nor passed to Client Components.',
    );
  }
  if (__DEV__) {
    jsxPropsParents.set(props, type);
    if (typeof props.children === 'object' && props.children !== null) {
      jsxChildrenParents.set(props.children, type);
    }
  }
  if (
    typeof type === 'function' &&
    !isClientReference(type) &&
    !isOpaqueTemporaryReference(type)
  ) {
    // This is a Server Component.
    return renderFunctionComponent(request, task, key, type, props, validated);
  } else if (type === REACT_FRAGMENT_TYPE && key === null) {
    // For key-less fragments, we add a small optimization to avoid serializing

View on GitHub (pinned to eafeac097b)

Solutions

  1. Move the ref-consuming logic into a file marked 'use client' and reference that component instead.
  2. Restructure so the server passes plain data and the client component owns its own refs.
  3. If a handle must cross, pass an id/string prop and let the client resolve the node itself.

Example fix

// before — Server Component
<ClientInput ref={inputRef} defaultValue="hi" />

// after — the ref lives in the client file
// Input.client.tsx
'use client';
export function ClientInput(props) {
  const ref = useRef(null);
  return <input ref={ref} {...props} />;
}
// Server Component
<ClientInput defaultValue="hi" />
Defensive patterns

Strategy: type-guard

Validate before calling

// DEV-time walk: catch ref-carrying elements before render does.
const hasRef = (el: any) =>
  el != null && typeof el === 'object' && typeof el.$$typeof === 'symbol' && el.ref != null;
export function assertNoRefs(props: Record<string, unknown>) {
  for (const [k, v] of Object.entries(props)) {
    if (hasRef(v)) throw new Error(`prop ${k} is an element carrying a ref — move ref usage to a client component`);
  }
}

Type guard

export function elementHasRef(el: unknown): boolean {
  return typeof el === 'object' && el !== null && 'ref' in el && (el as any).ref != null;
}

Prevention

When it happens

Trigger: Rendering <SomeComponent ref={r}/> inside a Server Component tree; passing an element that carries ref as a prop value into a Client Component; libraries injecting refs via cloneElement in server-rendered layout code.

Common situations: Trying to use useRef in a shared component that ends up in the server graph; forwarding refs through a component not marked 'use client'; design-system components imported into a server tree.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/194b740925ee8ce5. Report an issue: GitHub.