Skip to content

Commit 53c6ce2

Browse files
committed
chore(docs): added notes about regex paths
1 parent 4a46ede commit 53c6ce2

File tree

1 file changed

+166
-0
lines changed

1 file changed

+166
-0
lines changed

docs/getting-started/faq.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,169 @@ function MatchPath({ path, Comp }) {
193193
// Will match anywhere w/o needing to be in a `<Routes>`
194194
<MatchPath path="/accounts/:id" Comp={Account} />;
195195
```
196+
197+
## What Happened to Regexp Routes Paths?
198+
199+
Regexp route paths were removed for two reasons:
200+
201+
1. Regular expression paths in routes raised a lot of questions for v6's ranked route matching. How do you rank a regex?
202+
203+
2. We were able to shed an entire dependency (path-to-regexp) and cut the package weight sent to your user's browser significantly. If it were added back, it would represent 1/3 of React Router's page weight!
204+
205+
After looking at a lot of use cases, we found we can still meet them without direct regexp path support, so we made the tradeoff to significantly decrease the bundle size and avoid the open questions around ranking regexp routes.
206+
207+
The majority of regexp routes were only concerned about one URL segment at a time and doing one of two things:
208+
209+
1. Matching multiple static values
210+
2. Validating the param in some way (is a number, not a number, etc.)
211+
212+
**Matching generally static values**
213+
214+
A very common route we've seen is a regex matching multiple language codes:
215+
216+
```tsx filename=v5-regex-route.js
217+
function App() {
218+
return (
219+
<Switch>
220+
<Route path={/(en|es|fr)/} component={Lang} />
221+
</Switch>
222+
);
223+
}
224+
225+
function Lang({ params }) {
226+
let lang = params[0];
227+
let translations = I81n[lang];
228+
// ...
229+
}
230+
```
231+
232+
These are all actually just static paths, so in v6 you can make three routes and pass the code directly to the component. If you've got a lot of them, make an array and map it into routes to avoid the repetition.
233+
234+
```tsx filename=v6.js
235+
function App() {
236+
return (
237+
<Routes>
238+
<Route path="en" element={<Lang code="en" />} />
239+
<Route path="es" element={<Lang code="en" />} />
240+
<Route path="fr" element={<Lang code="en" />} />
241+
</Routes>
242+
);
243+
}
244+
245+
function Lang({ lang }) {
246+
let translations = I81n[lang];
247+
// ...
248+
}
249+
```
250+
251+
**Doing some sort of param validation**
252+
253+
Another common case was ensuring that parameters were an integer.
254+
255+
```tsx filename=v5-regex-route.js
256+
function App() {
257+
return (
258+
<Switch>
259+
<Route path={/users\/(\d+)/} component={User} />
260+
</Switch>
261+
);
262+
}
263+
264+
function User({ params }) {
265+
let id = params[0];
266+
// ...
267+
}
268+
```
269+
270+
In this case you have to do a bit of work yourself with the regex inside the matching component:
271+
272+
```tsx filename=v5-regex-route.js
273+
function App() {
274+
return (
275+
<Routes>
276+
<Route path="users/:id" element={<ValidateUser />} />
277+
<Route path="/users/*" component={NotFound} />
278+
</Routes>
279+
);
280+
}
281+
282+
function ValidateUser() {
283+
let params = useParams();
284+
let userId = params.id.match(/\d+/);
285+
if (!userId) {
286+
return <NotFound />;
287+
}
288+
return <User id={params.userId} />;
289+
}
290+
291+
function User(props) {
292+
let id = props.id;
293+
// ...
294+
}
295+
```
296+
297+
In v5 if the regex didn't match then `<Switch>` would keep trying to match the next routes:
298+
299+
```tsx filename=v5-switch.js
300+
function App() {
301+
return (
302+
<Switch>
303+
<Route path={/users\/(\d+)/} component={User} />
304+
<Route path="/users/new" exact component={NewUser} />
305+
<Route
306+
path="/users/inactive"
307+
exact
308+
component={InactiveUsers}
309+
/>
310+
<Route path="/users/*" component={NotFound} />
311+
</Switch>
312+
);
313+
}
314+
```
315+
316+
Looking at this example you might be concerned that in the v6 version your other routes won't get rendered at their URLs because the `:userId` route might match first. But, thanks to route ranking, that is not the case. The "new" and "inactive" routes will rank higher and therefore render at their respective URLs:
317+
318+
```tsx filename=v6-ranked.js
319+
function App() {
320+
return (
321+
<Routes>
322+
<Route path="/users/:id" element={<ValidateUser />} />
323+
<Route path="/users/new" element={<NewUser />} />
324+
<Route
325+
path="/users/inactive"
326+
element={<InactiveUsers />}
327+
/>
328+
</Routes>
329+
);
330+
}
331+
```
332+
333+
In fact, the v5 version has all sorts of problems if your routes aren't ordered _just right_. V6 competely eliminates this problem.
334+
335+
**Remix Users**
336+
337+
If you're using [Remix](https://remix.run), you can send proper 40x responses to the browser by moving this work into your loader. This also decreases the size of the browser bundles sent to the user because loaders only run on the server.
338+
339+
```tsx
340+
import { useLoaderData } from "remix";
341+
342+
export async function loader({ params }) {
343+
if (!params.id.match(/\d+/)) {
344+
throw new Response("", { status: 400 });
345+
}
346+
347+
let user = await fakeDb.user.find({ where: { id: params.id=}})
348+
if (!user) {
349+
throw new Response("", { status: 404})
350+
}
351+
352+
return user;
353+
}
354+
355+
function User() {
356+
let user = useLoaderData();
357+
// ...
358+
}
359+
```
360+
361+
Instead of rending your component, remix will render the nearest [catch boundary](https://docs.remix.run/v0.20/api/app/#catchboundary) instead.

0 commit comments

Comments
 (0)