The UI library

React

Components, JSX, props, state, hooks, and composition — the patterns every ReactBD product starts with, including when to stay on the server.

JSXComponentsHooksStateComposition

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 { }.

hello.tsx
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 null render 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.

card.tsx
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.

counter.tsx
"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.

search-field.tsx
"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.

note-list.tsx
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.

use-effect.ts
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 use so 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-copied.ts
"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.

locale-context.tsx
"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.

note.schema.ts
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 serverMake a client leaf
Fetch Mongo / Sanity / APIuseState, useEffect
Render markdown, lists, layoutsonClick, onChange
Build metadata and JSON-LDclipboard, dialogs, menus

Next steps

Related

Part of the ReactBD MERN documentation. Static content, rendered on the server.