crossroad 0.16.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.min.js +1 -1
- package/package.json +2 -2
- package/readme.md +104 -11
package/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t,{createContext as e,useState as r,
|
|
1
|
+
import t,{createContext as e,useState as r,useCallback as n,useEffect as a,useContext as o}from"react";var i=e();const s=t=>{if("string"!=typeof t)return t;const e={},r=new URL(t,"http://localhost:3000/");e.path=r.pathname.replace(/\/$/,"")||"/",e.query={};for(const[t]of r.searchParams)e.query[t]=(n=r.searchParams.getAll(t)).length>1?n:n[0];var n;return r.hash&&(e.hash=r.hash.replace(/^#/,"")),e},p=t=>{if("string"==typeof t)return t;const{path:e,query:r={},hash:n}=t||{};let a=e||"/";const o=new URLSearchParams(Object.entries(r).map(([t,e])=>(Array.isArray(e)?e:[e]).map(e=>[t,e])).flat().filter(([t,e])=>e)).toString();return o&&(a+="?"+o),n&&(a+="#"+n),a};var u=()=>"undefined"==typeof window;function c(t,e,r={}){if(t=JSON.parse(JSON.stringify(s(t))),(e=JSON.parse(JSON.stringify(s(e)))).path=e.path.replace(/\/$/,"")||"/",t.path=t.path.replace(/\/$/,"")||"/",t.path.endsWith("*")){t.path=t.path.replace(/\/?\*/,"")||"/";const r=t.path.split("/").filter(Boolean).length;e.path="/"+e.path.slice(1).split("/").slice(0,r).join("/")}if(Object.entries(t.query).length)for(let r in t.query){if(!(r in e.query))return!1;if(t.query[r]&&t.query[r]!==e.query[r])return!1}if(!t.path.includes(":"))return t.path===e.path&&r;if(t.path.split("/").length!==e.path.split("/").length)return!1;const n={},a=t.path.split("/").every((t,a)=>{const o=e.path.split("/")[a];return t.startsWith(":")?(n[t.slice(1)]=o,r):o===t});return a&&Object.assign(r,n),a&&r}var l=({path:e="*",exact:r=!0,component:n,render:a,children:s})=>{const p=o(i),u=c(e,p[0]);if(!u)return null;if(n){const e=n;s=t.createElement(e,u)}else if(a)s=a(u);else if(!s)throw new Error("Route needs prop `component`, `render` or `children`");return t.createElement(i.Provider,{value:[{...p[0],params:u},...p.slice(1)]},s)},h=()=>{const t=o(i);if(!t)throw new Error("Wrap your App with <Router>");return t};var f=({redirect:t,children:e})=>{const[r,n]=h(),o=(t=>(Array.isArray(t)||(t=[t]),t.filter(t=>t&&t.props)))(e).find(t=>c(t.props.path||"*",r))||null;return a(()=>{t&&(o||("function"==typeof t&&(t=t(r)),n(p(t))))},[t,o]),o},y=()=>{const[t,e]=h(),r=n((t,r)=>{e(e=>("function"==typeof t&&(t=t(e.path)),"string"!=typeof t&&(t="/"),{...e,path:t}),r)},[]);return[t.path,r]};const d=t=>p({query:t});var q=t=>t?(t=>{const[e,r]=h(),a=n((e,n)=>{r(r=>{const n=r.query[t];if((e="function"==typeof e?e(n):e)===n)return r;if(e)return{...r,query:{...r.query,[t]:e}};{const{[t]:e,...n}=r.query;return{...r,query:n}}},n)},[]);return[e.query[t],a]})(t):(()=>{const[t,e]=h(),r=n((t,r)=>{e(e=>("string"==typeof(t="function"==typeof t?t(e.query):t)&&(t=s("/?"+t.replace(/^\?/,"")).query),t=s(d(t)).query,d(t)===d(e.query)?e:{...e,query:t}),r)},[]);return[t.query,r]})(),m=()=>{const[t,e]=h(),r=n((t,r)=>{e(e=>("function"==typeof t&&(t=t(e.hash)),"string"!=typeof t&&(t=""),t=t.replace(/^#/,""),{...e,hash:t}),r)},[]);return[t.hash,r]},g=t=>{const e=o(i);return t?c(t,e[0].path)||{}:e[0].params};export default({url:e,children:o})=>{const c=e||(u()?"/":window.location.href),[l,h]=r(()=>s(c)),f=n((t,{mode:e="push"}={})=>{if(!history[e+"State"])throw new Error(`Invalid mode "${e}"`);t="function"==typeof t?t(l):t,h(r=>p(r)===p(t)?r:(history[e+"State"]({},null,p(t)),s(t)))},[]);return a(()=>{if(u())return;const t=()=>h(s(window.location.href)),e=t=>{const e=(t=>{if(!t)return null;const e=t.getAttribute("href");return e?/^https?:\/\//.test(e)||null!==t.getAttribute("target")?null:e:null})(t.target.closest("a"));e&&(t.preventDefault(),f(e))};return window.addEventListener("popstate",t),document.addEventListener("click",e),()=>{window.removeEventListener("popstate",t),document.removeEventListener("click",e)}},[f]),t.createElement(i.Provider,{value:[l,f]},o)};export{i as Context,l as Route,f as Switch,m as useHash,g as useParams,y as usePath,q as useQuery,h as useUrl};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossroad",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A React library to handle navigation in your WebApp. Built with simple components and React Hooks so your code is cleaner.",
|
|
5
5
|
"homepage": "https://crossroad.page/",
|
|
6
6
|
"repository": "https://github.com/franciscop/crossroad.git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"babel-polyfill": "^6.26.0",
|
|
36
36
|
"jest": "^25.3.0",
|
|
37
37
|
"react": "^16.8.0 || ^17.0.0",
|
|
38
|
-
"react-test": "^0.
|
|
38
|
+
"react-test": "^0.10.2",
|
|
39
39
|
"rollup": "^1.32.1",
|
|
40
40
|
"rollup-plugin-babel": "^4.4.0",
|
|
41
41
|
"rollup-plugin-terser": "^5.2.0"
|
package/readme.md
CHANGED
|
@@ -125,6 +125,30 @@ export default function App() {
|
|
|
125
125
|
|
|
126
126
|
You would normally setup this Router straight on your App, along things like [Statux](https://statux.dev/)'s or [Redux](https://redux.js.org/)'s Store, error handling, translations, etc.
|
|
127
127
|
|
|
128
|
+
An example for a simple app:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
|
|
132
|
+
// App.js
|
|
133
|
+
import Router, { Switch, Route} from "crossroad";
|
|
134
|
+
|
|
135
|
+
import Home from './pages/Home';
|
|
136
|
+
import Dashboard from './pages/Dashboard';
|
|
137
|
+
import Profile from './pages/Profile';
|
|
138
|
+
|
|
139
|
+
export default function App() {
|
|
140
|
+
return (
|
|
141
|
+
<Router>
|
|
142
|
+
<Switch>
|
|
143
|
+
<Route path="/" component={Home} />
|
|
144
|
+
<Route path="/dashboard" component={Dashboard} />
|
|
145
|
+
<Route path="/:username" component={Profile} />
|
|
146
|
+
</Switch>
|
|
147
|
+
</Router>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
128
152
|
### `<Switch />`
|
|
129
153
|
|
|
130
154
|
A component that will only render the first of its children that matches the current URL. This is very useful to handle 404s, multiple routes matching, etc. For example, if you have a username system like `"/:username"` but want to have a help page, you can make it work easily with the switch:
|
|
@@ -356,11 +380,45 @@ setUrl({ ...url, path: "/" });
|
|
|
356
380
|
setUrl({ ...url, query: { search: "hello" } });
|
|
357
381
|
|
|
358
382
|
// Modify only one query param
|
|
359
|
-
setUrl({ ...url, query: { ...url.query, safe:
|
|
383
|
+
setUrl({ ...url, query: { ...url.query, safe: "no" } });
|
|
360
384
|
```
|
|
361
385
|
|
|
362
386
|
`useUrl()` is powerful enough for all of your needs, but you might still be interested in other hooks to simplify situations where you do e.g. heavy query manipulation with `useQuery`.
|
|
363
387
|
|
|
388
|
+
#### Setter
|
|
389
|
+
|
|
390
|
+
The setter can be invoked directly, or with a callback:
|
|
391
|
+
|
|
392
|
+
```js
|
|
393
|
+
setUrl('/newurl');
|
|
394
|
+
setUrl(oldUrl => '/newurl');
|
|
395
|
+
setUrl(oldUrl => ({ ...oldUrl, path: newPath }));
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
The function `setUrl` is _always_ the same, so it doesn't matter whether you put it as a dependency or not. However the `path` can be updated and change, so you want to depend on it:
|
|
399
|
+
|
|
400
|
+
```js
|
|
401
|
+
const [url, setUrl] = useurl();
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
if (url.path === '/base') {
|
|
404
|
+
setUrl('/base/deeper');
|
|
405
|
+
}
|
|
406
|
+
}, [url, setUrl]);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
If you update the url with the current url, it won't trigger a rerender. So the above can also be written as this, removing all dependencies:
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
const [url, setUrl] = useUrl();
|
|
413
|
+
useEffect(() => {
|
|
414
|
+
setUrl(old => {
|
|
415
|
+
if (old.path === '/base') return '/base/deeper';
|
|
416
|
+
return old;
|
|
417
|
+
});
|
|
418
|
+
}, []);
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
|
|
364
422
|
#### New history entry
|
|
365
423
|
|
|
366
424
|
By default `setUrl()` will create a new entry in the browser history. If you want to instead replace the current url you can pass a second parameter with `{ mode: 'replace' }`:
|
|
@@ -369,7 +427,7 @@ By default `setUrl()` will create a new entry in the browser history. If you wan
|
|
|
369
427
|
setUrl("/newurl", { mode: "replace" });
|
|
370
428
|
```
|
|
371
429
|
|
|
372
|
-
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/c` and then click on the back button, the browser will go back to `/b`.
|
|
430
|
+
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/c` and then click on the back button, the browser will go back to `/b`. This is because `/b` and `/c` are both independent entries in your history.
|
|
373
431
|
- `replace`: creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(replace)> `/c` and then click on the back button, it'll go back to `/a`. This is because `/c` is overwriting `/b`, instead of adding a new entry.
|
|
374
432
|
|
|
375
433
|
### `usePath()`
|
|
@@ -389,8 +447,43 @@ const Login = () => {
|
|
|
389
447
|
};
|
|
390
448
|
```
|
|
391
449
|
|
|
450
|
+
The path is always a string equivalent to `window.location.pathname`.
|
|
451
|
+
|
|
392
452
|
> Note: this _only_ modifies the path(name) and keeps the search query and hash the same, so if you want to modify the full URL you should instead utilize `useUrl()` and `setUrl('/welcome')`
|
|
393
453
|
|
|
454
|
+
#### Setter
|
|
455
|
+
|
|
456
|
+
The setter can be invoked directly, or with a callback:
|
|
457
|
+
|
|
458
|
+
```js
|
|
459
|
+
setPath('/newpath');
|
|
460
|
+
setPath(oldPath => '/newpath');
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The function `setPath` is _always_ the same, so it doesn't matter whether you put it as a dependency or not. However the `path` can be updated, so you might want to put that:
|
|
464
|
+
|
|
465
|
+
```js
|
|
466
|
+
const [path, setPath] = usePath();
|
|
467
|
+
useEffect(() => {
|
|
468
|
+
if (path === '/base') {
|
|
469
|
+
setPath('/base/deeper');
|
|
470
|
+
}
|
|
471
|
+
}, [path, setPath]);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
If you update the path with the current path, it won't trigger a rerender. So the above can also be written as this, removing all dependencies:
|
|
475
|
+
|
|
476
|
+
```js
|
|
477
|
+
const [path, setPath] = usePath();
|
|
478
|
+
useEffect(() => {
|
|
479
|
+
setPath(old => {
|
|
480
|
+
if (old === '/base') return '/base/deeper';
|
|
481
|
+
return old;
|
|
482
|
+
});
|
|
483
|
+
}, []);
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
|
|
394
487
|
#### New history entry
|
|
395
488
|
|
|
396
489
|
By default `setPath()` will create a new entry in the browser history. If you want to instead replace the current url you can pass a second parameter with `{ mode: 'replace' }`:
|
|
@@ -399,7 +492,7 @@ By default `setPath()` will create a new entry in the browser history. If you wa
|
|
|
399
492
|
setPath("/newpath", { mode: "replace" });
|
|
400
493
|
```
|
|
401
494
|
|
|
402
|
-
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/c` and then click on the back button, the browser will go back to `/b`.
|
|
495
|
+
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/c` and then click on the back button, the browser will go back to `/b`. This is because `/b` and `/b?q=c` are both independent entries in your history.
|
|
403
496
|
- `replace`: creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(replace)> `/c` and then click on the back button, it'll go back to `/a`. This is because `/c` is overwriting `/b`, instead of adding a new entry.
|
|
404
497
|
|
|
405
498
|
### `useQuery()`
|
|
@@ -439,18 +532,18 @@ When you update it, it will clean any parameter not passed, so make sure to pass
|
|
|
439
532
|
const [query, setQuery] = useQuery();
|
|
440
533
|
|
|
441
534
|
setQuery({ search: "myname" });
|
|
442
|
-
// Goto /users?
|
|
535
|
+
// Goto /users?search=myname (removes the filter)
|
|
443
536
|
|
|
444
537
|
setQuery({ ...query, search: "myname" });
|
|
445
|
-
// Goto /users?
|
|
538
|
+
// Goto /users?search=myname&filter=new
|
|
446
539
|
|
|
447
540
|
setQuery(prev => ({ ...prev, search: "myname" }));
|
|
448
|
-
// Goto /users?
|
|
541
|
+
// Goto /users?search=myname&filter=new
|
|
449
542
|
```
|
|
450
543
|
|
|
451
544
|
`setQuery` only modifies the query string part of the URL, keeping the `path` and `hash` the same as they were previously.
|
|
452
545
|
|
|
453
|
-
When you set a search query to `null`
|
|
546
|
+
When you set a search query to `null` it will be removed from the URL. However, empty strings `""`, zero `0` or boolean `false` are not removed. So if you want falsy values to also remove the parameter in the URL, please do this:
|
|
454
547
|
|
|
455
548
|
```js
|
|
456
549
|
const [myname, setMyname] = useQuery("myname");
|
|
@@ -460,7 +553,7 @@ const [myname, setMyname] = useQuery("myname");
|
|
|
460
553
|
setMyname(newName || null);
|
|
461
554
|
```
|
|
462
555
|
|
|
463
|
-
If you are using `react-query` and already have a bunch of `useQuery()` in your code and prefer to use other name,
|
|
556
|
+
If you are using `react-query` and already have a bunch of `useQuery()` in your code and prefer to use other name, you can rename this method when importing it:
|
|
464
557
|
|
|
465
558
|
```js
|
|
466
559
|
import { useQuery as useSearch } from 'crossroad';
|
|
@@ -475,7 +568,7 @@ By default `setQuery()` will create a new entry in the browser history. If you w
|
|
|
475
568
|
setQuery({ search: "abc" }, { mode: "replace" });
|
|
476
569
|
```
|
|
477
570
|
|
|
478
|
-
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/b?q=c` and then click on the back button, the browser will go back to `/b`.
|
|
571
|
+
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/b?q=c` and then click on the back button, the browser will go back to `/b`. This is because `/b` and `/b?q=c` are both independent entries in your history.
|
|
479
572
|
- `replace`: creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(replace)> `/b?q=c` and then click on the back button, it'll go back to `/a`. This is because `/b?q=c` is overwriting `/b`, instead of adding a new entry.
|
|
480
573
|
|
|
481
574
|
### `useHash()`
|
|
@@ -507,7 +600,7 @@ By default `setHash()` will create a new entry in the browser history. If you wa
|
|
|
507
600
|
setHash("newhash", { mode: "replace" });
|
|
508
601
|
```
|
|
509
602
|
|
|
510
|
-
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/b#c` and then click on the back button, the browser will go back to `/b`.
|
|
603
|
+
- `push` (default): creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(push)> `/b#c` and then click on the back button, the browser will go back to `/b`. This is because `/b` and `/b?q=c` are both independent entries in your history.
|
|
511
604
|
- `replace`: creates a new entry in the history. E.g. if you navigate `/a` => `/b` =(replace)> `/b#c` and then click on the back button, it'll go back to `/a`. This is because `/b#c` is overwriting `/b`, instead of adding a new entry.
|
|
512
605
|
|
|
513
606
|
### `useParams()`
|
|
@@ -839,7 +932,7 @@ export default function Mock({ url, children }) {
|
|
|
839
932
|
// Undo the setup when the component unmounts
|
|
840
933
|
useEffect(() => {
|
|
841
934
|
return () => Object.defineProperty(window, "location", oldLocation);
|
|
842
|
-
});
|
|
935
|
+
}, []);
|
|
843
936
|
return <div>{children}</div>;
|
|
844
937
|
}
|
|
845
938
|
```
|