# Fix: Objects are not valid as a React child

JSX received a plain object, a `Date` or another non-renderable value where it expected text, a number, an element or an array. React cannot guess how to display an object, so it throws. The fix is to render a field of the object (`{user.name}`), format the value as a string (`{date.toLocaleDateString()}`), or map an array to elements.

| Fact | Detail |
|---|---|
| Error | `Objects are not valid as a React child (found: object with keys {id, name}). If you meant to render a collection of children, use an array instead.` |
| In production builds | `Minified React error #31` with the found value in the link, such as `args[]=object%20with%20keys%20%7Bid%2C%20name%7D` |
| Where you see it | Browser console, the Next.js error overlay, server logs when it happens during server rendering |
| Usual cause | `{user}` or `{error}` in JSX where you meant `{user.name}` or `{error.message}` |
| Usual fix | Render a string or number from the object, or map arrays to elements |
| BugTape catches it | Yes, automatically, when no error boundary of yours catches it. One line in your boundary otherwise |

## What the error means

React can render strings, numbers, elements, arrays of those, and `null`, `undefined` or booleans (which render nothing). When a child is any other object, React throws error #31.

The text inside `found:` tells you what it got. For a plain object React lists its keys: `object with keys {id, name}`. For anything else it prints the type from `Object.prototype.toString`, such as `[object Date]` or `[object Map]`. The keys are usually enough to recognise the object in your code.

Production builds keep that detail. The minified message links to `react.dev/errors/31` and passes the found value in the `args[]` part of the URL, so you can decode it from a production report.

## Common causes

1. **An object rendered where a field was meant.** `{user}` instead of `{user.name}`, or `{product.price}` when `price` is `{ amount, currency }`.
2. **An error object rendered as text.** `{error}` from a `fetch` wrapper, an API client or a form library, where the text is in `error.message`.
3. **A `Date` rendered directly.** The message says `found: [object Date]`.
4. **Props not destructured.** `function Title(title)` receives the whole props object, so `{title}` renders `{ title: '…' }` and the message lists the key `title`.
5. **An object passed to a component that renders its `children`.** `<Badge>{status}</Badge>` where `status` is an object.

## How to fix it

### Render a field of the object

```tsx
// Before: found: object with keys {id, name, email}
<p>Signed in as {user}</p>

// After
<p>Signed in as {user.name}</p>
```

To see an object's contents while debugging, turn it into a string:

```tsx
<pre>{JSON.stringify(user, null, 2)}</pre>
```

### Render the message of an error

```tsx
// Before
{error && <p role="alert">{error}</p>}

// After: works for Error objects and plain { message } objects
{error && <p role="alert">{error instanceof Error ? error.message : String(error.message ?? error)}</p>}
```

With React Hook Form, field errors are objects too. Render `errors.email?.message`.

### Format dates as strings

```tsx
// Before: found: [object Date]
<time>{order.createdAt}</time>

// After
<time dateTime={order.createdAt.toISOString()}>
  {order.createdAt.toLocaleDateString('en-GB')}
</time>
```

If the date is formatted during server rendering, format it the same way on both sides, or you can get a hydration mismatch instead. See [Next.js hydration mismatch](/guides/nextjs-hydration-mismatch/).

### Destructure props

```tsx
// Before: title is the whole props object
function Title(title) {
  return <h1>{title}</h1>;
}

// After
function Title({ title }: { title: string }) {
  return <h1>{title}</h1>;
}
```

### Map arrays of objects to elements

An array is a valid child only when its items are valid children. Map each object to an element with a `key`.

```tsx
// Before: an array of objects
<ul>{orders}</ul>

// After
<ul>
  {orders.map((order) => (
    <li key={order.id}>{order.number}</li>
  ))}
</ul>
```

For a `Map` or a `Set`, convert it first with `Array.from(map.values())`.

## How to catch it in production with BugTape

In production React throws `Minified React error #31` with the found value in the link. The error is thrown during render, so where it goes depends on your error boundaries.

- **No error boundary of yours catches it.** React 19 passes the error to `reportError()`, which fires a window `error` event. React 18 rethrows it as an uncaught error. The BugTape browser SDK reports it automatically with the page URL, the stack and the clicks and requests before it. That last part often shows which API response had the unexpected shape. In the Next.js App Router, an error that only the built-in boundary catches also goes to `reportError()`.
- **Your error boundary catches it**, including a Next.js `error.tsx`. React and Next.js then only call `console.error`. BugTape records console output into a report but does not start one from it. Add one line to the boundary:

```tsx
'use client';
import { useEffect } from 'react';

export default function Error({ error, retry }: { error: unknown; retry: () => void }) {
  useEffect(() => {
    reportError(error); // fires the window error event BugTape listens for
  }, [error]);
  return <button onClick={() => retry()}>Try again</button>;
}
```

For a plain React 19 app, `createRoot(el, { onCaughtError: (error) => reportError(error) })` does the same for every boundary.

The SDK records nothing before `init()` runs. When the error happens while a Server Component or server render runs in Next.js, the [Node SDK's `onRequestError` hook](/docs/node/) reports it on the Node.js runtime.

Add the browser SDK once (see the [quickstart](/docs/quickstart/)):

```ts
import { init } from '@bugtape/sdk';

init({ apiKey: 'YOUR_PROJECT_KEY', release: 'YOUR_GIT_SHA' });
```

Reports with the same error from the same place group into one issue with a count, so an API change that breaks one component for every user shows up as one issue. Set `release` so that when you mark the issue fixed, a report from a newer release reopens it as a regression. Your coding agent can read the issue over MCP (`npx -y bugtape connect claude`, or `cursor` or `codex`) and propose the change. You review the pull request.

## FAQ

### What is React error 31?

It is the production code for `Objects are not valid as a React child (found: …). If you meant to render a collection of children, use an array instead.` The link in the production message includes the found value, URL-encoded, after `args[]=`.

### Why does it say "found: [object Promise]"?

That message comes from React 18 and earlier, which could not render a promise. The usual cause was an `async` function used as a component, or a missing `await`. React 19 accepts a promise as a child and suspends until it resolves. An `async` component marked `'use client'` is still not supported, and React prints a development error saying it is an async Client Component.

### Can I render an object if I convert it to JSON?

Yes. `JSON.stringify(value)` returns a string, which React renders. It is useful for debugging. For real UI, render the fields you need.

### Why does the error only happen for some users?

The value comes from data, so it depends on what the API returned for that user. A field that is usually a string can be an object for some records, such as a name stored as `{ first, last }`. The found keys in the message show which shape arrived.

## Sources

- [React error codes](https://github.com/facebook/react/blob/main/scripts/error-codes/codes.json) (31), checked 2 Nov 2026.
- [React source, ReactChildFiber.js](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js) (`throwOnInvalidObjectType`, promise children), checked 2 Nov 2026.
- [React source, formatProdErrorMessage.js](https://github.com/facebook/react/blob/main/packages/shared/formatProdErrorMessage.js) (production message and `args[]`), checked 2 Nov 2026.
- [React source, ReactFiberHooks.js](https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js) (async Client Component warning), checked 2 Nov 2026.
- [React docs: createRoot `onCaughtError`](https://react.dev/reference/react-dom/client/createRoot), checked 2 Nov 2026.

## Related errors

- [Cannot read properties of undefined](/guides/cannot-read-properties-of-undefined/): `TypeError: Cannot read properties of undefined (reading 'map')`
- [React: Too many re-renders](/guides/react-too-many-re-renders/): `Too many re-renders. React limits the number of renders to prevent an infinite loop.`
- [Next.js hydration mismatch](/guides/nextjs-hydration-mismatch/): `Hydration failed because the server rendered HTML didn't match the client`
- [Next.js Server Components render error](/guides/nextjs-server-components-render-error/): `An error occurred in the Server Components render. The specific message is omitted in production builds`
- [React: Maximum update depth exceeded](/guides/react-maximum-update-depth-exceeded/): `Maximum update depth exceeded`

All guides: [Fix an error](/guides/).

[Start free](/console/register?utm_source=bugtape_seo&utm_campaign=error-guides). Free plan · No credit card · Set up in minutes. Or [try the live demo](/demo/?from=landing&start=1#capture) first.
