Skip to content

Commit 4127541

Browse files
committed
first
0 parents  commit 4127541

10 files changed

+4050
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/es
2+
/node_modules
3+
/umd
4+
/index.js
5+
npm-debug.log*

LICENSE

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2018-present, Ryan Florence
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+363
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
# @reactions/router
2+
3+
## Installation
4+
5+
```bash
6+
npm install @reactions/router
7+
# or
8+
yarn add @reactions/router
9+
```
10+
11+
And then import it:
12+
13+
```js
14+
// using es modules
15+
import { Router, Link } from "@reactions/router";
16+
17+
// common.js
18+
const { Router, Link } = require("@reactions/router");
19+
20+
// AMD
21+
// I've forgotten but it should work.
22+
```
23+
24+
Or use script tags and globals.
25+
26+
```html
27+
<script src="https://unpkg.com/@reactions/router"></script>
28+
```
29+
30+
And then grab it off the global like so:
31+
32+
```js
33+
ReactionsRouter.Router;
34+
ReactionsRouter.Link;
35+
```
36+
37+
## Take 5 minutes and read this first
38+
39+
### Rendering
40+
41+
Routers select a child to render based on the child's path. The children are just other components that could be rendered on their own.
42+
43+
```js
44+
import { React } from "react";
45+
import { render } from "react-dom";
46+
import { Router, Link } from "@reactions/router";
47+
48+
const Home = () => <div>Home</div>;
49+
const Dash = () => <div>Dash</div>;
50+
51+
render(
52+
<Router>
53+
<Home path="/" />
54+
<Dash path="dashboard" />
55+
</Router>
56+
);
57+
```
58+
59+
### Navigate with Link
60+
61+
To navigate around the app, render a `Link` somewhere.
62+
63+
```jsx
64+
render(
65+
<div>
66+
<nav>
67+
<Link to="/">Home</Link> | <Link to="/dashboard">Dashboard</Link>
68+
</nav>
69+
<Router>
70+
<Home path="/" />
71+
<Dash path="dashboard" />
72+
</Router>
73+
</div>
74+
);
75+
```
76+
77+
### Parse data from the URL
78+
79+
If you need to parse the data out of the URL, use a dynamic segment--they start with a `:`. The parsed value will become a prop sent to the matched component.
80+
81+
```jsx
82+
// at url "/23"
83+
84+
render(
85+
<Router>
86+
<Home path="/" />
87+
<Invoice path=":invoiceId" />
88+
</Router>
89+
);
90+
91+
const Invoice = props => (
92+
<div>
93+
<h1>Invoice {props.invoiceId}</h1>
94+
</div>
95+
);
96+
```
97+
98+
It's the same as rendering the component directly.
99+
100+
```jsx
101+
<Invoice invoiceId={23} />
102+
```
103+
104+
### Ambiguous paths and ranking
105+
106+
Even though two paths might be ambiguous--like "/:invoiceId" and "/invoices"--Router ranks the paths and renders the one that makes the most sense.
107+
108+
```jsx
109+
render(
110+
<Router>
111+
<Home path="/" />
112+
<Invoice path=":invoiceId" />
113+
<InvoiceList path="invoices" />
114+
</Router>
115+
);
116+
```
117+
118+
The URL "/invoices" will render `<Invoices/>` and "/123" will render `<Invoice invoiceId={123}/>`. Same thing with the `Home` component. Even though it’s defined first, and every path will match "/", `Home` won't render unless the path is exactly "/". So don't worry about the order of your paths.
119+
120+
### Nested Router children and paths
121+
122+
You can nest components inside of a Router, and the paths will nest too. The matched child component will come in as the `children` prop, the same as if you'd rendered it directly. (Internally `Router` just renders another `Router` with a `basepath`, but I digress...)
123+
124+
```jsx
125+
const Dash = ({ children }) => (
126+
<div>
127+
<h1>Dashboard</h1>
128+
<hr />
129+
{children}
130+
</div>
131+
);
132+
133+
render(
134+
<Router>
135+
<Home path="/" />
136+
<Dash path="dashboard">
137+
<Invoices path="invoices" />
138+
<Team path="team" />
139+
</Dash>
140+
</Router>
141+
);
142+
```
143+
144+
If the URL is "/dashboard/invoices" then the Router will render `<Dash><Invoices/></Dash>`. If it's just "/dashboard", `children` will be `null` and we’ll only see `<Dash/>`.
145+
146+
### Relative Links
147+
148+
You can link to relative paths. The relativity comes from the path of the component that rendered the Link. These two links will link to "/dashboard/invoices" and "/dashboard/team" because they're rendered inside of `<Dash/>`. This is really nice when you change a parent's URL, or move the components around, there’s no need to change the links.
149+
150+
```jsx
151+
render(
152+
<Router>
153+
<Home path="/" />
154+
<Dash path="dashboard">
155+
<Invoices path="invoices" />
156+
<Team path="team" />
157+
</Dash>
158+
</Router>
159+
);
160+
161+
const Dash = ({ children }) => (
162+
<div>
163+
<h1>Dashboard</h1>
164+
<nav>
165+
<Link to="invoices">Invoices</Link> <Link to="team">Team</Link>
166+
</nav>
167+
<hr />
168+
{children}
169+
</div>
170+
);
171+
```
172+
173+
This also makes it trivial to render any section of your app as its own application.
174+
175+
### "Index" paths
176+
177+
Nested components can use the path `/` to signify they should render
178+
at the path of the parent component, like an index.html file inside
179+
a folder on a static server. If this app was at "/dashboard" we'd see this
180+
component tree: `<Dash><DashboardGraphs/></Dash>`
181+
182+
```jsx
183+
render(
184+
<Router>
185+
<Home path="/" />
186+
<Dash path="dashboard">
187+
<DashboardGraphs path="/" />
188+
<InvoiceList path="invoices" />
189+
</Dash>
190+
</Router>
191+
);
192+
```
193+
194+
### Not found "default" components
195+
196+
Put a default prop on a component and Router will render it when nothing else matches.
197+
198+
```jsx
199+
const NotFound = () => <div>Sorry, nothing here.</div>;
200+
201+
render(
202+
<Router>
203+
<Home path="/" />
204+
<Dash path="dashboard">
205+
<DashboardGraphs path="/" />
206+
<InvoiceList path="invoices" />
207+
</Dash>
208+
<NotFound default />
209+
</Router>
210+
);
211+
```
212+
213+
### Multiple Routers
214+
215+
If you want to match the same path in two places in your app, just render two
216+
Routers. Again, a Router picks a single child to render based on the URL, and
217+
then ignores the rest.
218+
219+
```jsx
220+
render(
221+
<div>
222+
<Sidebar>
223+
<Router>
224+
<HomeNav path="/" />
225+
<DashboardNav path="dashboard" />
226+
</Router>
227+
</Sidebar>
228+
229+
<MainScreen>
230+
<Router>
231+
<Home path="/">
232+
<About path="about" />
233+
<Support path="support" />
234+
</Home>
235+
<Dash path="dashboard">
236+
<Invoices path="invoices" />
237+
<Team path="team" />
238+
</Dash>
239+
</Router>
240+
</MainScreen>
241+
</div>
242+
);
243+
```
244+
245+
### Nested Routers
246+
247+
You can render a router anywhere you want in your app, even deep inside another Router. All the matching and linking will be relative to all the parents.
248+
249+
```jsx
250+
render(
251+
<Router>
252+
<Home path="/" />
253+
<Dash path="dashboard" />
254+
</Router>
255+
);
256+
257+
const Dash = () => (
258+
<div>
259+
<p>A nested router</p>
260+
<Router>
261+
<DashboardGraphs path="/" />
262+
<InvoiceList path="invoices" />
263+
</Router>
264+
</div>
265+
);
266+
```
267+
268+
This allows you to have all of your routes configured at the top of the app, or to configure only where you need them, which is really helpful for code-splitting and very large apps.
269+
270+
### Navigating programmatically
271+
272+
If you need to navigate programmatically (like after a form submits)
273+
use the `navigate` prop that comes to your component
274+
275+
```jsx
276+
const Invoices = ({ navigate }) => (
277+
<div>
278+
<NewInvoiceForm
279+
onSubmit={async event => {
280+
const newInvoice = await createInvoice(event.target);
281+
navigate(`/invoice/${newInvoice.id}`);
282+
}}
283+
/>
284+
</div>
285+
);
286+
```
287+
288+
Or get it from context if you're deep in the render tree.
289+
290+
```jsx
291+
const NotARouterChildComponent = () => (
292+
<div>
293+
<p>Somewhere deep</p>
294+
<Location>
295+
{({ navigate }) => (
296+
<NewInvoiceForm
297+
onSubmit={async event => {
298+
const newInvoice = await createInvoice(event.target);
299+
navigate(`/invoice/${newInvoice.id}`);
300+
}}
301+
/>
302+
)}
303+
</Location>
304+
</div>
305+
);
306+
```
307+
308+
Navigate returns a promise so you can await it. It resolves after React is completely finished rendering the next screen.
309+
310+
```jsx
311+
class Invoices extends React.Component {
312+
state = {
313+
creatingNewInvoice: false
314+
};
315+
316+
render() {
317+
const { navigate } = this.props;
318+
return (
319+
<div>
320+
<LoadingBar animate={this.state.creatingNewInvoice} />
321+
<NewInvoiceForm
322+
onSubmit={async event => {
323+
this.setState({ creatingNewInvoice: true });
324+
const newInvoice = await createInvoice(event.target);
325+
await navigate(`/invoice/${newInvoice.id}`);
326+
this.setState({ creatingNewInvoice: false });
327+
}}
328+
/>
329+
<InvoiceList />
330+
</div>
331+
);
332+
}
333+
}
334+
```
335+
336+
## React Suspense and Time Slicing Ready
337+
338+
### History stack handling
339+
340+
With React Suspense a user may click on a link, and while data is loading they may change their mind and click on a different link. The browser navigation history will contain all three entries:
341+
342+
```
343+
/first-page -> /cancelled-page -> /desired-page
344+
```
345+
346+
If they click the back button from `/desired-page`, they'll get an unexpected `/cancelled-paged` screen. They didn't see it on their way to `/desired-page` so it doesn’t make sense for it to be there on the way back to `/first-page`.
347+
348+
Reactions Router will not add the cancelled page to the history stack, so it would look like this:
349+
350+
```
351+
/first-page -> /desired-page
352+
```
353+
354+
Now when the user clicks back, they don’t end up on a page they never even saw. This is how browsers work with plain HTML pages, too.
355+
356+
### Low priority updates
357+
358+
Router takes advantage of "Time Slicing" in React . It's very common to hook a user input up to a query string in the URL. Every time the user types, the url updates, and then React rerenders. Router state is given "low priority" so these inputs will not bind the CPU like they would have otherwise.
359+
360+
## Legal
361+
362+
MIT License
363+
Copyright (c) 2018-present, Ryan Florence

0 commit comments

Comments
 (0)