Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
From React 18 to 19.3: Concurrent Rendering, Actions, use(), ref as Prop, Activity, useEffectEvent, ViewTransition and Fragment Refs
React 18 laid the base for concurrent rendering (createRoot, batching, transitions), 19 brought Actions, use() and ref as a prop, 19.2 Activity and useEffectEvent, and 19.3 stable ViewTransition and Fragment refs. We add ViewTransition and a Fragment ref to Bookstore, apply React Router 8.5's deprecation, and upgrade a small React 18 project to 19.3 with the 18.3 warnings, the TypeScript codemod and manual fixes.
A Brief Look at Next.js: Server Components, the App Router, Server Actions, Cache Components and When an SPA Is Enough
Next.js runs React on the server: Server Components await data directly and their code never reaches the browser, Cache Components let you choose what is cached with 'use cache' and cacheLife, and every Server Function is a public endpoint that needs validation and authorization. We serve Bookstore's book details as a Partial Prerender with Next.js 16.4, call a Server Function directly with curl, and discuss when an SPA is enough.
Finishing the Bookstore SPA End to End: Feature Folders, Authentication, Environments and Deployment
Getting a React SPA ready for production means guarding feature boundaries with a lint rule, knowing that VITE_ variables are written into the bundle at build time so secrets stay out of the browser, and handing the session to a BFF that uses an HttpOnly cookie instead of a token. We add a small ASP.NET Core and YARP BFF to Bookstore and show the sign-in flow, the SPA fallback, cache headers, an nginx config and bundle analysis with real output.
Testing React: Vitest, React Testing Library and Mocking APIs with MSW
The practical way to test React is to run React Testing Library, which queries the UI as a user does, on Vitest, which reuses the Vite config, and to fake the API at the network level with MSW without touching the code. In Bookstore we test the pure applyChange function, useDebouncedValue and useCart with renderHook, BookCard with user-event, and the catalog and checkout screens through the real route tree in a memory router, with real failures for getBy vs findBy and leaking store state.
Performance in React: Understanding Re-renders, memo, useMemo, useCallback, the React Compiler and the Profiler
Reducing re-renders only helps when there is a measured cost, and often the real cost is the browser's DOM work rather than React. On Bookstore's 5,000-row stock page we measure memo and useCallback cutting row renders from 5,000 to 1, an inline callback undoing that, the React Compiler halving render time while the click stays at 400 ms, and virtualization bringing it down to 40 ms; plus the effect of useDeferredValue and Activity.
Global State in React: The Limits of Context, Zustand and When to Use Redux Toolkit
Most "global state" is really server, URL or form state; for the small client state that is truly global, Zustand's store with selectors fixes Context's render-every-reader problem, while Redux Toolkit comes in when you need an action log and rules for a large team. In Bookstore we move the recently viewed books and the cart id into a persisted store, and show reading without selectors causing 13 renders instead of 1 and a selector returning a new object looping forever in Zustand 5.
Forms in React: Controlled Inputs, Form Actions, useActionState, useFormStatus and react-hook-form + zod
React 19's form actions (useActionState, useFormStatus, useOptimistic) are enough for small forms; for forms with line items and nested data, react-hook-form with a zod schema means less code and fewer renders. On the Bookstore order form we show the controlled version rendering on every key, an action resetting the form after a validation error, useOptimistic reverting by itself on failure, and Problem Details errors mapped to fields with focus moved to them.
Routing with React Router 8: Routes, Nested Layouts, URL Params, Loaders/Actions and Lazy Routes
In React Router 8's data mode, routes are passed to createBrowserRouter as objects; layout routes and Outlet give nested pages, loaders fetch data before entering a route, middleware acts as a guard, actions handle forms and lazy enables code splitting. We split Bookstore into URLs, move the search into the query string, and show state leaking to the next book without a key, a lazy chunk downloading before a middleware redirect, and why a static server needs an SPA fallback.
Data Fetching in React: From fetch + Effects to TanStack Query, Suspense and use()
Fetching in an effect gives you no cache, deduplication, retries or invalidation; TanStack Query gathers them around the query key. We move the Bookstore catalog and the server-side cart to TanStack Query and measure a revisited search coming from the cache, an optimistic update rolled back on a 500, a 409 Problem Details shown to the user, Suspense's 300 ms reveal window, and a promise created during render sending 3,739 requests through use() in three seconds.
useRef, Context and Custom Hooks: DOM Access, Context Instead of DI and the Rules of Hooks
useRef is a box that does not trigger renders (a DOM node or a timer id), while Context carries a value deep into the tree without props and re-renders every component that reads it when it changes. We move the Bookstore cart, theme and user into Context, measure a version where a theme change renders 28 components instead of 2, and show an inline ref callback stealing focus, a conditional hook crashing the app and the search split into custom hooks.
Using useEffect Correctly: Synchronization, Cleanup, Dependency Arrays and "You Might Not Need an Effect"
useEffect is not a lifecycle hook but a tool for synchronizing a component with an external system: every setup has a cleanup, and the dependency array lists the reactive values the effect reads. We connect the Bookstore catalog to API search and show with real network records that debounce and AbortController send one request even under StrictMode, that a late response overwrites the list without cleanup, and that useEffectEvent avoids a needless request.
Where Should State Live? Lifting State Up, Controlled Inputs, Derived State and useReducer
State should live in the closest common ancestor of the components that need it; computable values stay out of state, you store an identity instead of a copy, and state that must reset on a prop change gets a key. We lift Bookstore's cart into App, turn it into a type-safe reducer with useReducer and a discriminated union, and show with real output that a separately kept total goes wrong on remove and that a details panel without a key carries a note to another book.