You've got the CicloUrbano project created and running, but everything you see on screen is still Vite's sample template. It's time to clear it out and write your first genuinely-yours code. In this lesson you'll take apart, piece by piece, the mechanism that gets React onto the page, understand the exact relationship between index.html and src/main.jsx, and create your first two components: Welcome and BikeCard. By the end you'll know what a component is, how to write one, how to use it, and why its name must start with a capital letter.
Contents
- Clearing out the sample template
- Anatomy of
index.html: the anchor point src/main.jsxline by line- Your first component:
Welcome - A component is a function that returns JSX
- The initial-capital convention
- Second component:
BikeCard React.StrictMode: what it is and why you see things twice- Watching the change happen live
- Clearing out the sample template
Vite generates a demo with a counter and logos. It's useful for confirming everything works, but it's in the way once you're ready to start. With the development server running (npm run dev), do this cleanup.
Delete these files, which we won't need:
Empty out src/index.css and leave it with some sober base styles for CicloUrbano:
/* src/index.css — CicloUrbano global styles */
:root {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #1f2933;
background-color: #f5f7fa;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 2rem;
}
h1,
h2,
h3 {
line-height: 1.25;
margin-top: 0;
}Replace the entire contents of src/App.jsx:
// src/App.jsx
function App() {
return (
<main>
<h1>CicloUrbano</h1>
</main>
);
}
export default App;Save and check the browser: the demo is gone, and a heading remains. If you see an error on screen or in the console, it's almost certainly because App.jsx is still importing ./App.css or ./assets/react.svg, files you just deleted. Check that no leftover import remains.
This minimal App.jsx already contains every concept in this lesson: a function called App, which returns something that looks like HTML, and which gets exported. Let's understand each part.
- Anatomy of
index.html: the anchor point
index.html: the anchor pointRecall the project's HTML, now with the CicloUrbano title:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CicloUrbano — Urban Bike Rental</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>Here's what happens when someone opens the page:
- The browser downloads this HTML. The
<body>contains a single emptydiv. At that moment the page is literally blank. - It finds the
<script type="module">tag and downloads/src/main.jsx(in development, Vite serves it already transformed; in production, it'll be the bundle generated bynpm run build). - That script runs React, which creates the DOM nodes and inserts them inside
div#root. - The page appears.
That's where the name SPA (Single Page Application) comes from: there's only one real HTML page, and JavaScript generates all the content inside a container. The id="root" isn't magic: it could be called app or ciclourbano, as long as main.jsx looks for it under the same name. root is the universal convention, so keep it.
sequenceDiagram
participant N as Browser
participant H as index.html
participant M as main.jsx
participant R as React
N->>H: Requests the page
H-->>N: HTML with an empty div#root
N->>M: Loads the /src/main.jsx module
M->>R: createRoot(div#root)
R->>R: Runs the App component
R-->>N: Inserts the nodes inside div#root
src/main.jsx line by line
src/main.jsx line by lineHere's the full file:
// src/main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>
);Five import lines and one statement. Let's go through them one at a time.
import { StrictMode } from 'react';
From the react package we import StrictMode, a special development-helper component. We'll cover it in section 8.
import { createRoot } from 'react-dom/client';
Here's the separation of responsibilities we mentioned in lesson 01-01: react knows how to describe interfaces; react-dom knows how to paint them in a browser. The /client subpath indicates the client renderer (there's also react-dom/server for server-side rendering, a topic in Module 10).
createRoot creates a React root: the connection between a real DOM node and the tree of components React will manage inside it.
Important:
createRootis the modern API, introduced in React 18. In older tutorials you'll seeReactDOM.render(<App />, document.getElementById('root')). That API is removed in React 19 and won't work. Always usecreateRoot.
import './index.css';
Importing a CSS file from JavaScript feels jarring the first time, but it's a build-tool feature, not a React one: Vite sees the import, processes the stylesheet, and injects it into the page. Since we're not extracting any value from the file, there are no braces or variable name.
import App from './App.jsx';
A default import: that's why it has no braces. It matches the export default App; at the end of App.jsx. Since it's a default export, the local name is up to you — you could write import Root from './App.jsx' — though for clarity it's always kept the same.
The final statement. Read it from the inside out:
document.getElementById('root')is plain old DOM JavaScript: it grabs the emptydivfrom the HTML.createRoot(...)turns thatdivinto a React-managed root and returns an object with arendermethod..render(...)receives the interface description and paints it inside the root.<App />is your component, used as though it were a tag. Notice the trailing slash: components with no inner content are self-closing, just like<img />or<input />.
This is the only time in the whole application you'll call createRoot. From here on, everything else is components inside components.
- Your first component:
Welcome
WelcomeCreate the folder where CicloUrbano's components will live, and inside it, the first one:
// src/components/Welcome.jsx
function Welcome() {
return (
<header>
<h1>CicloUrbano</h1>
<p>Rent an urban bike in your district, by the hour, hassle-free.</p>
</header>
);
}
export default Welcome;Let's break down the four parts:
function Welcome(): it's a perfectly ordinary JavaScript function. No parameters, for now.return (...): returns the description of a piece of interface written in JSX. The parentheses afterreturnaren't mandatory when the JSX fits on one line, but in practice they are when it spans several: without them, JavaScript's automatic semicolon insertion would slip a;right afterreturn, and the function would returnundefined.- A single root element: all the content is wrapped in
<header>. React needs to return one single thing; you'll dig deeper into this rule and into fragments in the lesson JSX. export default Welcome;: makes the component importable from other files.
Now use it in App.jsx:
// src/App.jsx
import Welcome from './components/Welcome.jsx';
function App() {
return (
<main>
<Welcome />
</main>
);
}
export default App;Save and check the browser: CicloUrbano's title and tagline appear. You've just written and used your first React component.
- A component is a function that returns JSX
This sentence is worth internalizing, because it's the complete definition:
A React component is a JavaScript function whose name starts with a capital letter and that returns an interface description (JSX).
No inheritance, no classes, no registering it anywhere, no configuration. A function. And because it's a function, it's used like a tag:
| What you write | What React does |
|---|---|
<Welcome /> |
Calls the Welcome() function and places what it returns at that spot |
<Welcome></Welcome> |
Exactly the same thing; the self-closing form is the usual one when there's no content inside |
Welcome() |
Calls the function directly. Don't do this: React stops treating it as a component and it loses its identity for state, DevTools, and reconciliation |
You can write the same component as an arrow function; both forms are equivalent, and you'll see both in real projects:
// Function declaration (what we'll use in this course)
function Welcome() {
return <h1>CicloUrbano</h1>;
}
// Arrow function assigned to a constant
const Welcome = () => {
return <h1>CicloUrbano</h1>;
};
// Arrow function with implicit return (no braces, no return)
const Welcome = () => <h1>CicloUrbano</h1>;In this course we'll use function declarations, because their name always shows up clearly in error messages and in React DevTools, and because they don't depend on their position in the file.
And since components are functions that return interface descriptions, they compose with each other into a tree:
flowchart TD
R["React root (div#root)"] --> S[StrictMode]
S --> A[App]
A --> B[Welcome]
A --> T[BikeCard]
- The initial-capital convention
This isn't a style preference: it's a mandatory rule. JSX decides what to do with a tag by looking at the first letter of its name.
<header> // lowercase -> React understands: standard HTML tag 'header'
<Welcome /> // uppercase -> React understands: my component called WelcomeThe reason is that JSX compiles down to function calls, and in that transformation the first letter determines whether the first argument is a text string or a reference to your function:
// You write this:
<header />
<Welcome />
// And it roughly transforms into this:
jsx('header', ...) // string: React creates a browser <header>
jsx(Welcome, ...) // reference: React runs your functionIf you write your component in lowercase, React will look for an HTML tag called welcome, won't find it among the known elements, and the browser will render it empty. You won't see a red error, just an incomplete page — which makes this one of the most frustrating mistakes for beginners.
| Name | Valid? | Result |
|---|---|---|
Welcome |
Yes | A correct component |
BikeCard |
Yes | Correct; PascalCase for compound names |
welcome |
No | React looks for a nonexistent HTML tag; nothing renders |
bike_card |
No | Same problem, plus it breaks naming conventions |
Practical rule: components in PascalCase, files named the same as the component. Welcome lives in Welcome.jsx, BikeCard in BikeCard.jsx. That way, finding the code behind what you see on screen is immediate.
- Second component:
BikeCard
BikeCardLet's build CicloUrbano's most distinctive piece: the card that represents a bike. In this lesson the data is hard-coded inside the component; making it flexible with data coming from outside is the goal of the lesson Props.
// src/components/BikeCard.jsx
function BikeCard() {
return (
<article className="bike-card">
<h3>Classic Urban</h3>
<p>Type: urbana</p>
<p>Status: disponible</p>
<p>Station: Main Square</p>
<p>
<strong>€2.50 / hour</strong>
</p>
</article>
);
}
export default BikeCard;Two details you may have noticed:
classNameinstead ofclass.classis a reserved word in JavaScript, and JSX is JavaScript. That's why React usesclassName. It's the most common beginner mistake, and we'll cover it in depth in the next lesson.<article>and<h3>instead ofdivfor everything. React doesn't free you from writing semantic HTML: components end up producing real tags, and choosing them well benefits accessibility and SEO.
The data matches bike bici-001 from the CicloUrbano domain we set up in lesson 01-01.
Now use it, and while you're at it, confirm something important: the same component can be used as many times as you like.
// src/App.jsx
import Welcome from './components/Welcome.jsx';
import BikeCard from './components/BikeCard.jsx';
function App() {
return (
<main>
<Welcome />
<section>
<h2>Featured bikes</h2>
<BikeCard />
<BikeCard />
<BikeCard />
</section>
</main>
);
}
export default App;In the browser you'll see three identical cards. That they're identical is exactly the limitation props will solve: each instance will receive its own bike and show different data. But the reuse mechanism is already visible, and it's the essence of the component model.
Add some styles to src/index.css to make it look presentable:
/* Add to the end of src/index.css */
.bike-card {
background: #ffffff;
border: 1px solid #d9e2ec;
border-radius: 8px;
padding: 1rem 1.25rem;
margin-bottom: 1rem;
max-width: 22rem;
}
.bike-card h3 {
margin-bottom: 0.5rem;
color: #12805c;
}
.bike-card p {
margin: 0.25rem 0;
font-size: 0.95rem;
}
React.StrictMode: what it is and why you see things twice
React.StrictMode: what it is and why you see things twiceGo back to main.jsx and notice that <App /> is wrapped in <StrictMode>:
StrictMode is a special component that renders absolutely nothing: it produces no tag in the DOM. Its only job is to turn on extra checks during development to catch problematic code as early as possible.
What it does:
- Runs your components twice on every render (development only). This helps surface accidental side effects: if your component modifies an external variable or does something beyond computing its interface, running it twice will produce a different result, and the bug surfaces immediately instead of six months from now.
- Mounts, unmounts, and remounts every component at startup, to verify that effect cleanup is written correctly.
- Warns about deprecated APIs and discouraged patterns.
That leads to the consequence that confuses beginners the most: if you put a console.log('rendering') inside a component, you'll see the message twice in the console. It's not a bug in your code or in React.
Check it for yourself:
// src/components/Welcome.jsx
function Welcome() {
console.log('Welcome is running');
return (
<header>
<h1>CicloUrbano</h1>
<p>Rent an urban bike in your district, by the hour, hassle-free.</p>
</header>
);
}
export default Welcome;The browser console shows the message twice. Remember:
With StrictMode (development) |
In production | |
|---|---|---|
| Component runs | Doubled, on purpose | A single run |
| Effects on mount | Run, clean up, and repeat | A single time |
| Impact on the end user | None: only affects npm run dev |
None |
Don't remove it. The temptation to strip out StrictMode to get a clean console is strong, and it's a mistake: you'd be turning off the bug detector exactly where it's useful. Effect duplication and its relationship with useEffect is covered in detail in the lesson The useEffect Hook.
Delete the console.log now before continuing.
- Watching the change happen live
With everything set up, take the chance to experiment and cement what you've learned. With npm run dev running and the browser next to your editor:
- Change
Welcome's tagline and save. The text updates instantly, with no reload. - Change
BikeCard's model toElectric Proand the price to€4.00 / hour. All three cards change at once, because all three are the same function returning the same thing. - Open React DevTools, Components tab. You'll see the tree
App → Welcomeand threeBikeCards. Notice thatStrictModeshows up in the React tree but not in the browser's Elements tab: that confirms it produces no HTML. - Comment out one of the three
<BikeCard />with{/* ... */}and confirm it disappears.
That loop — edit, save, see the result — is the normal rhythm of working in React, and it's why Vite's HMR matters so much.
Common Mistakes and Tips
- Forgetting
export default. The import returnsundefined, and React throwsElement type is invalid: expected a string ... but got: undefined. Whenever you see that message, check the export/import pairing for the component named. - Confusing default exports with named exports.
export default Welcomeis imported asimport Welcome from ...;export function Welcomeis imported asimport { Welcome } from .... Mixing them up givesundefined. - Naming the component in lowercase.
<welcome />doesn't throw a visible error: it just renders nothing. If a component "doesn't show up" and the console is clean, check the initial capital before anything else. - Putting
returnon one line and the JSX on the next without parentheses. Automatic semicolon insertion makes the function returnundefined, and React complains that nothing was returned. Open the parenthesis on the same line asreturn. - Using
ReactDOM.render. It's removed in React 19. If a tutorial uses it, it's outdated; translate it tocreateRoot(...).render(...). - Calling the component as a function,
{Welcome()}. It works visually, but React loses the component's identity: it doesn't show up in DevTools, it can't hold its own state, and it doesn't participate properly in reconciliation. Always use<Welcome />. - Removing
StrictModebecause "the console shows things twice". That's expected behavior in development, and disabling it hides real bugs. - Tip: one component per file, named the same as the file, inside
src/components/. Once CicloUrbano has thirty components, you'll be glad you did.
Exercises
Exercise 1
Create a Footer component in src/components/Footer.jsx that shows, inside a <footer> tag, the text "CicloUrbano — Urban bike rental service" and, on a second line, "Stations: Main Square · North Park · Central Station". Use it in App.jsx below the cards.
Exercise 2
This code doesn't work. Find the three errors and fix them, explaining why each one fails.
// src/components/station.jsx
function station() {
return
<div class="estacion">
<h3>Main Square</h3>
<p>Downtown district · 20 docks</p>
</div>;
}
export station;Exercise 3
Create a StationCard component that shows the data for station est-02 from the CicloUrbano domain (North Park, district North, 15 docks) with hard-coded data, and mount it in App.jsx inside a <section> with the heading "Stations". Then answer: if you wanted to show all three stations in the domain using this same component, what limitation would you run into, and which lesson in the course solves it?
Solutions
Solution 1.
// src/components/Footer.jsx
function Footer() {
return (
<footer>
<p>CicloUrbano — Urban bike rental service</p>
<p>Stations: Main Square · North Park · Central Station</p>
</footer>
);
}
export default Footer;// src/App.jsx
import Welcome from './components/Welcome.jsx';
import BikeCard from './components/BikeCard.jsx';
import Footer from './components/Footer.jsx';
function App() {
return (
<main>
<Welcome />
<section>
<h2>Featured bikes</h2>
<BikeCard />
<BikeCard />
<BikeCard />
</section>
<Footer />
</main>
);
}
export default App;Solution 2. The three errors:
- Lowercase name.
function station()makes<station />get interpreted as an unknown HTML tag, and nothing renders. It should beStationCard(PascalCase), and for consistency the file should be named the same. returnfollowed by a line break with no parentheses. JavaScript automatically inserts a semicolon afterreturn, the function returnsundefined, and React complains. You need to open the parenthesis on the same line asreturn.classinstead ofclassName.classis a reserved word in JavaScript. In JSX it's written asclassName. (Andexport station;isn't valid syntax either: it's missingdefault.)
// src/components/StationCard.jsx
function StationCard() {
return (
<div className="station">
<h3>Main Square</h3>
<p>Downtown district · 20 docks</p>
</div>
);
}
export default StationCard;Solution 3.
// src/components/StationCard.jsx
function StationCard() {
return (
<article className="station-card">
<h3>North Park</h3>
<p>District: North</p>
<p>Docks: 15</p>
</article>
);
}
export default StationCard;The limitation: the data is hard-coded inside the component, so all three instances would show exactly "North Park, North, 15 docks". For each card to show a different station, the component needs to receive its data from outside. That mechanism is props, covered in the lesson Props: Passing Data to Components. Rendering a full list from an array is covered in Lists and Keys.
Conclusion
You've gone from running Vite's template to writing your own components. You now know that index.html provides an empty div#root, that main.jsx turns it into a React root with createRoot(...).render(...) — never with the deprecated ReactDOM.render — and that a component is nothing more than a capitalized function that returns JSX and gets used like a tag. You've created Welcome and BikeCard, you've confirmed that the same component can be reused as many times as needed, and you understand why StrictMode runs your code twice in development.
Everything you've written inside return is JSX, and so far you've used it by intuition: it looks like HTML and works almost like HTML. Almost. In the next lesson, JSX: A JavaScript Syntax Extension, you'll see what it actually compiles into, which rules set it apart from HTML, and how to insert dynamic values with curly braces so your cards stop being fixed text.
React Course
Module 1: Getting Started with React
- What Is React?
- Setting Up the Development Environment
- Hello World in React
- JSX: A JavaScript Syntax Extension
- How React Renders: Virtual DOM and Reconciliation
Module 2: React Components
- Understanding Components
- Function vs Class Components
- Props: Passing Data to Components
- State: Managing Component State
- Styling Components: CSS, Modules and Utilities
Module 3: Working with Events
- Handling Events in React
- Conditional Rendering
- Lists and Keys
- Forms and Controlled Components
- Form Validation and Uncontrolled Components
- Accessibility in Interactive Components
Module 4: Advanced Component Concepts
- Lifting State Up
- Composition vs Inheritance
- React Lifecycle Methods
- Hooks: Introduction and Basic Use
- Error Boundaries: Catching Failures in the UI
Module 5: React Hooks
- The useState Hook
- The useEffect Hook
- The useRef Hook and DOM Access
- The useContext Hook
- The useReducer Hook
- Custom Hooks
Module 6: Routing in React
- Introducing React Router
- Setting Up React Router
- Nested Routes
- Programmatic Navigation
- Protected Routes and Access Control
Module 7: State Management
- Introduction to State Management
- The Context API
- Redux: Introduction and Setup
- Redux: Actions and Reducers
- Redux: Connecting to React
- Server State: Fetching, Caching and Syncing
Module 8: Performance Optimization
- Performance Optimization Techniques in React
- Memoization with React.memo
- The useMemo and useCallback Hooks
- Code Splitting and Lazy Loading
- Measuring Performance with React DevTools Profiler
Module 9: Testing React Applications
- Introduction to Testing
- Unit Testing with Jest
- Component Testing with React Testing Library
- Testing Asynchronous Code and Mocking APIs
- End-to-End Testing with Cypress
Module 10: Advanced Topics
- Server-Side Rendering (SSR) with Next.js
- Static Site Generation (SSG) with Next.js
- Suspense and React Server Components
- TypeScript with React
- React Native: Building Mobile Apps
