React Get Started

React is a JavaScript library for building user interfaces out of small, reusable pieces called components. Instead of manually grabbing DOM nodes and mutating them by hand the way you might with plain JavaScript, you describe what the UI should look like for the current data, and React takes care of updating the actual browser DOM to match. This lesson gets you from zero to a running React app: installing the tooling, understanding JSX, and rendering your first components.

Overview: How React Works

A React application is a tree of components. A component is just a JavaScript function that returns a description of some UI — what to render, not step-by-step instructions for how to build it in the DOM. That description is written using JSX, an HTML-like syntax extension for JavaScript. Because you say what the UI should look like rather than how to mutate it, this style is called declarative UI programming, as opposed to the imperative style of calling document.createElement and appendChild yourself.

Under the hood, JSX is not valid JavaScript on its own — a build tool (Vite, using a Babel or SWC plugin) compiles each JSX tag into a plain function call, roughly jsx("h1", { children: "Hello" }). Calling your top-level component function produces a lightweight tree of plain JavaScript objects describing the UI; this is often called the virtual DOM. React then walks this tree and creates the real DOM nodes to match it. This first pass is called mounting.

Later, when a component’s state changes (covered in depth in the State lesson), React calls that component’s function again to get a new virtual DOM tree, then compares it to the previous one in a process called reconciliation (or “diffing”). React figures out the minimal set of real DOM changes needed — update this text, add that attribute — and applies only those changes in the commit phase. This is why React is fast even though it re-runs your component functions often: re-running a function is cheap, but React is careful about which actual DOM operations it performs.

Syntax

The fastest, officially recommended way to start a new React project today is Vite. Run these commands in a terminal:

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

This scaffolds a project with the following key files:

File Purpose
index.html The single real HTML page. It contains an empty <div id="root"></div> that React will render into, and loads your JavaScript entry point.
src/main.jsx The entry point. It finds the root DOM node and tells React to render your top-level component into it.
src/App.jsx Your top-level component, where the rest of your component tree begins.

index.html stays almost empty:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>My React App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

main.jsx is where React actually takes over the page, using the React 18+ createRoot API:

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>
);
  • createRoot(domNode) — creates a React root attached to a real DOM element. Call it once per app.
  • .render(element) — tells React what component tree to display inside that root. Call this again (React does this internally on every state update) to update the UI.
  • <StrictMode> — a development-only wrapper that helps surface bugs (like impure render logic) by intentionally double-invoking certain functions. It renders nothing itself and is stripped in production.

Examples

Example 1: A basic component

function App() {
  return (
    <div>
      <h1>Hello, React!</h1>
      <p>This is my first component.</p>
    </div>
  );
}

export default App;

Renders: a page showing the heading “Hello, React!” followed by the paragraph “This is my first component.” App is just a JavaScript function whose name starts with a capital letter (required so JSX can tell it apart from a plain HTML tag like <div>) and that returns a single JSX tree. Everything inside the returned JSX is wrapped in one root element, here a <div>, because a component can only return one root node (or a Fragment, shown later).

Example 2: Passing data with props

function Greeting({ name, role }) {
  return (
    <div>
      <h2>Welcome, {name}!</h2>
      <p>You are logged in as a {role}.</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <Greeting name="Ava" role="admin" />
      <Greeting name="Marcus" role="editor" />
    </div>
  );
}

export default App;

Renders: two greeting blocks — “Welcome, Ava! You are logged in as a admin.” and “Welcome, Marcus! You are logged in as a editor.” Greeting receives a single props object, destructured here directly in the function signature as { name, role }. Curly braces { } inside JSX switch from “HTML mode” back into JavaScript, so {name} evaluates the variable and inserts its value as text. App reuses Greeting twice with different attributes, which is the essence of components: write the UI once, reuse it with different data.

Example 3: A component that reacts to user input

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Add 1</button>
    </div>
  );
}

export default Counter;

Renders: the text “Count: 0” and a button labeled “Add 1”. Clicking the button re-renders the component showing “Count: 1”, then “Count: 2”, and so on. This introduces the useState hook (covered fully in the State lesson): useState(0) gives you a piece of state starting at 0 and a setter function, setCount. Calling setCount tells React “this component’s data changed” — React re-runs Counter, gets fresh JSX with the new count, and updates only the text node that changed in the real DOM. Note the event prop is onClick (camelCase), not the HTML attribute onclick.

How It Works Step by Step

On first load (mount):

  • The browser loads index.html, which loads main.jsx as a module.
  • createRoot attaches React to the #root DOM node.
  • .render(<App />) calls the App function, which may call other component functions it renders, building a full virtual DOM tree.
  • React walks that tree and creates matching real DOM nodes, inserting them under #root.

On a state update:

  • An event handler (like handleClick above) calls a state setter (setCount).
  • React schedules a re-render of that component (and its children).
  • The component function runs again, producing a new virtual DOM tree.
  • React diffs the new tree against the previous one (reconciliation) and applies only the minimal real DOM changes (commit).

On unmount: if a component is removed from the tree (for example, conditionally rendered away), React removes its DOM nodes and runs any cleanup registered in its effects.

Common Mistakes

Mistake 1: Using the old ReactDOM.render API

Older tutorials and React 17 code use ReactDOM.render, which is deprecated in React 18+ and does not enable concurrent features:

import ReactDOM from "react-dom";
import App from "./App.jsx";

ReactDOM.render(<App />, document.getElementById("root"));

This still works in older codebases, but new projects should use createRoot from react-dom/client instead:

import { createRoot } from "react-dom/client";
import App from "./App.jsx";

createRoot(document.getElementById("root")).render(<App />);

Mistake 2: Returning multiple root elements

A component can only return one root JSX node. This fails because two sibling elements are returned with nothing wrapping them:

function App() {
  return (
    <h1>Hello</h1>
    <p>Welcome to React</p>
  );
}

Wrap the siblings in a single parent, or use a Fragment (<>...</>) when you don’t want to add an extra DOM element:

function App() {
  return (
    <>
      <h1>Hello</h1>
      <p>Welcome to React</p>
    </>
  );
}

Two more easy-to-miss traps worth knowing right away: JSX uses className instead of class and htmlFor instead of for, because class and for are reserved words in JavaScript; and component names must start with a capital letter (Greeting, not greeting), or JSX will treat the tag as a plain lowercase HTML element instead of your component.

Best Practices

  • Use createRoot from react-dom/client, not the legacy ReactDOM.render.
  • Keep one component per file, named the same as the file (Greeting.jsx exports Greeting), so components are easy to locate.
  • Start every new component’s return value with a single root element or a Fragment.
  • Keep components small and focused — if a component’s JSX gets long or does several unrelated things, split it into smaller components.
  • Wrap your root render in <StrictMode> during development to catch mistakes early.
  • Let Vite’s dev server (npm run dev) handle hot module reloading while you work instead of manually refreshing.

Practice Exercises

  • Exercise 1: Scaffold a new Vite + React project, then edit App.jsx so it renders your name inside an <h1> and a short bio inside a <p>.
  • Exercise 2: Create a Profile component that accepts username and followers props and renders a sentence using both. Render three <Profile /> elements from App with different prop values.
  • Exercise 3: Take the Counter example from this lesson and add a second button labeled “Subtract 1” that decreases the count. Hint: you’ll need a second click handler that calls setCount with count - 1.

Summary

  • React builds UIs from small, reusable function components that return JSX.
  • JSX compiles to plain JavaScript function calls; components describe what the UI should look like, not imperative DOM steps.
  • React renders by building a virtual DOM tree, diffing it against the previous tree (reconciliation), and applying minimal changes to the real DOM (commit).
  • Vite is the recommended way to scaffold a new React project; main.jsx uses createRoot(...).render(<App />) from the React 18+ API.
  • A component must return a single root element or a Fragment, use className instead of class, and be named with a capital letter.
  • Data flows into components through props; interactivity is introduced with hooks like useState, covered fully in later lessons.