What React is
React is a library for user interfaces. You describe what the screen should look like for a given state. React updates the DOM. It is not a backend, not a router, not a CSS framework — which is why it pairs with Next.js, Tailwind, and a Node API instead of replacing them.
Mental model
UI is a function of state. UI = f(state). When state changes, React re-renders the component, diffs the result, and patches the DOM. You do not document.querySelector a button to change its label — you set state and let the tree catch up.
- Props flow down. Events (and setters) flow up.
- State lives in the component that owns it, or in a context/store if many need it.
- Re-renders are cheap when components are small and keys are stable.
- The React 19 compiler can memoize for you — still write simple components.
JSX
JSX looks like HTML in JavaScript. It compiles to React.createElement (or the automatic runtime). In JSX, className not class, htmlFor not for, and expressions live in { }.
export function Hello({ name }: { name: string }) {
return (
<h1 className="text-xl font-semibold">
Hello, {name}!
</h1>
);
}- One parent (or a fragment
<>...</>) per return. - Self-close void tags:
<img />,<br />. - Comments:
{/* like this */}. - Booleans and
nullrender nothing — useful for{isOn && <Badge />.
Components and props
A component is a function that returns JSX. Props are its arguments. They are read-only. If a child needs to change something, the parent passes a callback.
type CardProps = {
title: string;
children: React.ReactNode;
};
export function Card({ title, children }: CardProps) {
return (
<article className="rounded-xl border border-border p-4">
<h2 className="font-semibold">{title}</h2>
<div className="mt-2 text-sm text-muted-foreground">{children}</div>
</article>
);
}State with useState
useState holds values that change because of user input or time. Updating state queues a re-render. Do not mutate arrays/objects in place — replace them.
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount((n) => n + 1)}>
Count: {count}
</button>
);
}- Use the functional updater
setCount((n) => n + 1)when the next value depends on the previous. - State is per instance. Two
<Counter />s do not share a count. - Lift state up when two siblings need the same value.
Events
React events are synthetic but feel like DOM events: onClick, onChange, onSubmit. Always preventDefault on forms if you handle submit in JavaScript.
"use client";
import { useState } from "react";
export function SearchField({ onSearch }: { onSearch: (q: string) => void }) {
const [q, setQ] = useState("");
return (
<form
onSubmit={(e) => {
e.preventDefault();
onSearch(q.trim());
}}
>
<input
value={q}
onChange={(e) => setQ(e.target.value)}
placeholder="Search notes"
/>
</form>
);
}Lists and keys
Render arrays with .map. Keys tell React which item is which across renders. Use a stable id (_id, slug), never the array index if the list can reorder or delete.
export function NoteList({
notes,
}: {
notes: { _id: string; title: string }[];
}) {
return (
<ul>
{notes.map((note) => (
<li key={note._id}>{note.title}</li>
))}
</ul>
);
}Effects with useEffect
useEffect synchronizes with something outside React: a subscription, a non-React widget, document.title. It is not the place to fetch for first paint in Next.js — fetch in a Server Component instead.
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);Rules of hooks
- Only call hooks at the top of a function component or custom hook.
- Never call hooks inside loops, conditions, or nested functions.
- Custom hooks start with
useso lint can see them (useNotes,useDebounced). - The same component must call the same hooks in the same order every render.
Custom hooks
When two components share stateful logic, extract a hook — not a mega-component. Return the minimum the UI needs.
"use client";
import { useState } from "react";
export function useCopied(ms = 1600) {
const [copied, setCopied] = useState(false);
async function copy(text: string) {
await navigator.clipboard.writeText(text);
setCopied(true);
window.setTimeout(() => setCopied(false), ms);
}
return { copied, copy };
}Context
Context avoids prop drilling for values many components need: theme, locale, current user. Do not put high-frequency state (mouse position, keystrokes) in context — every consumer re-renders.
"use client";
import { createContext, useContext } from "react";
const LocaleContext = createContext<"en" | "bn">("en");
export function useLocaleCode() {
return useContext(LocaleContext);
}Forms
For anything that writes to the server, share a Zod schema between the client form and the server action. ReactBD uses react-hook-form + zodResolver so the same rules run in the browser and on the server.
import { z } from "zod";
export const noteSchema = z.object({
title: z.string().trim().min(1).max(120),
});
export type NoteInput = z.infer<typeof noteSchema>;Composition over inheritance
React does not use class inheritance for UI. Pass children, slots, or render props. A Card that accepts children outlives a UserCard / ProductCard class tree.
children— the default slot.- Named props (
header,footer) — extra slots. - Compound components (
Select+Select.Item) — when pieces must share state. - Avoid HOCs for new code; hooks replaced most of them.
Server Components vs client leaves
In Next.js App Router, files are Server Components by default. They can be async, they can read env, they never ship their code to the browser. Add a use-client directive only when you need state, effects, or browser APIs.
| Stay on the server | Make a client leaf |
|---|---|
| Fetch Mongo / Sanity / API | useState, useEffect |
| Render markdown, lists, layouts | onClick, onChange |
| Build metadata and JSON-LD | clipboard, dialogs, menus |