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

  1. Clearing out the sample template
  2. Anatomy of index.html: the anchor point
  3. src/main.jsx line by line
  4. Your first component: Welcome
  5. A component is a function that returns JSX
  6. The initial-capital convention
  7. Second component: BikeCard
  8. React.StrictMode: what it is and why you see things twice
  9. Watching the change happen live

  1. 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:

rm src/App.css
rm src/assets/react.svg
rm public/vite.svg

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.

  1. Anatomy of index.html: the anchor point

Recall 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:

  1. The browser downloads this HTML. The <body> contains a single empty div. At that moment the page is literally blank.
  2. 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 by npm run build).
  3. That script runs React, which creates the DOM nodes and inserts them inside div#root.
  4. 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

  1. src/main.jsx line by line

Here'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: createRoot is the modern API, introduced in React 18. In older tutorials you'll see ReactDOM.render(<App />, document.getElementById('root')). That API is removed in React 19 and won't work. Always use createRoot.

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:

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>
);
  1. document.getElementById('root') is plain old DOM JavaScript: it grabs the empty div from the HTML.
  2. createRoot(...) turns that div into a React-managed root and returns an object with a render method.
  3. .render(...) receives the interface description and paints it inside the root.
  4. <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.

  1. Your first component: Welcome

Create the folder where CicloUrbano's components will live, and inside it, the first one:

mkdir -p src/components
// 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 after return aren'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 after return, and the function would return undefined.
  • 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.

  1. 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]

  1. 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 Welcome

The 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 function

If 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.

  1. Second component: BikeCard

Let'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:

  • className instead of class. class is a reserved word in JavaScript, and JSX is JavaScript. That's why React uses className. It's the most common beginner mistake, and we'll cover it in depth in the next lesson.
  • <article> and <h3> instead of div for 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;
}

  1. React.StrictMode: what it is and why you see things twice

Go back to main.jsx and notice that <App /> is wrapped in <StrictMode>:

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </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.

  1. 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:

  1. Change Welcome's tagline and save. The text updates instantly, with no reload.
  2. Change BikeCard's model to Electric Pro and the price to €4.00 / hour. All three cards change at once, because all three are the same function returning the same thing.
  3. Open React DevTools, Components tab. You'll see the tree App → Welcome and three BikeCards. Notice that StrictMode shows up in the React tree but not in the browser's Elements tab: that confirms it produces no HTML.
  4. 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 returns undefined, and React throws Element 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 Welcome is imported as import Welcome from ...; export function Welcome is imported as import { Welcome } from .... Mixing them up gives undefined.
  • 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 return on one line and the JSX on the next without parentheses. Automatic semicolon insertion makes the function return undefined, and React complains that nothing was returned. Open the parenthesis on the same line as return.
  • Using ReactDOM.render. It's removed in React 19. If a tutorial uses it, it's outdated; translate it to createRoot(...).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 StrictMode because "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:

  1. Lowercase name. function station() makes <station /> get interpreted as an unknown HTML tag, and nothing renders. It should be StationCard (PascalCase), and for consistency the file should be named the same.
  2. return followed by a line break with no parentheses. JavaScript automatically inserts a semicolon after return, the function returns undefined, and React complains. You need to open the parenthesis on the same line as return.
  3. class instead of className. class is a reserved word in JavaScript. In JSX it's written as className. (And export station; isn't valid syntax either: it's missing default.)
// 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;
// src/App.jsx (excerpt)
<section>
  <h2>Stations</h2>
  <StationCard />
</section>

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

Module 2: React Components

Module 3: Working with Events

Module 4: Advanced Component Concepts

Module 5: React Hooks

Module 6: Routing in React

Module 7: State Management

Module 8: Performance Optimization

Module 9: Testing React Applications

Module 10: Advanced Topics

Module 11: Project: Building a Complete Application

© Copyright 2026. All rights reserved