Skip to content

Commit 35f98b4

Browse files
committed
feat: adding route documentation with basic example
1 parent 05a78f2 commit 35f98b4

File tree

3 files changed

+149
-2
lines changed

3 files changed

+149
-2
lines changed

data/sidebar_react_latest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"components-and-props",
1010
"arrays-and-keys",
1111
"refs-and-the-dom",
12-
"context"
12+
"context",
13+
"router"
1314
],
1415
"Hooks & State Management": [
1516
"hooks-overview",

pages/docs/react/latest/router.mdx

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
---
2+
title: Router
3+
description: "Basic concepts for navigation and routing in ReScript & React"
4+
canonical: "/docs/react/latest/router"
5+
---
6+
7+
# Router
8+
9+
<Intro>
10+
11+
RescriptReact comes with a router! We've leveraged the language and library features in order to create a router that's:
12+
13+
- The simplest, thinnest possible.
14+
- Easily pluggable anywhere into your existing code.
15+
- Performant and tiny.
16+
17+
</Intro>
18+
19+
## How does it work?
20+
21+
The available methods are listed here:
22+
- `RescriptReactRouter.push(string)`: takes a new path and update the URL.
23+
- `RescriptReactRouter.replace(string)`: like `push`, but replaces the current URL.
24+
- `RescriptReactRouter.watchUrl(f)`: start watching for URL changes. Returns a subscription token. Upon url change, calls the callback and passes it the `RescriptReactRouter.url` record.
25+
- `RescriptReactRouter.unwatchUrl(watcherID)`: stop watching for URL changes.
26+
- `RescriptReactRouter.dangerouslyGetInitialUrl()`: get `url` record outside of `watchUrl`. Described later.
27+
- `RescriptReactRouter.useUrl(~serverUrl)`: returns the `url` record inside a component.
28+
29+
> If you want to know more about the low level details on how the router interface is implemented, refer to the [RescriptReactRouter implementation](https://github.com/rescript-lang/rescript-react/blob/master/src/RescriptReactRouter.res).
30+
31+
## Match a Route
32+
33+
*There's no API*! `watchUrl` gives you back a `url` record of the following shape:
34+
35+
<CodeTab labels={["ReScript", "JS Output"]}>
36+
37+
```res prelude
38+
type url = {
39+
/* path takes window.location.pathname, like "/book/title/edit" and turns it into `list{"book", "title", "edit"}` */
40+
path: list<string>,
41+
/* the url's hash, if any. The # symbol is stripped out for you */
42+
hash: string,
43+
/* the url's query params, if any. The ? symbol is stripped out for you */
44+
search: string
45+
}
46+
```
47+
```js
48+
// Empty output
49+
```
50+
51+
</CodeTab>
52+
53+
So the url `www.hello.com/book/10/edit?name=Jane#author` is given back as:
54+
55+
<CodeTab labels={["ReScript", "JS Output"]}>
56+
57+
```res prelude
58+
{
59+
path: list{"book", "10", "edit"},
60+
hash: "author",
61+
search: "name=Jane"
62+
}
63+
```
64+
```js
65+
// Empty output
66+
```
67+
68+
</CodeTab>
69+
70+
## Basic Example
71+
72+
Let's start with a first example to see how a ReScript React Router looks like:
73+
74+
<CodeTab labels={["ReScript", "JS Output"]}>
75+
76+
```res
77+
// App.res
78+
@react.component
79+
let make = () => {
80+
let url = RescriptReactRouter.useUrl()
81+
82+
switch url.path {
83+
| list{"user", id} => <User id />
84+
| list{} => <Home/>
85+
| _ => <PageNotFound/>
86+
}
87+
}
88+
```
89+
```js
90+
import * as React from "react";
91+
import * as User from "./User.bs.js";
92+
import * as RescriptReactRouter from "@rescript/react/src/RescriptReactRouter.bs.js";
93+
import * as Home from "./Home.bs.js";
94+
import * as NotFound from "./NotFound.bs.js";
95+
96+
function App(Props) {
97+
var url = RescriptReactRouter.useUrl(undefined, undefined);
98+
var match = url.path;
99+
if (!match) {
100+
return React.createElement(Home.make, {});
101+
}
102+
if (match.hd === "user") {
103+
var match$1 = match.tl;
104+
if (match$1 && !match$1.tl) {
105+
return React.createElement(User.make, {
106+
id: match$1.hd
107+
});
108+
}
109+
110+
}
111+
return React.createElement(NotFound.make, {});
112+
}
113+
114+
var make = App;
115+
116+
export {
117+
make ,
118+
119+
}
120+
```
121+
122+
</CodeTab>
123+
124+
## Directly Get a Route
125+
126+
In one specific occasion, you might want to take hold of a `url` record outside of `watchUrl`. For example, if you've put `watchUrl` inside a component's `didMount` so that a URL change triggers a component state change, you might also want the initial state to be dictated by the URL.
127+
128+
In other words, you'd like to read from the `url` record once at the beginning of your app logic. We expose `dangerouslyGetInitialUrl()` for this purpose.
129+
130+
Note: the reason why we label it as "dangerous" is to remind you not to read this `url` in any arbitrary component's e.g. `render`, since that information might be out of date if said component doesn't also contain a `watchUrl` subscription that re-renders the component when the URL changes. Aka, please only use `dangerouslyGetInitialUrl` alongside `watchUrl`.
131+
132+
## Push a New Route
133+
From anywhere in your app, just call e.g. `RescriptReactRouter.push("/books/10/edit#validated")`. This will trigger a URL change (without a page refresh) and `watchUrl`'s callback will be called again.
134+
135+
We might provide better facilities for typed routing + payload carrying in the future!
136+
137+
Note: because of browser limitations, changing the URL through JavaScript (aka pushState) cannot be detected. The solution is to change the URL then fire a "popState" event. This is what Router.push does, and what the event watchUrl listens to.
138+
So if, for whatever reason (e.g. incremental migration), you want to update the URL outside of `RescriptReactRouter.push`, just do `window.dispatchEvent(new Event('popState'))`.
139+
140+
## Design Decisions
141+
142+
We always strive to lower the performance and learning overhead in RescriptReact, and our router design's no different. The entire implementation, barring browser features detection, is around 20 lines. The design might seem obvious in retrospect, but to arrive here, we had to dig back into ReactJS internals & future proposals to make sure we understood the state update mechanisms, the future context proposal, lifecycle ordering, etc. and reject some bad API designs along the way. It's nice to arrive at such an obvious solution!
143+
144+
The API also doesn't dictate whether matching on a route should return a component, a state update, or a side-effect. Flexible enough to slip into existing apps.
145+
146+
Performance-wise, a JavaScript-like API tends to use a JS object of route string -> callback. We eschewed that in favor of pattern-matching, since the latter in Rescript does not allocate memory, and is compiled to a fast jump table in C++ (through the JS JIT). In fact, the only allocation in the router matching is the creation of the url record!

src/layouts/SidebarLayout.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)