4. React Fundamentals
React Basics
4.1.1 What is React?
React is a declarative, component-based JavaScript library for building user interfaces. It minimizes direct DOM manipulations through a Virtual DOM diffing algorithm, ensuring efficient updates. Data flows in a unidirectional manner — from parent to child via props — making state changes predictable and easier to debug.
Example:
function App() { return <div><Header /><Main /><Footer /></div>; }
The above snippet composes the UI from reusable pieces: Header, Main, and Footer. Starting with React 18, concurrent features such as automatic batching and transitions improve responsiveness by allowing React to prepare multiple updates without blocking the main thread.
4.1.2 JSX
JSX (JavaScript XML) lets you write HTML‑like syntax directly in JavaScript. Under the hood, JSX is transpiled to React.createElement calls.
Basic syntax:
const element = <h1>Hello</h1>; // becomes React.createElement('h1', null, 'Hello')
Expressions are embedded using curly braces:
<div>{user.name}</div>
Fragments allow grouping without extra DOM nodes:
<><ChildA /><ChildB /></>or<React.Fragment><ChildA /><ChildB /></React.Fragment>
Dynamic attributes and event handlers:
<button className={isActive ? 'btn-active' : 'btn'} onClick={handleClick}>Click</button>
4.1.3 Components
Components are the building blocks of React UI. Function components are the preferred way due to their simplicity and compatibility with hooks.
Function component:
function Welcome({ name }) { return <h1>Hello, {name}</h1>; }
Class component (legacy):
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
Reusable button with defaults:
const Button = ({ children, onClick, variant = 'primary' }) => <button className={`btn btn-${variant}`} onClick={onClick}>{children}</button>;
4.1.4 Props
Props are read‑only inputs that flow from parent to child. They enable component composition and reuse.
- Destructuring:
function Card({ title, children }) - Default props:
Card.defaultProps = { title: 'Default' }or using default parameters. - Children prop:
props.childrenlets a component receive arbitrary JSX.
Example with children:
<Modal title="Confirm" onClose={close}><p>Are you sure?</p></Modal>
Validation can be done with PropTypes or TypeScript interfaces to catch mistakes early.
4.1.5 State
Local component state is managed with the useState hook. State updates are asynchronous and may be batched for performance.
Basic usage:
const [count, setCount] = useState(0);
Functional updates ensure you work with the latest state:
setCount(c => c + 1);
Object state example:
const [form, setForm] = useState({ email: '', password: '' });// Update while preserving other fieldssetForm(prev => ({ ...prev, email: 'new@email.com' }));
4.1.6 Event Handling
React uses SyntheticEvents that normalize browser differences. Handlers receive an event object; calling e.preventDefault() stops default browser actions.
Controlled input pattern:
<form onSubmit={e => { e.preventDefault(); submitForm(formData); }}>
<input onChange={e => setValue(e.target.value)} />
</form>
React Hooks
4.2.1 useState
The useState hook returns a state variable and a setter function. Lazy initialization avoids expensive computations on every render.
Signature:
const [state, setState] = useState(initialValue);
Lazy initialization:
useState(() => expensiveComputation())
Example – persisting todos:
const [todos, setTodos] = useState<Todo[]>(() => {
const saved = localStorage.getItem('todos');
return saved ? JSON.parse(saved) : [];
});
4.2.2 useEffect
useEffect lets you perform side effects such as data fetching, subscriptions, or manual DOM changes. The cleanup function prevents memory leaks.
Signature:
useEffect(() => { ... }, [deps])
- No dependency array → runs after every render.
- Empty array → runs once on mount and cleanup on unmount.
- Specific values → runs when any of those values change.
Subscription example:
useEffect(() => {
const sub = api.subscribe(id, handleData);
return () => sub.unsubscribe(); // cleanup
}, [id]);
Document title side effect:
useEffect(() => { document.title = `Count: ${count}`; }, [count]);
4.2.3 useRef
useRef returns a mutable object whose .current property persists across renders without triggering re‑renders. It is commonly used to access DOM nodes or store previous values.
Signature:
const ref = useRef(initialValue);
Accessing an input:
const inputRef = useRef<HTMLInputElement>(null); const focus = () => inputRef.current?.focus();<input ref={inputRef} />
Previous value pattern:
const prevCount = useRef(count);
useEffect(() => { prevCount.current = count; });
4.2.4 useMemo
useMemo memoizes the result of an expensive calculation, recomputing only when its dependencies change. This prevents unnecessary work on every render.
Signature:
const memoized = useMemo(() => computeExpensive(a, b), [a, b]);
Example – sorted list:
const sortedUsers = useMemo(() => users.slice().sort((a, b) => a.name.localeCompare(b.name)), [users]);
Stable style object:
const style = useMemo(() => ({ width: `${progress}%` }), [progress]);
4.2.5 useCallback
useCallback returns a memoized version of a callback function that only changes when its dependencies change. This is useful when passing callbacks to memoized child components to prevent unnecessary re‑renders.
Signature:
const fn = useCallback(() => { doSomething(a); }, [a]);
Example – stable click handler for list items:
const handleClick = useCallback((id: string) => {
analytics.track('click', { id });
navigate(`/item/${id}`);
}, [navigate]);
Combine with React.memo on the child:
const ListItem = React.memo(({ id, onClick }) => <li onClick={() => onClick(id)}>Item {id}</li>);
4.2.6 useContext
useContext consumes a value from a React Context, causing the component to re‑render when the provider’s value changes. It eliminates prop drilling for global‑like data such as themes, authentication, or locale.
Signature:
const value = useContext(MyContext);
Example – thematic button:
const ThemeContext = createContext<Theme>('light');function ThemedButton() {const theme = useContext(ThemeContext);return <button className={theme}>...</button>;}
4.2.7 Custom Hooks
Custom hooks allow you to extract reusable stateful logic into a function whose name starts with “use”. They can call other hooks and encapsulate side effects.
Example – useLocalStorage:
function useLocalStorage<T>(key, initial) {const [value, setValue] = useState(() => {const stored = localStorage.getItem(key);return stored ? JSON.parse(stored) : initial;});useEffect(() => {localStorage.setItem(key, JSON.stringify(value));}, [key, value]);return [value, setValue];}
Example – useDebounce:
function useDebounce<T>(value: T, delay: number) {const [debounced, setDebounced] = useState(value);useEffect(() => {const t = setTimeout(() => setDebounced(value), delay);return () => clearTimeout(t);}, [value, delay]);return debounced;}
Component Architecture & Form Handling
Beyond the basics, scalable React applications benefit from a clear component hierarchy: presentational (UI‑only) vs. container (data‑fetching) components, or the newer approach of separating concerns with hooks and feature‑sliced design.
Form handling is streamlined with libraries like React Hook Form, which leverages uncontrolled inputs and minimizes re‑renders.
Basic React Hook Form usage:
import { useForm } from 'react-hook-form';function Login() {const { register, handleSubmit, errors } = useForm();const onSubmit = data => console.log(data);return (<form onSubmit={handleSubmit(onSubmit)}><input {...register('email', { required: true, pattern: /^\S+@\S+$/i })} />{errors.email && <span>Invalid email</span>}<input {...register('password', { required: true, minLength: 6 })} /><button type="submit">Login</button></form>);}
Summary
This chapter covered the essential building blocks of React: JSX syntax, component creation, props and state management, event handling, and the powerful Hooks API. Mastery of these concepts enables developers to build efficient, maintainable, and scalable user interfaces, setting the foundation for advanced topics such as performance optimization, server‑side rendering with Next.js, and state management libraries.