How does declarative mode work, and what does it offer?
First, we wrap
BrowserRouteraround our app, which connects your React app to the browser's address bar so you can change pages without reloading
which gives us the true feel of a Single Page Application.Next, we make routes and subroutes according to our choice.
<Routes>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route element={<AuthLayout />}>
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
</Route>
<Route path="concerts">
<Route index element={<ConcertsHome />} />
<Route path=":city" element={<City />} />
<Route path="trending" element={<Trending />} />
</Route>
</Routes>
- If a path segment starts with
:then it becomes a "dynamic segment". When the route matches the URL, the dynamic segment will be parsed from the URL and provided as params to other router APIs likeuseParams.
<Route path="teams/:teamId" element={<Team />} />
Why was it not enough?
In Declarative Mode, the router acts strictly as a UI switcher
—it simply determines which component tree to render based on the current URL. The fundamental flaw is that data fetching was coupled to the React component lifecycle:
- The Network Waterfall: React Router had to mount a parent component before its useEffect or React Query hook could fire. If a layout had nested child routes, the parent rendered, fetched its data, finished loading, rendered the child, and only then could the child begin fetching its own data.
- Loading State Jitter & Layout Shifts: Every level of a nested route managed its own loading, error, and empty states. This caused cascading layout shifts (CLS) where users saw nested spinners popping in sequentially.
- No Synchronization for Mutations: Submitting forms or mutating data meant manually managing pending states and manually triggering cache invalidation across every affected component to keep the UI in sync.
- No Route Pre-fetching or Parallelization: The router couldn't know what data a page needed until the browser downloaded the JavaScript bundle, executed the code, and evaluated the component tree.
In the next part we will see how data mode fixed it ->
Top comments (1)
How does data mode handle a child loader that needs a value returned by its parent loader?