A practical, beginner-friendly guide to building user interfaces with the most popular JavaScript library on the web.
React is a JavaScript library for building user interfaces out of small, reusable pieces called components.
Instead of manually updating the page when data changes, you describe what the UI should look like for a given state, and React efficiently updates the DOM to match. This is called a declarative approach.
The fastest modern way to start a React app is with Vite. You'll need Node.js installed first.
# Create a new React project
npm create vite@latest my-app -- --template react
# Move in, install dependencies, and start the dev server
cd my-app
npm install
npm run dev
Open the URL it prints (usually http://localhost:5173) and you'll see your app with hot reloading — edits appear instantly.
A component is just a JavaScript function that returns markup. That markup is written in JSX — an HTML-like syntax inside JavaScript.
function Welcome() {
return <h1>Hello, world!</h1>;
}
export default Welcome;
You then use it like an HTML tag. Component names must start with a capital letter.
function App() {
return (
<div>
<Welcome />
<p>Welcome to my app.</p>
</div>
);
}
<div> or <>...</> fragment), use className instead of class, and close every tag.Props let a parent pass data down to a child component, like HTML attributes.
function Greeting({ name }) {
return <h2>Hi, {name}!</h2>;
}
// Used like this:
<Greeting name="Hammad" />
The curly braces { } let you drop any JavaScript value into your JSX. Props are read-only — a component never modifies its own props.
When a component needs to remember something that changes over time (a counter, form input, toggle), use the useState Hook.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
useState returns a pair: the current value, and a function to update it. Calling that setter tells React to re-render the component with the new value.
count++). Always use the setter function so React knows to re-render.Attach handlers with camelCase props like onClick, onChange, and onSubmit. Pass a function, not a function call.
function NameForm() {
const [text, setText] = useState("");
return (
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type here..."
/>
);
}
This is a controlled input: React state is the single source of truth for the value.
Turn an array into elements with .map(). Give each item a stable key so React can track them.
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
Use useEffect for things outside React's render flow: fetching data, subscriptions, or timers.
import { useState, useEffect } from "react";
function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("https://api.example.com/users")
.then((res) => res.json())
.then(setUsers);
}, []); // empty array = run once on mount
return <p>{users.length} users loaded</p>;
}
The second argument is the dependency array. It controls when the effect re-runs: empty means once, or list the values it depends on.