A React component not rendering is almost never a React bug. In the great majority of cases it is one of a handful of ordinary mistakes: the tag starts with a lower-case letter, the function forgot its return, the import does not match the export, && put a 0 on the page instead of your component, the component is never mounted, CSS collapsed it to nothing, or something threw and took the tree with it. Each cause below is reproduced in a real browser, so you can match what you see on your screen against a screenshot and a console line, then jump straight to the fix.
Every example here was built with React 19.3.0 in a Vite project and rendered in a real browser; the screenshots and console output are that run. Reference: Your First Component on react.dev.
Start with the symptom, not with the code
The fastest route to the cause is the shape of the failure. Find your row first:
| What you see | Console | Usually means | Go to |
|---|---|---|---|
| A gap where the component should be | Silent | Lower-case tag name, or no return | Cause 1 and Cause 2 |
A bare 0 or NaN | Silent | && with a number on the left | Cause 4 |
| The whole page is blank | Element type is invalid | Import / export mismatch | Cause 3 |
| The whole page is blank | A red error from your own code | A throw during render | Cause 7 |
| Nothing at all, anywhere | Silent | The component is never mounted | Cause 5 |
| Visible in DevTools, not on screen | Silent | CSS: zero height, hidden, off-screen | Cause 6 |
| It appears, but never updates | Silent | A state problem, not a render problem | the re-rendering guide |
That last row matters. This guide is about a component that never appears at all. If yours appears once and then ignores your updates, the cause is a different family of mistakes, and it is covered in React component not re-rendering after a state update.
Cause 1: the component name starts with a lower-case letter
This is the one that catches the most people, because nothing goes wrong. JSX decides what a tag means from its first character: capitalised means “the variable with this name”, lower-case means “an HTML tag with this name”. So <userCard /> never calls your function. React builds a DOM element called usercard, the browser accepts it as an unknown element, and you get an empty space and a clean console:
import { useEffect } from "react";
function userCard() { // lower-case name
return <p style={{ margin: 0 }}>Ada Lovelace - ada@example.com</p>;
}
function UserCard() { // same component, capitalised
return <p style={{ margin: 0 }}>Grace Hopper - grace@example.com</p>;
}
export default function App() {
useEffect(() => {
console.log(document.getElementById("slot").innerHTML);
}, []);
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<h3 style={{ margin: "0 0 8px" }}>Team</h3>
<div id="slot" style={{ border: "1px dashed #c8ced6", padding: 8 }}>
<userCard /> {/* JSX builds an HTML tag called "userCard" */}
<UserCard /> {/* JSX calls the component */}
</div>
<p style={{ margin: "8px 0 0", color: "#666" }}>
One of those two lines rendered. There is no error in the console.
</p>
</div>
);
}
The markup React actually produced:
<usercard></usercard> <p style="margin: 0px;">Grace Hopper - grace@example.com</p>

The console line is the proof. <usercard></usercard> is a real element sitting in the page with nothing inside it. The fix is to capitalise the function and every tag that uses it. The same rule bites when you choose a component at runtime: const Tag = isLink ? Link : Button then <Tag /> works, while a lower-case const tag = ... used as <tag /> quietly produces an HTML element instead.
Cause 2: the function never returns anything
An arrow function with braces, or a regular function whose return was lost in an edit, builds the JSX and then throws it away. React receives undefined, which it treats as “render nothing” without complaining:
import { useEffect } from "react";
function Greeting({ name }) {
<p>Hello, {name}!</p>; // built, then thrown away: the function returns undefined
}
function GreetingFixed({ name }) {
return <p style={{ margin: 0 }}>Hello, {name}!</p>;
}
export default function App() {
useEffect(() => {
console.log("Greeting returned:", String(Greeting({ name: "Ada" })));
}, []);
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<div style={{ border: "1px dashed #c8ced6", padding: 8, marginBottom: 8 }}>
<Greeting name="Ada" />
</div>
<div style={{ border: "1px solid #bfe3c8", background: "#f4fff6", padding: 8 }}>
<GreetingFixed name="Ada" />
</div>
</div>
);
}
What the component handed back:
Greeting returned: undefined

return. Bottom box: the identical component with one.Two habits prevent it. Use the concise arrow form, const Greeting = ({ name }) => <p>Hello, {name}!</p>;, so there is no body to forget. And never start the JSX on the line below a bare return, because JavaScript inserts a semicolon after it and hands back undefined; put the opening bracket on the same line as return.
Cause 3: the import does not match the export
A file that exports ProfileCard by name has no default export, so a default import binds undefined, and React throws the moment it tries to use that as an element type. This is the error behind most “my whole app went blank” reports:
import { Component } from "react";
import * as CardModule from "./ProfileCard";
// ProfileCard.jsx exports ProfileCard by name only, so the file has no default export.
// This is exactly what `import ProfileCard from "./ProfileCard"` would bind: undefined.
const ProfileCard = CardModule.default;
class Boundary extends Component {
state = { message: null };
static getDerivedStateFromError(error) { return { message: error.message }; }
componentDidCatch(error) { console.log(error.message.split("\n")[0]); }
render() {
if (!this.state.message) return this.props.children;
return (
<pre style={{ margin: 0, whiteSpace: "pre-wrap", fontSize: 13, color: "#8a1f1f",
background: "#fff4f4", border: "1px solid #f3c2c2", padding: 10 }}>
{this.state.message}
</pre>
);
}
}
export default function App() {
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<Boundary>
<ProfileCard name="Ada Lovelace" />
</Boundary>
</div>
);
}
App.displayName = "App"; // the bundler mangles function names; this keeps React's message readable
The first line React logged:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Read the second half of that message: it tells you to check the export in the child file, then match it. export default function ProfileCard pairs with import ProfileCard from "./ProfileCard", and export function ProfileCard pairs with import { ProfileCard } from "./ProfileCard". Modern bundlers often catch the mismatch before the browser does and fail the build instead, which is the friendlier version of the same problem.
Cause 4: the && operator printed a 0 instead of your component
&& does not return a boolean. It returns the left operand whenever that operand is falsy, 0 is falsy, and React renders the number 0 quite happily. So an empty list gives you a stray zero where the component should be. An empty string behaves the same way, only invisibly:
import { useState } from "react";
function Cart({ items }) {
return <ul style={{ margin: "0 0 0 18px" }}>{items.map((i) => <li key={i}>{i}</li>)}</ul>;
}
export default function App() {
const [items] = useState([]); // the cart is empty
return (
<div style={{ fontFamily: "system-ui", padding: 16, display: "flex", gap: 32 }}>
<div>
<code>{"{items.length && <Cart />}"}</code>
<div style={{ border: "1px solid #f3c2c2", background: "#fff4f4",
padding: 10, marginTop: 6, minWidth: 250 }}>
{items.length && <Cart items={items} />}
</div>
</div>
<div>
<code>{"{items.length > 0 && <Cart />}"}</code>
<div style={{ border: "1px solid #bfe3c8", background: "#f4fff6",
padding: 10, marginTop: 6, minWidth: 250 }}>
{items.length > 0 && <Cart items={items} />}
</div>
</div>
</div>
);
}

0; the right one renders nothing.Make the left side a real boolean: items.length > 0 &&, Boolean(count) &&, or a ternary when you want a fallback for the empty case. The same care pays off whenever a parent renders whatever it is handed, which is covered in React components with children.
Cause 5: the component is never mounted
It sounds too obvious to check, and it is the second most common cause after the lower-case tag. The component exists, the file is saved, the editor even autocompletes it, and nothing ever renders it. Three variants, in the order worth checking them:
- The component is defined and exported but never used in any parent’s JSX. Search the project for its name: one hit means the definition is the only hit.
- It is rendered inside a branch that is never taken, for example under a route you never visit, or behind a flag that is always
false. - It is mounted into a root that is not on screen. A typo in
createRoot(document.getElementById("root"))throws immediately, but a second root rendered into a detached node fails silently.
React DevTools settles it in seconds. Open the Components tab and search for the name: if it is not in the tree it was never mounted, and no amount of editing the component itself will help.
Cause 6: it rendered, you just cannot see it
When DevTools shows the component but the screen does not, the render succeeded and CSS is hiding the result. Measure it rather than guessing:
import { useEffect, useRef } from "react";
function Sidebar() {
return <p style={{ margin: 0 }}>Filters, sorting and saved views live here.</p>;
}
export default function App() {
const box = useRef(null);
useEffect(() => {
const el = box.current;
console.log("child in the DOM:", !!el.firstChild,
"| rendered height:", el.getBoundingClientRect().height);
}, []);
return (
<div style={{ fontFamily: "system-ui", padding: 16 }}>
<h3 style={{ margin: "0 0 8px" }}>Reports</h3>
<div ref={box} style={{ height: 0, overflow: "hidden" }}>
<Sidebar />
</div>
<p style={{ margin: "8px 0 0", color: "#666" }}>
The sidebar is in the page. Its wrapper is zero pixels tall.
</p>
</div>
);
}
Measured after the first paint:
child in the DOM: true | rendered height: 0
