Component Identity and Capitalization
You already know JSX treats lowercase and uppercase names differently.
<section />
<LessonCard />But there's another part to this which is probably more interesting: React doesn't only care about the name you typed. Once JSX is converted to JavaScript, React receives an actual value as the element type, and that value is used when React figures out whether it's still dealing with the same component as before.
So if React rendered something at one position, then later renders again, it checks the next element type against the previous one. Same type? React can keep the existing component instance there. Different type? The old one gets removed and React creates the new one.
This becomes pretty important once state enters the picture, because changing the component type also means the old component state goes away.
Let's first see what JSX actually produces for lowercase and uppercase names.
Lowercase names become host type strings
Take a normal HTML element:
const element = <article />;JSX treats article as a host element name. Conceptually, the element React receives contains a type something along these lines:
type "article"That "article" is a string.
React DOM sees that string and knows you're asking for a browser element with that tag name, so eventually it creates an actual <article> DOM element.
And one slightly funny thing here is that JSX does not even look for a JavaScript variable called article.
You can have one sitting right there:
const article = "unused local value";
function App() {
return <article />;
}That variable changes nothing. <article /> still means the host type string "article".
So lowercase JSX names such as these:
<main>
<h1>ReactBook</h1>
</main>are treated as browser element names.
React DOM deals with creating those DOM nodes, but all the normal HTML rules still apply. If you use a bad element for the job, React doesn't somehow fix the semantics for you.
Uppercase names are JavaScript lookups
Uppercase works differently.
function LessonCard() {
return <article>Component Identity</article>;
}
const element = <LessonCard />;Here JSX sees the uppercase L, so it treats LessonCard as a JavaScript identifier.
Meaning React doesn't receive the string "LessonCard" as some made-up HTML tag. It receives the value stored in the LessonCard variable, which in this case is the function itself.
Conceptually:
type LessonCardAnd because that value is a function component, React can call it while rendering.
This also explains what happens if you write:
const element = <LessonPanel />;without ever declaring or importing LessonPanel.
JavaScript has no value called LessonPanel in scope, so the lookup fails. React isn't searching the browser for a <LessonPanel> tag or anything weird. The JavaScript binding itself is missing.
Uppercase JSX names refer to JavaScript values, so the name has to resolve to something you've declared or imported.
An uppercase alias works fine too
The variable name doesn't have to match the original function name.
function LessonCard() {
return <article>Component Identity</article>;
}
const Card = LessonCard;Now both of these are valid:
<LessonCard />
<Card />Why?
Because LessonCard and Card currently point to the same function object.
JSX looks up the binding you wrote, gets the function value from it, and that function becomes the element type.
You can rename an import too and the same rule applies.
Now try doing this:
const card = LessonCard;
<card />Completely different result.
Because card starts lowercase, JSX does not read the card variable at all. It treats "card" as a host tag name.
So even though your variable contains the LessonCard function, JSX never asks for its value.
Capitalization is part of how JSX decides what kind of lookup to perform.
Picking a component through a variable
This becomes useful when you want to choose which component to render.
Say we have these two:
function ReadingView() {
return <p>Continue reading</p>;
}
function ReviewView() {
return <p>Review completed lessons</p>;
}Now maybe our app should render one or the other depending on some value.
You can first choose the component function:
const CurrentView = reviewing ? ReviewView : ReadingView;
return <CurrentView />;CurrentView is uppercase, so JSX reads the variable. If it currently contains ReviewView, then ReviewView becomes the element type. If it contains ReadingView, then that's the type React receives instead.
You can also choose between already-created React elements:
const currentView = reviewing
? <ReviewView />
: <ReadingView />;Both are fine, but those variables contain different kinds of values.
CurrentView contains a component type, which here means a function. currentView contains a React element that has already been created.
That's also why the capitalization is useful for us humans reading the code. When I see <CurrentView />, I immediately know we're treating that variable as a component type.
JSX can also read properties
You aren't limited to one identifier either.
JSX supports property access:
const Chapter = {
Heading: ChapterHeading,
Progress: LessonProgress,
};And then:
<Chapter.Heading />
<Chapter.Progress />JSX evaluates the property access and uses whatever value comes back as the component type.
So if Chapter.Heading contains the ChapterHeading function, that's the function React receives.
You'll sometimes see libraries group components this way. Whether you should do it in your own app depends on whether the grouping is actually useful. For a lot of normal application code, plain imports are easier to read:
import { ChapterHeading, LessonProgress } from "./chapter-ui.jsx";No need to create a component namespace just because JSX allows one.
The actual function object is part of the identity
Now we get to the part which causes some very confusing state bugs.
Look at this function:
function LessonCard() {
return <article>One lesson</article>;
}When this module loads, JavaScript creates that function object and stores a reference to it in LessonCard.
Every normal time you render:
<LessonCard />JSX reads that binding and React receives the same function reference as the type.
So you can roughly imagine React seeing this across renders:
previous type -> LessonCard function
next type -> LessonCard functionSame function reference, so at the type level React can treat it as the same component type.
But suppose the next render gives React another component:
previous type -> LessonCard function
next type -> ChapterCard functionThose are different function objects, so they're different component types.
At the same position, React will remove the previous component instance and create the new one. Any local state belonging to the removed component goes with it.
This is why component identity isn't based only on whatever name appears in DevTools. React is receiving JavaScript values.
Don't define components inside components
And now we can see why this causes trouble:
function App() {
function LessonCard() {
return <article>One lesson</article>;
}
return <LessonCard />;
}At first glance this can look completely reasonable. LessonCard is still called LessonCard, so what could possibly go wrong?
The problem is that every time App() runs, JavaScript executes that function declaration again and creates a new LessonCard function object.
So across two renders, you effectively get:
first App render -> LessonCard function A
second App render -> LessonCard function BSame source name, different function references.
React gets function A during one render and function B during the next render, so as far as the component type check goes, the type changed.
If that nested LessonCard had state, a parent render could cause that state to reset because React sees a new component type sitting there.
Put component definitions at module level instead:
function LessonCard() {
return <article>One lesson</article>;
}
function App() {
return <LessonCard />;
}Now the module creates LessonCard once, and later App renders keep referring to that same function.
Don't declare a component inside another component. Each parent render creates another function object, which means React receives another component type.
Now, you might look at this and ask about inline event handlers:
<button onClick={() => console.log("open")}>Open</button>That arrow function is also newly created during render, yes.
But React isn't using that function as the element type. The button's type is still the host string "button". That arrow function is only the value of the onClick prop.
So creating a fresh callback and creating a fresh component type are two different situations.
The filename doesn't decide component identity
Say LessonCard is currently in LessonCard.jsx.
Then you move it into:
components/lesson/LessonCard.jsxDid its runtime component identity somehow become different because the file moved?
React doesn't know your folder structure.
After the module system loads everything, React receives JavaScript values. When JSX creates <LessonCard />, React gets the function currently stored in the imported LessonCard binding.
The filename helps you organise code and helps tooling find source files, but React's normal runtime type check uses the actual type value it receives.
Development tooling can make this slightly more confusing because Fast Refresh tries to preserve state while you're editing code. It has extra development-only logic for associating component exports before and after module updates.
That's tooling trying to make local development nicer. Your normal runtime component model still comes back to the type React receives.
Custom elements stay lowercase
There's one more case that can look confusing if you've mostly worked with React components.
Browsers support custom elements with names containing a hyphen:
<reading-progress value="2"></reading-progress>That stays lowercase because reading-progress is a browser custom-element tag.
React DOM treats it as a host element name and passes properties or attributes according to React's custom-element handling.
Now maybe you write a React wrapper around it:
function ReadingProgress(props) {
return <reading-progress {...props} />;
}Then these two names refer to two different types of thing.
ReadingProgress is your JavaScript component function.
reading-progress is the browser custom element.
They happen to be connected because your component returns that custom element, but JSX handles the two names differently because one is uppercase and the other is lowercase.
Component names should still be useful to humans
React uses the function value for type identity, but names are still useful when you're debugging.
Open React DevTools and you might see:
App
ChapterPage
LessonList
LessonCardThat's quite readable. If something is wrong inside a lesson card, you can already see roughly where you are in the component tree.
Now imagine everything was called:
Container
Wrapper
Content
ItemTechnically React can run that code just fine. You, however, now have to open files and inspect each component to figure out what any of those names mean.
Going too far in the other direction is also not helping anybody:
AppMainContentChapterSectionLessonCardComponentNobody wants to read that every time an error stack appears.
LessonCard already tells us enough.
You can test the capitalization rule yourself
If you want to actually see JSX choosing between a component binding and a host element, add a temporary log:
function LessonCard() {
console.log("LessonCard render");
return <article>One lesson</article>;
}Render it normally:
<LessonCard />You'll see the log because React calls the function.
Now change only the capitalization:
<lessonCard />The function no longer gets called.
JSX now treats lessonCard as a host tag name, so React DOM tries to deal with it as an element instead of reading your LessonCard function.
Change it back after testing, obviously.
That tiny experiment tells you quite a lot about what JSX is doing.
So what should you remember from all this?
When JSX sees:
<article />it produces the host type string "article".
When it sees:
<LessonCard />it reads the LessonCard JavaScript binding and uses that value as the type.
When it sees:
<CurrentView />it reads whichever component type CurrentView currently contains.
And with:
<Chapter.Heading />it evaluates that property and uses the resulting value.
Something such as:
<reading-progress />stays a browser custom-element tag because it's lowercase and hyphenated.
But the bigger idea here is the function reference.
A component defined at module level normally keeps giving React the same function object, so React can keep seeing the same component type across renders.
Define that component again inside another component and you create another function object on every parent render. The name might still say LessonCard, but React receives a different value.
And yeah, that small difference can be enough to reset the entire component instance and its local state.