Why Your UI Shows [object Object] and How to Fix It Fast
Seeing [object Object] instead of a title is a classic JavaScript issue. Learn exactly why it happens in React and other UIs, how to debug it quickly, and the patterns that prevent it for good.

![Why Your UI Shows [object Object] and How to Fix It Fast](https://fvnmlvqcgqaarpyajaoh.supabase.co/storage/v1/object/public/images/1758580185084-c5pj4s.png)
The moment you see it
You push a build, reload the page, and there it is: a card that should list recent titles now reads [object Object]. Your first thought is that the data must be wrong. In reality, the browser is telling you something precise. Some code tried to turn a JavaScript object into a string without telling it how to present itself. The platform did the only thing it knows to do and produced a string token that literally says it is an object.
The root cause is straightforward. When JavaScript needs to render or concatenate a value into text, it asks the value to describe itself as a string. For plain objects, the default object toString returns the tag "[object Object]". That return value is useful for debugging types, but it is useless for your end users.
This guide explains why that happens, where it usually pops up, and how to fix it in minutes. It also shows how to design your data flow so the problem does not come back.
Why it happens
JavaScript performs implicit string conversion in many places:
- Template literals and string concatenation
- JSX text nodes in frameworks like React and Preact
- DOM APIs that expect strings, such as
textContent
- Logging and error messages that interpolate variables
If the value you interpolate is an object, JavaScript calls toString()
or valueOf()
to get a text representation. For plain object literals, toString()
yields "[object Object]". That is why the symptom appears in any UI stack that relies on JavaScript stringification.
The most common places it sneaks in
You will see this pattern show up in a handful of repeatable situations.
1) Template strings with raw objects
const article = { title: "Travel rules update" };
const msg = `Latest: ${article}`; // "Latest: [object Object]"
Fix by selecting the exact field you want.
const msg = `Latest: ${article.title}`;
2) JSX text nodes with unformatted props
function Card({ title }) {
return <h3>{title}</h3>;
}
const title = { text: "Inflation cools in August" };
<Card title={title} /> // renders [object Object]
Fix by passing a string, not an object.
<Card title={title.text} />
3) Generic list renderers that assume strings
A reusable component might accept an array of items and render each item inside a <li>
. If items
is an array of objects and the component blindly does {item}
, you will get [object Object]
for each row.
Fix by providing a projection function or a renderItem
prop that maps each object to a human readable string.
4) I18n and templating with unformatted values
Translation strings often include placeholders. If the code hands an entire object to a placeholder instead of a field, the templating library will stringify it.
Fix by passing primitives to the translation call, not composite objects.
5) Logging that hides the real issue
Sometimes [object Object]
appears in logs or error toasts. The user facing toast is interpolating a whole error object rather than the message. You will see [object Object]
on the screen and a vague bug report. Meanwhile the console has the real stack trace.
Fix by displaying error.message
or a mapped message, and logging the full object to the console or your telemetry system.
A fast debugging checklist
Use this list when you encounter the symptom in production or during review. For a deeper walkthrough, see the rendering checklist guide.
- Reproduce the state
- Navigate to the view that shows
[object Object]
and capture the data payloads from your network tab.
- Inspect the value types
- In the component or function that renders the string, add a temporary log:
console.log('title type:', typeof title, title);
- If it says
object
, look at the keys that are available.
- Render the one field you actually want
- Most UIs want
item.title
,user.name
, orerror.message
. Pick the right field and render that.
- Guard against missing data
- If your field can be null or undefined, supply a default:
<h3>{item?.title ?? 'Untitled'}</h3>
- Avoid blanket
JSON.stringify
in the UI
JSON.stringify
is handy for debugging but do not commit it as the final user facing text. It reveals raw structure and may expose sensitive fields.
Fixes that stick
Once you have patched the immediate bug, add a couple of structural guard rails.
Create tight component contracts
If a component is named Title
, it should accept a string called text
. Not a generic data
prop. Not an entire article
object. Constrain the API and TypeScript will help keep it honest.
type TitleProps = { text: string };
function Title({ text }: TitleProps) {
return <h2>{text}</h2>;
}
// Good
<Title text={article.title} />
// Avoid
<Title text={article} />
Validate at the edges
When data enters the app, validate it once and transform it to the shape your views expect. Use a schema library to ensure that title
is always a string.
import { z } from 'zod';
const Article = z.object({
id: z.string(),
title: z.string(),
excerpt: z.string().optional()
});
const parsed = Article.parse(serverPayload);
Now your components can assume title
is a string, which prevents accidental object interpolation. For more patterns, see data validation best practices.
Prefer formatters over ad hoc interpolation
Create small utilities to format domain objects instead of sprinkling ${object}
across the codebase.
function formatUser(u: { first: string; last: string }) {
return `${u.first} ${u.last}`;
}
function formatMoney(cents: number, currency = 'USD') {
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(cents / 100);
}
When you find yourself writing ${obj}
, stop and ask which formatter should exist.
Add lint rules that catch implicit coercion
Enable rules that flag suspicious string concatenations. Examples:
- Disallow
+
with non primitives in your codebase except in very specific utils - Prefer template literals that call clear formatters
- Use TypeScript’s
noImplicitAny
and strict null checks so accidental object values are obvious
Unit tests for rendering
Snapshot tests sometimes normalize [object Object]
into harmless looking output, which hides the bug. Prefer explicit rendering tests that assert the presence of meaningful text.
test('Card renders a title string', () => {
render(<Card title={{ text: 'Hello' }} />);
expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('Hello');
});
This test will fail until you pass title.text
to the component.
Special cases that fool people
Not every object is a plain object, and not every object stringifies the same way.
Objects that implement toString
Some libraries provide rich objects that define their own toString
method. They will not produce [object Object]
. They might emit a custom formatted string. This can hide problems because the UI appears to work until a different object that lacks toString
slips in.
Fix by standardizing the component contract. Do not rely on an object’s magic methods for user facing text.
toJSON
changes stringification
JSON.stringify
checks for a toJSON
method. If present, it uses that result. This is helpful for logs, but it can mislead you into thinking an object is safe to interpolate everywhere. Treat JSON.stringify
as a debugging tool, not a rendering strategy. For details on options and the replacer parameter, see the MDN JSON.stringify reference.
Arrays and Maps
Rendering arrays directly will coerce them by joining with commas, which can be acceptable if the array contains strings. Rendering Maps or Sets will produce [object Map]
or [object Set]
.
Fix by mapping to strings explicitly.
<ul>
{tags.map(tag => <li key={tag}>{tag}</li>)}
</ul>
Defensive defaults that mask bugs
A common quick fix is const text = String(value || '')
. That silences errors but loses signal. If value
is an object, you still get [object Object]
. Instead, throw early or surface a clear developer facing message so you can correct the data flow.
A robust pattern for list UIs
Your incident started with a list of recent titles that failed to render. Here is a small, robust pattern for that exact scenario.
// 1) Define the contract
interface RecentItem { id: string; title: string }
interface RecentListProps {
items: RecentItem[]
}
// 2) Render only strings in text nodes
function RecentList({ items }: RecentListProps) {
return (
<ul aria-label="Recent posts">
{items.map(item => (
<li key={item.id}>
<a href={`/posts/${item.id}`}>{item.title || 'Untitled'}</a>
</li>
))}
</ul>
);
}
// 3) Validate and transform at the boundary
function normalize(raw: unknown): RecentItem[] {
// Replace with your schema library in production
if (!Array.isArray(raw)) return [];
return raw
.filter(x => x && typeof x === 'object')
.map((x: any) => ({
id: String(x.id ?? ''),
title: typeof x.title === 'string' ? x.title : ''
}))
.filter(x => x.id);
}
This approach ensures the rendering layer never receives composite objects for text nodes. The component is simple and cannot accidentally print [object Object]
. For more UI patterns, see error message patterns.
Production checklists
Use these checklists to prevent regressions during code review.
Rendering checklist
- Are all text nodes primitives or results of formatters
- Are object props used only for components that expect objects
- Are null and undefined handled with safe defaults
Data checklist
- Is incoming server data validated and normalized before reaching the view
- Are the field names and types stable and documented
- Do we avoid over fetching objects when we only need a simple field
Observability checklist
- Do user facing toasts display human friendly messages
- Do logs include the raw object for debugging without exposing it to the UI
- Do we track the count of occurrences of
[object Object]
in the DOM during QA
Rethinking error messages
If you ever consider showing raw objects to users, stop and design the message. Users need a clear action, not internal state. Convert system details into user centered copy and send the full object to your telemetry platform for diagnostics.
For example, prefer this:
- Screen: "We could not load recent titles. Try again in a moment."
- Log: Full error object with stack trace, request ID, and user context
This separation keeps the user experience clean while preserving the information you need to fix the root cause.
A final word on culture
Bugs like [object Object]
are usually the symptom of a small contract mismatch, not a big system failure. Encourage a culture that celebrates removing accidental complexity. Ask for clear component props, typed boundaries, and formatting helpers. Make it boring to render text correctly. Once the team adopts these patterns, this entire class of bugs fades away.
Quick reference
- What it is:
[object Object]
appears when an object is implicitly converted to a string - Quick fix: render the exact field you want
- Make it durable: strict component contracts and schema validation
- Preferred approach: formatters over ad hoc interpolation
- Test it: assert meaningful text, not just snapshots