ai react tutorial 2026-08-30
write a react js tutorial
ai answer
React JS Tutorial (2026)
A practical beginner-to-intermediate guide. You will set up a modern React app with Vite, learn components, props, state, effects, and finish with a small task tracker you can run locally.
Official docs: https://react.dev
1. What React is
React is a JavaScript library for building user interfaces. You describe the UI as components (functions that return markup). When data changes, React updates only the parts of the page that need to change.
Core ideas:
- Component — a reusable piece of UI
- JSX — HTML-like syntax inside JavaScript
- Props — data passed into a component
- State — data a component remembers and can change
- Hooks — functions like
useStateanduseEffectthat add features to components
React does not include routing, data fetching, or a backend. For those you add libraries or use a framework such as Next.js. This tutorial stays on client-side React so you learn the core first.
2. Prerequisites
- Basic JavaScript: functions, arrays, objects,
map, arrow functions - Some HTML and CSS
- https://nodejs.org LTS installed (needed for npm and the Vite dev server)
Check your install:
node -v npm -v
3. Create a project (Vite, not Create React App)
Create React App is deprecated. Use Vite.
npm create vite@latest my-react-app -- --template react cd my-react-app npm install npm run dev
Open the URL Vite prints (usually http://localhost:5173).
Useful scripts:
| Command | What it does |
|---|---|
npm run dev |
Start the development server with hot reload |
npm run build |
Production build into dist/ |
npm run preview |
Preview the production build locally |
JavaScript template is used here so beginners can focus on React, not TypeScript. You can later use --template react-ts.
4. Project structure
A typical Vite + React app looks like this:
my-react-app/ ├── index.html # page shell; Vite injects your JS here ├── package.json ├── vite.config.js └── src/ ├── main.jsx # mounts React onto the page ├── App.jsx # root component ├── App.css └── index.css
src/main.jsx is the entry point:
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> );
createRoot attaches React to the <div id="root"> in index.html. StrictMode helps catch mistakes in development; it does not affect production.
5. JSX
JSX looks like HTML, but it is JavaScript. A few rules:
- Use
classNameinstead ofclass - Use
htmlForinstead offor - Tags must be closed:
<img />,<input /> - You can embed JS with
{ } - A component must return one parent (or a fragment
<>...</>)
function Greeting() { const name = "Ada"; const year = new Date().getFullYear(); return ( <> <h1 className="title">Hello, {name}</h1> <p>The year is {year}.</p> </> ); }
JSX expressions must produce a value. You can use ternary operators and &&, but not raw if statements inside { }.
6. Components
A component is a function whose name starts with a capital letter and returns JSX.
function Header() { return ( <header> <h1>Task Tracker</h1> </header> ); } export default function App() { return ( <div> <Header /> <p>Welcome.</p> </div> ); }
Put reusable pieces in their own files and export them:
// src/Header.jsx export default function Header() { return <h1>Task Tracker</h1>; }
// src/App.jsx import Header from "./Header.jsx"; export default function App() { return <Header />; }
7. Props
Props are arguments passed to a component, like HTML attributes.
function UserCard({ name, role }) { return ( <article> <h2>{name}</h2> <p>{role}</p> </article> ); } export default function App() { return ( <> <UserCard name="Ada Lovelace" role="Mathematician" /> <UserCard name="Grace Hopper" role="Computer scientist" /> </> ); }
Notes:
- Props are read-only. Do not mutate them.
- You can pass any JS value: strings, numbers, arrays, objects, functions, even other JSX.
- Default values work like normal function parameters:
function Button({ label = "Click me" }) { return <button>{label}</button>; }
8. State with useState
State is data that belongs to a component and can change over time. Changing state triggers a re-render.
import { useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times.</p> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={() => setCount(0)}>Reset</button> </div> ); }
Rules:
useState(initialValue)returns[currentValue, setter]- Always update with the setter, never by assigning (
count++will not work) - If the next value depends on the previous one, use the functional form:
setCount((previous) => previous + 1);
That avoids stale values when updates happen quickly.
9. Events
React events use camelCase: onClick, onChange, onSubmit.
function SearchBox() { const [query, setQuery] = useState(""); function handleChange(event) { setQuery(event.target.value); } function handleSubmit(event) { event.preventDefault(); alert(`Searching for: ${query}`); } return ( <form onSubmit={handleSubmit}> <input value={query} onChange={handleChange} placeholder="Search" /> <button type="submit">Go</button> </form> ); }
event.preventDefault() stops the browser from reloading the page on form submit.
10. Conditional rendering
function Inbox({ messages }) { if (messages.length === 0) { return <p>No messages.</p>; } return ( <p> You have {messages.length} {messages.length === 1 ? "message" : "messages"}. </p> ); }
Common patterns:
{isLoading && <p>Loading…</p>} {error ? <p>{error}</p> : <List items={items} />}
Do not render 0 by accident. {count && <Badge />} will show 0 when count is 0. Prefer {count > 0 && <Badge />}.
11. Lists and keys
Render arrays with .map(). Give each item a stable unique key.
const people = [ { id: "a1", name: "Ada" }, { id: "b2", name: "Grace" }, ]; function PeopleList() { return ( <ul> {people.map((person) => ( <li key={person.id}>{person.name}</li> ))} </ul> ); }
Keys help React match items when the list changes. Do not use the array index as a key if items can be reordered, inserted, or deleted.
12. Effects with useEffect
Use useEffect when a component needs to talk to something outside React: the browser, a timer, localStorage, or a network request.
import { useEffect, useState } from "react"; function WindowWidth() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { function handleResize() { setWidth(window.innerWidth); } window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); return <p>Window width: {width}px</p>; }
- The function you return is the cleanup. It runs when the component unmounts or before the effect runs again.
- The dependency array controls when the effect re-runs:
[]— once, after the first render[roomId]— wheneverroomIdchanges- omitted — after every render (usually a mistake)
Fetching example:
function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { let cancelled = false; fetch(`https://jsonplaceholder.typicode.com/users/${userId}`) .then((res) => res.json()) .then((data) => { if (!cancelled) setUser(data); }); return () => { cancelled = true; }; }, [userId]); if (!user) return <p>Loading…</p>; return <h2>{user.name}</h2>; }
The cancelled flag avoids updating state after the component unmounts or after userId changes mid-request.
13. Rules of Hooks
- Only call Hooks at the top level of a component or custom Hook — not inside loops, conditions, or nested functions.
- Only call Hooks from React functions (components or custom Hooks).
// Bad if (loggedIn) { const [name, setName] = useState(""); } // Good const [name, setName] = useState(""); if (loggedIn) { // use name here }
14. Lifting state up
If two children need the same data, put the state in their closest shared parent and pass it down as props.
function FilterableList() { const [query, setQuery] = useState(""); const items = ["React", "Vite", "JavaScript"]; const visible = items.filter((item) => item.toLowerCase().includes(query.toLowerCase()) ); return ( <div> <SearchInput value={query} onChange={setQuery} /> <Results items={visible} /> </div> ); } function SearchInput({ value, onChange }) { return ( <input value={value} onChange={(e) => onChange(e.target.value)} placeholder="Filter" /> ); } function Results({ items }) { return ( <ul> {items.map((item) => ( <li key={item}>{item}</li> ))} </ul> ); }
This is the standard React data flow: state down, events up.
15. Mini project: Task Tracker
Replace src/App.jsx with the following. You can keep Vite’s CSS or use the styles at the end.
import { useEffect, useState } from "react"; import "./App.css"; const STORAGE_KEY = "react-tutorial-tasks"; function uid() { return crypto.randomUUID(); } export default function App() { const [tasks, setTasks] = useState(() => { const saved = localStorage.getItem(STORAGE_KEY); return saved ? JSON.parse(saved) : [ { id: uid(), text: "Learn JSX", done: true }, { id: uid(), text: "Build a component", done: false }, ]; }); const [text, setText] = useState(""); const [filter, setFilter] = useState("all"); useEffect(() => { localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks)); }, [tasks]); function addTask(event) { event.preventDefault(); const trimmed = text.trim(); if (!trimmed) return; setTasks((current) => [ { id: uid(), text: trimmed, done: false }, ...current, ]); setText(""); } function toggleTask(id) { setTasks((current) => current.map((task) => task.id === id ? { ...task, done: !task.done } : task ) ); } function deleteTask(id) { setTasks((current) => current.filter((task) => task.id !== id)); } const visible = tasks.filter((task) => { if (filter === "active") return !task.done; if (filter === "done") return task.done; return true; }); const remaining = tasks.filter((task) => !task.done).length; return ( <main className="app"> <h1>Task Tracker</h1> <p className="muted">{remaining} task{remaining === 1 ? "" : "s"} left</p> <form onSubmit={addTask} className="row"> <input value={text} onChange={(e) => setText(e.target.value)} placeholder="Add a task" /> <button type="submit">Add</button> </form> <div className="filters"> {["all", "active", "done"].map((value) => ( <button key={value} type="button" className={filter === value ? "active" : ""} onClick={() => setFilter(value)} > {value} </button> ))} </div> {visible.length === 0 ? ( <p className="muted">Nothing here.</p> ) : ( <ul className="list"> {visible.map((task) => ( <li key={task.id} className={task.done ? "done" : ""}> <label> <input type="checkbox" checked={task.done} onChange={() => toggleTask(task.id)} /> <span>{task.text}</span> </label> <button type="button" onClick={() => deleteTask(task.id)}> Delete </button> </li> ))} </ul> )} </main> ); }
Optional styles for src/App.css:
.app { max-width: 28rem; margin: 3rem auto; font-family: system-ui, sans-serif; } .muted { color: #666; } .row { display: flex; gap: 0.5rem; } .row input { flex: 1; padding: 0.5rem; } .filters { display: flex; gap: 0.5rem; margin: 1rem 0; } .filters .active { font-weight: 700; } .list { list-style: none; padding: 0; } .list li { display: flex; justify-content: space-between; align-items: center; padding: 0.4rem 0; border-bottom: 1px solid #eee; } .done span { text-decoration: line-through; color: #888; }
What this app practices:
useStatefor tasks, input text, and filter- Lazy initial state from
localStorage useEffectto persist changes- Controlled inputs
- Immutable updates with
map/filter - Keys, conditional rendering, and event handlers
16. How React rendering works (short version)
- A component function runs and returns JSX.
- React compares that tree with the previous one (reconciliation).
- It updates the DOM only where something actually changed.
That is why you treat state as immutable: React needs to see a new array or object to know something changed.
// Bad — mutates the same array tasks.push(newTask); setTasks(tasks); // Good — new array setTasks([...tasks, newTask]);
17. Common mistakes
| Mistake | Fix |
|---|---|
| Using Create React App for a new project | Use Vite or a framework |
| Mutating state | Copy arrays/objects, then set state |
Missing key on lists |
Use a stable id |
Calling Hooks inside if |
Move Hooks to the top |
Forgetting preventDefault on forms |
The page will reload |
| Fetching without cleanup | Ignore or abort stale responses |
| Using index as a key on editable lists | Items get the wrong state when reordered |
18. What to learn next
Once this feels comfortable:
- Custom Hooks — extract repeated logic (
useLocalStorage,useFetch) - React Router — multiple pages in a SPA
- Context — share data without prop-drilling (theme, auth)
- Forms at scale —
useActionStatein React 19, or a library like React Hook Form - A framework — Next.js or React Router’s framework mode when you need routing, data loading, and deployment conventions
- TypeScript — catch prop and state mistakes at compile time
Start here: https://react.dev/learn
If you want a follow-up, I can turn this into a TypeScript version, add React Router pages, or expand the task tracker into a multi-file app with components split out.