Setting Up a React Project: Introduction and First Steps
React is a JavaScript library for building user interfaces, maintained by Meta and first released in 2013. Setting up a React project has never been simpler: Vite gives you a fast dev server and instant hot module replacement out of the box. This tutorial walks you through what React solves, how to scaffold a project with Vite, and how to write your first components.
What problems React solves
Building UIs with plain JavaScript works, but it creates specific headaches as applications grow. Every time your data changes, you have to find the right DOM nodes and update them manually. That process is slow, error-prone, and difficult to reason about.
React addresses this through a few core ideas:
- Virtual DOM — React keeps an in-memory copy of the DOM. When your data changes, it rebuilds the virtual representation, compares it against the previous version (this is called diffing), and surgically updates only the nodes that actually changed in the real DOM.
- Components — UI is broken into small, self-contained pieces. Each component encapsulates its own logic and rendering, making it easy to reason about isolated pieces of an interface.
- Unidirectional data flow — Data flows down from parent to child via props. When a child needs to communicate upward, it calls a function passed down as a prop. This constraint makes data flow predictable and bugs easier to trace.
These ideas work together. Components keep your code organised. The virtual DOM keeps rendering fast. Unidirectional data flow keeps your application easy to follow.
Scaffolding with Vite
Vite is the recommended way to create a React project. Its name is French for “fast”, and it earns that label. During development, Vite serves your files as native ES modules. No bundling step, no waiting forWebpack to rebuild everything. Changes appear the moment you save.
Install and scaffold a project:
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
Vite will print the address of your local dev server, typically http://localhost:5173. Open that in your browser and you’ll see the starter app. Behind the scenes, Vite uses esbuild for lightning-fast dependency pre-bundling and serves source files as native ESM, so changes appear instantly without a full rebuild.
Other package managers work equally well:
# Yarn
yarn create vite my-react-app --template react
# pnpm
pnpm create vite my-react-app --template react
To add TypeScript from the start, swap the template to react-ts. The setup is otherwise identical. Once scaffolding completes, you will have a working React project with a standard directory layout. Understanding this structure helps you know where to put new files:
Project Structure
After scaffolding, your project looks like this:
my-react-app/
├── index.html # HTML entry point
├── package.json # Dependencies and scripts
├── vite.config.js # Vite configuration
├── src/
│ ├── main.jsx # React app bootstrap
│ ├── App.jsx # Root component
│ ├── App.css # App-level styles
│ ├── index.css # Global styles
│ └── assets/ # Images and static assets
└── public/ # Static assets served as-is
The most important file is index.html, which contains a single <div id="root"></div>. Every React element your application produces renders inside that div. The entry point that connects this div to your React component tree is src/main.jsx, which calls ReactDOM.createRoot() to mount the app:
src/main.jsx is the bootstrap point:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
React.StrictMode activates additional checks in development that help you spot problems early, such as unsafe lifecycle methods or missing key props in lists.
src/App.jsx is the root component. This is where you start composing your interface. In a new project it renders some boilerplate; you’ll replace it with your own UI.
JSX: HTML in JavaScript
JSX lets you write HTML-like markup directly inside JavaScript. It is not required; React.createElement() works without it, but JSX is the idiomatic way to describe React UIs.
JSX expressions must follow a few rules:
- Return a single root element. Wrap siblings in a
<div>or a fragment<>...</>. - Use
classNameinstead ofclass.classis reserved in JavaScript. - Use camelCase for attributes.
tab-indexbecomestabIndex,onclickbecomesonClick. - Embed JavaScript with curly braces. Any JS expression goes inside
{}. - Close self-closing tags.
<img />not<img>.
// Wrong — two siblings without a wrapper
return (<h1>Title</h1><p>Text</p>);
// Correct — wrapped in a fragment
return (
<>
<h1>Title</h1>
<p>Text</p>
</>
);
// Embedding a JavaScript expression
const name = "World";
return <h1>Hello, {name}!</h1>;
Under the hood, JSX compiles to React.createElement() calls. The result is a plain JavaScript object, not HTML.
Your first component
A React component is a function that accepts an input (called props) and returns JSX. Component names must start with a capital letter, because React treats <MyComponent /> as a component and <mycomponent /> as an HTML element.
function Greeting({ name }) {
return <p>Welcome, {name}!</p>;
}
function App() {
return (
<div>
<Greeting name="Sarah" />
<Greeting name="Mike" />
</div>
);
}
// Renders: "Welcome, Sarah!" and "Welcome, Mike!"
The Greeting component takes a name prop and renders a welcome message. Notice how the same component is reused with different data, and this is the core of component composition. A component receives props as an object and uses them to determine what to render.
Props
Props pass data from a parent to a child. They are read-only, meaning a component should never modify the props it receives. Props can be any JavaScript value: strings, numbers, objects, arrays, or even functions for callbacks:
function UserCard({ username, role }) {
return (
<div className="user-card">
<h2>{username}</h2>
<p>Role: {role}</p>
</div>
);
}
// Usage:
<UserCard username="alice" role="admin" />
// Renders a card showing "alice" with role "admin"
The UserCard component destructures username and role from its props and renders them inside a div. This destructuring pattern is the standard way to access props in modern React because it makes the component’s expected inputs visible at a glance.
State with useState
Props flow down. When data needs to change inside a component, you use state, data that lives in the component and, when it changes, triggers a re-render. The useState hook manages local component state. It returns an array with two elements: the current value and a setter function:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<span>Count: {count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// Updates the UI immediately on every click
Calling setCount tells React that the state has changed. React then re-renders the component with the new value, updates the DOM, and the user sees the updated count. The setter can accept either a new value directly or a function that receives the previous state, which is important when your update depends on the previous value.
Conditional Rendering
Components can return different JSX depending on state or props. This is plain JavaScript: you use if statements, ternary operators, or logical && to decide what to render:
function LoginStatus({ isLoggedIn, username }) {
if (isLoggedIn) {
return <p>Hello, {username}!</p>;
}
return <p>Please log in.</p>;
}
function App() {
return (
<>
<LoginStatus isLoggedIn={true} username="Jordan" />
<LoginStatus isLoggedIn={false} />
</>
);
}
// Renders: "Hello, Jordan!" and "Please log in."
The LoginStatus component shows a basic pattern: check a condition and return different JSX. This is how you handle loading states, empty states, and permission-based UI in React, all with plain JavaScript conditionals inside your component body.
Rendering Lists
Use Array.prototype.map() to render a list of elements. Every item in a rendered list needs a key prop, a unique identifier that helps React track items across re-renders. Without keys, React may reuse DOM nodes incorrectly when the list order changes, causing visual glitches or lost component state:
function FruitList({ fruits }) {
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
);
}
function App() {
const fruits = ['Apple', 'Banana', 'Cherry'];
return <FruitList fruits={fruits} />;
}
// Renders: Apple, Banana, Cherry as an unordered list
Use the array index as a key only when the list is static and will not be reordered. For dynamic lists where items can be added, removed, or reordered, use a stable unique ID instead, typically from your data model. With your components in place, you are ready to see them in the browser.
Running and Building
# Start the dev server with hot module replacement
npm run dev
# Build for production (outputs to dist/)
npm run build
# Preview the production build locally
npm run preview
Press Ctrl+C in the terminal to stop the dev server.
Common mistakes to avoid
- Capitalization —
<MyComponent />is a component;<mycomponent />is an HTML element. This is one of the most common beginner errors. classNamenotclass— Writingclass="container"in JSX silently fails. Always useclassName.- Missing
keyin lists — Maps without akeyprop produce console warnings and cause rendering bugs when the list changes. - Mutating state directly — Always use the setter function:
setCount(count + 1), notcount++. Direct mutations do not trigger a re-render. - Self-closing tags —
<img />requires the trailing slash.<img>is invalid JSX.
Next Steps
With a working project and the component basics in place, you’re ready to move deeper. The next topics worth exploring are how React handles side effects with the useEffect hook, how to share state across components with React Context, and how routing works in single-page applications.
See Also
- JavaScript Closures — closures are used throughout React for event handlers, callbacks, and maintaining state in scope
- The Event Loop — understanding how JavaScript handles asynchronous operations pairs well with React’s state model
- ES Modules — React projects use ES module imports and exports to organize components across files