Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/franciscop/crossroad
🛣 A React library to handle navigation in your WebApp. Built with simple components and React Hooks so your code is cleaner.
https://github.com/franciscop/crossroad
browser javascript react react-router router routing ssr
Last synced: 13 days ago
JSON representation
🛣 A React library to handle navigation in your WebApp. Built with simple components and React Hooks so your code is cleaner.
- Host: GitHub
- URL: https://github.com/franciscop/crossroad
- Owner: franciscop
- License: mit
- Created: 2021-08-12T15:06:10.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2024-08-28T00:31:33.000Z (3 months ago)
- Last Synced: 2024-10-23T02:37:08.211Z (21 days ago)
- Topics: browser, javascript, react, react-router, router, routing, ssr
- Language: JavaScript
- Homepage: https://crossroad.page
- Size: 206 KB
- Stars: 43
- Watchers: 4
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Crossroad [![npm install crossroad](https://img.shields.io/badge/npm%20install-crossroad-blue.svg "install badge")](https://www.npmjs.com/package/crossroad) [![test badge](https://github.com/franciscop/crossroad/workflows/tests/badge.svg "test badge")](https://github.com/franciscop/crossroad/blob/master/.github/workflows/tests.yml) [![gzip size](https://img.badgesize.io/franciscop/crossroad/master/index.min.js.svg?compression=gzip "gzip badge")](https://github.com/franciscop/crossroad/blob/master/index.min.js)
A React library to handle navigation in your WebApp. Built with simple components and React Hooks so you write cleaner code:
- ``, `` and `` inspired by React Router so it's easy to get started.
- Very useful hooks like [`useUrl`](#useurl), [`useQuery`](#usequery), etc. Follow [the rules of hooks](https://reactjs.org/docs/hooks-rules.html).
- Links are plain `` instead of custom components. [Read more](#a).
- The `` path is `exact` by default and can match query parameters.
- It's [just ~1.5kb](https://bundlephobia.com/package/crossroad) (min+gzip) instead of the 17kb of React Router(+Dom).
- Add `scrollUp` to `` o `` to automatically scroll up on a route change.[**🔗 Demo on CodeSandbox**](https://codesandbox.io/s/recursing-wozniak-uftyo?file=/src/App.js)
```js
// App.js
import Router, { Switch, Route } from "crossroad";export default function App() {
return (
Home
Users
...
);
}
```## Getting Started
Create a React project (`npx create-react-app demo`) and install Crossroad:
```js
npm i crossroad
```Then import it on your App.js and define some routes:
```js
import Router, { Switch, Route } from "crossroad";export default function App() {
return (
);
}
```Then let's add some navigation and the actual pages:
```js
import Router, { Switch, Route } from "crossroad";const Home = () => Home Page;
const Profile = ({ id }) => Hello {id.toUpperCase()};export default function App() {
return (
Home
User A
User B
);
}
```Now you can start your project and test it by visiting `http://localhost:3000/` and `http://localhost:3000/login`:
```bash
npm start
```See the more complete working example [in this CodeSandbox](https://codesandbox.io/s/recursing-wozniak-uftyo?file=/src/App.js).
## API
The API is composed of these parts:
- [``](#router): the top-level component that should wrap your whole app.
- [``](#switch): renders only the first child that matches the current url.
- [``](#route): filters whether the given component should be rendered or not for the current URL.
- [``](#a): a plain HTML link, use it to navigate between pages.
- [`useUrl()`](#useurl): a hook that returns the current URL and a setter to update it.
- [`usePath()`](#usepath): a hook that returns the current path and a setter to update it.
- [`useQuery()`](#usequery): a hook that returns the current query and a setter to update it.
- [`useHash()`](#usehash): a hook that returns the current hash and a setter to update it.
- [`useParams()`](#useparams): a hook that extracts params form the current path.`Router` is the default export, `` is not exported since it's just the plain link element, and everything else are named exports:
```js
import Router, { Switch, Route, useUrl, usePath } from "crossroad";
```### ``
The top-level component that has to wrap everything else. Internally it's used to handle clicks, history, etc. It's also the default export of the library:
```js
// App.js
import Router from "crossroad";export default function App() {
return ... Your normal App code ...;
}
```Add the prop `scrollUp` to automatically scroll up the browser window when _any_ route changes. In contrast, you could also add it only to a single or multiple ``.
Add the prop `url` to simulate a fake URL instead of the current `window.location`, useful specially for testing.
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.
An example for a simple app:
```js
// App.js
import Router, { Switch, Route } from "crossroad";import Home from "./pages/Home";
import Dashboard from "./pages/Dashboard";
import Profile from "./pages/Profile";export default function App() {
return (
);
}
```### ``
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:
```js
// In https://example.com/help, it'll render the Help component only
```
You might want to redirect the user to a specific route (like `/notfound`) when none of the given routes matches the current URL. You can then use the attribute "redirect":
```js
```
The redirect parameter can be a plain string, an url-like object or a callback that returns any of the previous:
```js
"/gohere"}>
({ ...url, path: "/gohere" })}>
```Or to keep it in the current route, whatever it is, you can render a component with no path (no path === `*`):
```js
```
The `` component only accepts `` as its children.
### ``
This component defines a conditional path that, when strictly matched, renders the given component. Its props are:
- `path`: the path to match to the current browser's URL. It can have parameters `/:id` and a wildcard at the end `*` to make it a partial route.
- `component`: the component that will be rendered if the browser's URL matches the `path` parameter.
- `render`: a function that will be called with the params if the browser's URL matches the `path` parameter.
- `children`: the children to render if the browser's URL matches the `path` parameter.
- `scrollUp`: automatically scroll up the browser window when this route/component/etc is matched.So for example if the `path` prop is `"/user"` and you visit the page `"/user"`, then the component is rendered; it is ignored otherwise:
```js
// In https://example.com/
// Rendered
// Rendered
// Not rendered
// Not rendered// In https://example.com/user/
// Not Rendered
// Rendered
// Rendered
// Rendered
```When matching a path with a parameter (a part of the url that starts with `:`) it will be passed as a prop straight to the children:
```js
// In https://example.com/user/abc
const User = ({ id }) =>Hello {id};
const UserList = () =>List here;;
//Hello abc} />;
//Hello abc// Avoid when you need the params, since they cannot be passed
;
//List here
```> NOTE: the parameter is passed straight to the component instead of wrapped like in React Router.
The path can also include a wildcard `*`, in which case it will perform a partial match of everything before itself. It can only be at the end of the path:
```js
// In https://example.com/user/abc// All of these match the current route
```
> NOTE: in Crossroad the paths are exact by default, and with the wildcard you can make them partial matches. So the wildcard is the opposite of adding `exact` to React Router.
It can also match query parameters:
```js
// In /profile?page=settings&filter=abc// All of these match the current route
// These shall not match:
// Wrong path
// Wrong key
// Wrong value
```### ``
Links with Crossroad are just traditional plain ``. You write the URL and a relative path, and Crossroad handles all the history, routing, etc:
```js
export default () => (
Home
Users
Settings
);
```An important concept to understand is where links open, whether it's a react navigation or a browser page change:
- `/`: plain paths will navigate within React
- `/?abc=def`: queries, hashtags, etc. will also perform a navigation in React
- `https://example.com/`: full URLs will trigger a browser page change
- `target="_self"`: will trigger a browser page change, in the same tab
- `target="_blank"`: will open a new tabSome examples:
```js
// In https://example.com/users/25// React navigation:
Home// React navigation:
New users// Page refresh (since it's a full URL)
Google it// Page refresh (a full URL, even in the same domain)
Home// Page refresh (it has a target="_self")
Update// New tab (it has a target="_blank")
Read terms of service
```### `useUrl()`
> NOTE: within Crossroad's and for lack of a better name, "URL" refers to the combination of path + search query + hash.
Read and set the full URL:
```js
import { useUrl } from "crossroad";export default function Login() {
const [url, setUrl] = useUrl();const login = async () => {
// ... do some stuff ...
setUrl("/welcome");
};return Login;
}
```These are the structures of each:
- `url`: an object with the properties, it's similar to the native URL:
- `url.path`: a string with the current pathname
- `url.query`: an object with the keys and values. Example: `{ q: 'hello' }`, `{ q: 'hello', s: 'world' }`.
- `url.hash`: the hashtag, without the "#"
- `setUrl()`: a setter in the React Hooks style
- `setUrl("/newpath?search=hello")`: a shortcut with the string
- `setUrl({ path: '/newpath' })`: set the path (and delete anything else if any)
- `setUrl({ path: '/newpath', query: { hello: 'world' } })`: update the path and query (and delete the hash if any)
- `setUrl(prev => ...)`: use the previous url (object)`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`](#usequery).
#### url
The resulting `url` is an object containing each of the parts of the URL:
```js
// In /whatever?filter=hello#world
const [url, setUrl] = useUrl();
console.log(url.path); // /whatever
console.log(url.query); // { filter: hello }
console.log(url.hash); // world
```It is memoized, so that if the url doesn't change then the object will remain the same. The same of course applies to the subelements like `url.path`. It will however change when the url changes, so you want to put it in your dependencies as usual:
```js
// You can put the whole thing if you want to listen to
// ANY change on the url
useEffect(() => {
// ...
}, [url]);// Or only a part of it. This is useful becase it WON'T trigger
// when the query or hashtag change
useEffect(() => {
// ...
}, [url.path]);
```#### Setter
The setter can be invoked directly, or with a callback:
```js
const [url, setUrl] = useUrl();// [Shorthand] Redirect to home with a hashtag
setUrl("/#firsttime");// Same as above, but specifying the parts
setUrl({ path: "/", hash: "firsttime" });// Keep everything the same except the path
setUrl({ ...url, path: "/" });// Set a full search query
setUrl({ ...url, query: { search: "hello" } });// Modify only one query param
setUrl({ ...url, query: { ...url.query, safe: "no" } });
```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:
```js
const [url, setUrl] = useUrl();
useEffect(() => {
if (url.path === "/base") {
setUrl("/base/deeper");
}
}, [url.path, setUrl]);
```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:
```js
const [url, setUrl] = useUrl();
useEffect(() => {
setUrl((old) => {
if (old.path === "/base") return "/base/deeper";
return old;
});
}, []);
```#### New history entry
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' }`:
```js
setUrl("/newurl"); // Default: "push"
setUrl("/newurl", { mode: "replace" });
```- `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.
- `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.### `usePath()`
Read and set only the path(name) part of the URL:
```js
const Login = () => {
const [path, setPath] = usePath();const login = async () => {
// ...
setPath("/welcome");
};return Login;
};
```The path is always a string equivalent to `window.location.pathname`. Why not use `window.location.pathname` then? Because usePath() is a hook that will trigger a re-render when the path changes!
> Note: `setPath` _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')`
#### Setter
The setter can be invoked directly, or with a callback:
```js
setPath("/newpath");
setPath((oldPath) => "/newpath");
```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:
```js
const [path, setPath] = usePath();
useEffect(() => {
if (path === "/base") {
setPath("/base/deeper");
}
}, [path, setPath]);
```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:
```js
const [path, setPath] = usePath();
useEffect(() => {
setPath((old) => {
if (old === "/base") return "/base/deeper";
return old;
});
}, []);
```#### New history entry
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' }`:
```js
setPath("/newpath"); // Default: "push"
setPath("/newpath", { mode: "replace" });
```- `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.
- `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.### `useQuery()`
Read and set only the search query parameters from the URL:
```js
import { useQuery } from "crossroad";export default function SearchInput() {
// In /users?search=
const [query, setQuery] = useQuery();
// [{ search: "" }, fn]// Goes to /users?search={value}
const onChange = (e) => setQuery({ search: e.target.value });return ;
}
```If you pass a key, it can read and modify that parameter while keeping the others the same. This is specially useful in e.g. a search form:
```js
// In /users?search=name&filter=new
const [search, setSearch] = useQuery("search");
// 'name'setSearch("myname");
// Goto /users?search=myname&filter=new
```When you update it, it will clean any parameter not passed, so make sure to pass the old ones if you want to keep them or a new object if you want to scrub them:
```js
// In /users?search=name&filter=new
const [query, setQuery] = useQuery();setQuery({ search: "myname" });
// Goto /users?search=myname (removes the filter)setQuery({ ...query, search: "myname" });
// Goto /users?search=myname&filter=newsetQuery((prev) => ({ ...prev, search: "myname" }));
// Goto /users?search=myname&filter=new
````setQuery` only modifies the query string part of the URL, keeping the `path` and `hash` the same as they were previously.
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:
```js
const [myname, setMyname] = useQuery("myname");// ...
setMyname(newName || null);
```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:
```js
import { useQuery as useSearch } from 'crossroad';
...
```#### New history entry
By default `setQuery()` will create a new entry in the browser history. If you want to instead replace the current entry, so that the "Back" button goes to the previous page, you can pass a second parameter with `{ mode: 'replace' }`:
```js
setQuery({ search: "abc" }); // Default: "push"
setQuery({ search: "abc" }, { mode: "replace" });
```- `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.
- `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.### `useHash()`
Read and set only the hash part of the URL (without the `"#"`):
```js
// In /login#welcome
const [hash, setHash] = useHash();
// welcomesetHash("bye");
// Goto /login#bye
```By default `setHash()` will create a new entry in the browser history. If you want to instead replace the current entry you can pass a second parameter with `{ mode: 'replace' }`:
```js
setHash("newhash", { mode: "replace" });
```If you want to remove the hash, pass a `null` or `undefined` to the setter.
#### New history entry
By default `setHash()` will create a new entry in the browser history. If you want to instead replace the current entry, so that the "Back" button goes to the previous page, you can pass a second parameter with `{ mode: 'replace' }`:
```js
setHash("newhash"); // Default: "push"
setHash("newhash", { mode: "replace" });
```- `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.
- `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.### `useParams()`
Parse the current URL against the given reference:
```js
// In /users/2
const params = useParams("/users/:id");
// { id: '2' }
```> Note: this returns a plain object, not a [value, setter] array
It's not this method responsibility to match the url, just to attempt to parse it, so if there's no good match it'll just return an empty object (use a `` for path matching):
```js
// In /pages/settings
const params = useParams("/users/:id");
// {}
```## Examples
### Static routes
Let's see a traditional company website, where you have a homepage, some specific pages and a PDF:
[**Codesandbox example**](https://codesandbox.io/s/loving-joana-jikne)
https://user-images.githubusercontent.com/2801252/131257834-bfd9b6c6-f22e-46f2-9d06-8c14ac7f2708.mp4
```js
// App.js
import Router, { Switch, Route } from "crossroad";import Nav from "./Nav";
import Pages from "./Pages";export default function App() {
return (
);
}
```Now that we have our routing, and for simplicity sake, let's say all of our navigation links are inside a ``:
```js
export default function Nav() {
return (
Home
About Us
Product 1
Product 2
License
);
}
```That's it, in the [Codesandbox](https://codesandbox.io/s/loving-joana-jikne) we added some filler for the pages, but that's the basic structure of how to make it work.
### Vanity URLs
These refer to the websites where your username is straight after the domain, like Twitter (https://twitter.com/fpresencia). Of course Twitter has _other_ pages besides the username, so how can we emulate loading the page e.g. `/explore` in this case?
The best way is to first define the known, company pages and then use the wildcard for the usernames. This **must** be inside a ``, otherwise multiple will be rendered:
```js
```
This way we can handle the username inside Profile, and the other company-specific pages will load as expected. To work with the parameter, you can either use the props passed form the component or with the hook [useParams()](#useparams):
```js
// The parameters are passed straight to the component:
function Profile({ username }) {
returnHello {username};
}// or
// Use a hook to access the parameters:
function Profile() {
const { username } = useParams("/:username");
//
returnHello {username};
}// or
// The path is defined as `/:username` already in , we can reuse that:
function Profile() {
const { username } = useParams();
//
returnHello {username};
}
```In the end of the day we recommend picking one style and following it. For simple applications we recommend the first one, where you receive the parameters straight in the props. For more complex applications, including those with deep nesting, we recommend the hook with the named parameter (explicit is more clear than implicit).
### Search page
There are many ways to store the state to be able to visit later; localStorage, through API calls to the backend, cookies, etc. One place that people don't think often is the URL itself.
Thanks to `useQuery()`, it's trivial to use the search query for storing variables. Let's say you are looking for trips in a specific location, on a budget:
[**Codesandbox demo**](https://codesandbox.io/s/festive-murdock-1ctv6?file=/src/SearchForm.js)
https://user-images.githubusercontent.com/2801252/132189338-e09aa220-b773-43ed-803b-fa6c7449bf44.mp4
```js
import { useQuery } from "crossroad";export default function SearchForm() {
const [place, setPlace] = useQuery("place");
const [max, setMax] = useQuery("max");return (
);
}
```In here we can see that we are treating the output of `useQuery` in the same way that we'd treat the output of `useState()`. This is on purpose and it makes things a lot easier for your application to work.
### Query routing
Some times you prefer the current page to be defined by the query, instead of by the pathname. This might be true for subpages, for tabs, or for other things depending on your app. With Crossroad it's easy to manage:
[**Codesandbox**](https://codesandbox.io/s/white-moon-5q0hr)
https://user-images.githubusercontent.com/2801252/132944863-3caf9399-d0c1-4cdc-86a0-dca1a6a4b4d1.mp4
```js
```
With the code above, it will match the given component when the path is exactly "/" and the query parameter is the given one. If no one is matched, then it'll redirect you to `/?page=home`, the main page.
You can also use this for subpages, say if you were in a Dashboard:
```js
```
### Not found
We have already seen in different examples how to do simple redirects with a single ``, so now let's create a page for whenever the switch is not found:
```js
{/* Not found page */}
```
This page will maintain the url in the browser, but render the NotFound component. Notice how we didn't write any `path=""`, omitting the `path` is the same as writing it as `path="*"`, which will catch everything.
So the way this Switch works here, it will try to match the URL against `"/"`, then against `"/users"`, and if it's none of those it'll match it against `"*"` (since that's always a match) and render the NotFound component.
We can also have different not found pages. Let's say we have a specific "documentation page not found" with helpful documentation links and a general one for the rest of the website, we can manage them this way then:
```js
{/* Not found page only for the docs */}
{/* Not found page only for everything else */}
```
In this case the order matters, because the generic NotFound will be matched with any route (since it's `"*"`), so we need to match first the docs that is not found and then, even if that fails (e.g. on the path `/hello`) we can render the generic NotFound component.
### Github hosting
> NOTE: this is a bad idea for SEO, but if that doesn't matter much for you go ahead and host your webapp in Github Pages
Github pages is a bit particular in that as of this writing it does not allow for a generic redirect like most other static website servers, so we need to do a workaround with the `404.html` page.
This is because any of your visitors landing on `https://example.com/` will see the proper website (since that'll be directed to `docs/index.html`), but when the user lands on other paths like `https://example.com/info` it'll not find `docs/info.html` and thus render `404.html`.
So let's save the url and setup a redirect in `404.html`:
```html
Redirecting...
const url = JSON.stringify(location.pathname + location.search);
localStorage.url = url;
location.replace("/");
```
Then in your index.html, or in almost anywhere else, you can overwrite the URL:
```js
if (localStorage.url) {
history.replaceState({}, null, JSON.decode(localStorage.url));
delete localStorage.url;
}
```### Testing routes
When testing a route, we can do it mainly in two different ways. The recommended one in general is that you pass a `url` prop straight into your `` component, which will force the Router to behave like the browser is in that route.
Let's see first a very simple App example, noting that for this case we are passing the `url` from App to Router:
```js
// App.js
import Router, { Switch, Route } from "crossroad";// Imagine these are your apps and components:
const Home = () =>Home;
const Users = () =>Users;
const NotFound = () =>Website not found;export default function App({ url }) {
return (
);
}
```How does it work? The `url` prop will be undefined when a user loads the app (since we **don't** add it to index.js), so it is only being written for testing. On the users' browser, since it's undefinde Crossroad will use `window.location.href` instead.
```js
// App.test.js
import React from "react";
import $ from "react-test";import App from "./App";
describe("use the url prop", () => {
it("renders the home component on /", () => {
const $home = $();
expect($home.text()).toBe("Home");
});it("renders the user list on /users", () => {
const $home = $();
expect($home.text()).toBe("Users");
});it("renders not found when in another route", () => {
const $home = $();
expect($home.text()).toContain("not found");
});
});
```This method is the simplest to get started, but some people don't like having to add code to the production website only for the testing environment. That's all fine, there's another way that is a bit harder to setup but it's also more accurate to the browser's real behavior.
#### Mock window.location
The previous method has **a big limitation**: it doesn't allow you to navigate within your app for a test, since it's always forcing the same url. To avoid this and be able to test better the real-world behavior, use this method.
When you are running Jest, it creates a fake `window` already, so you can plug into that to mock the behavior for the duration of the test. Doing it with a React component makes it even smoother:
```js
// Mock.js
import React, { useEffect } from "react";export default function Mock({ url, children }) {
const href = "http://localhost:3000" + url;
const oldLocation = { value: window.location };
delete global.window.location;
Object.defineProperty(global.window, "location", {
value: new URL(href),
configurable: true,
});// Undo the setup when the component unmounts
useEffect(() => {
return () => Object.defineProperty(window, "location", oldLocation);
}, []);
return{children};
}
```With this Mock component, then you can wrap your normal application into working with routes:
```js
import React from "react";
import $ from "react-test";import App from "./App";
import Mock from "./Mock";describe("use the Mock component", () => {
it("renders the home component on /", () => {
const $home = $(
);
expect($home.text()).toBe("Home");
});it("renders the user list on /users", () => {
const $home = $(
);
expect($home.text()).toBe("Users");
});it("renders not found when in another route", () => {
const $home = $(
);
expect($home.text()).toContain("not found");
});
});
```### Server Side Render
Crossroad has been tested with these libraries/frameworks for SSR:
- ✅ [Razzle](https://razzlejs.org/): it works adding a bit of config; Razzle bundles React Router Dom by default, so you need to install Crossroad, remove React Router Dom and add the code mentioned below.
- ⚠️ [Next.js](https://nextjs.org/): it works, but is generally not needed since Next.js include its own router and file-based routing.
- ❌ [Babel-Node](https://babeljs.io/docs/en/babel-node): BabelNode [doesn't support ECMAScript modules (ESM)](https://babeljs.io/docs/en/babel-node#es6-style-module-loading-may-not-function-as-expected), but you are **also** [not supposed to use `babel-node` for production anyway](https://babeljs.io/docs/en/babel-node#not-meant-for-production-use) so this is not a real framework for SSR.
- Others? I couldn't find many other ways that people are running SSR that I could test.For Razzle (based on [these docs FaQ](https://razzlejs.org/docs/customization#transpilation-of-external-modules)):
```js
// razzle.config.js
module.exports = {
modifyWebpackOptions({ options: { webpackOptions } }) {
webpackOptions.notNodeExternalResMatch = (req) => /crossroad/.test(req);
webpackOptions.babelRule.include.push(/crossroad/);
return webpackOptions;
},
};
```When working on the server, and similar to [how we saw in testing](#testing-routes), we can overload the current url:
```js
// An express example
const App = ({ url }) => ...;app.get("/users", (req, res) => {
res.render();
});app.get("/users/:id", (req, res) => {
// {...} validate the `id` it here!res.render();
});
```## React Router diff
This part of the documentation tries to explain in detail the differences between Crossroad and React Router (Dom). Crossroad goal is to build a modern Router API from scratch, removing the legacy code and using Hooks natively.
### Intuitive API
I've been using React Router for 3-4 years and I _still_ get wrong how to properly import it and have to try 2-3 combinations or reading the docs! I prefer to use intuitive tools that I can learn and get out of the way.
So this is a clear win, with Crossroad you import it like this:
```js
import Router, { Switch, Route, ... } from 'crossroad';
```While with React Router, guess the correct one:
```js
import { Switch, Route, ... } from 'react-router';
import Router from 'react-router-dom';import Router, { Switch, Route, ... } from 'react-router-dom';
import { Switch, Route, ... } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
```> Tip: none of them are correct!
### Remove imperative API
With React Router your component receives the props `history`. This is no longer needed with Crossroad; instead of handling the history details, we provide a hook `useUrl()` with the setter `setUrl()` where you can set the new URL straight away:
```js
import { useUrl } from "crossroad";export default function LoginButton() {
const [url, setUrl] = useUrl();
const login = async (e) => {
// ...
setUrl("/welcome");
};
return Login;
}
```The other hooks, like [`useQuery()`](usequery), behave in a similar way so you don't need to be concerned about the history API at all.
### Useful Hooks
I've seen in multiple codebases people end up creating a `useQuery()` hook wrapping `useLocation` and `useHistory` to work with query parameters. Fear no more, this and some other useful hooks are there already on Crossroad and you can use them straight away:
```js
// setUrl() is quite flexible:
const [url, setUrl] = useUrl();setUrl("/#firsttime"); // [Shorthand] Redirect to home with a hashtag
setUrl({ path: "/", hash: "firsttime" }); // Same as above
setUrl({ ...url, path: "/" }); // Keep everything the same except the path
setUrl({ ...url, query: { search: myQuery } }); // Set a full search query
setUrl({ ...url, query: { ...url.query, safe: 0 } }); // Modify only one query param
``````js
// In /?search=myname&filter=new// Manipulate the whole query object
const [query, setQuery] = useQuery();
setQuery({ ...query, search: "myname2" });// Manipulate _only_ the query parameter "search"
const [search, setSearch] = useQuery("search");
setSearch("myname2");
```See a video and a demo of how useful and easy it is to use `useQuery()`:
[**Codesandbox demo**](https://codesandbox.io/s/festive-murdock-1ctv6?file=/src/SearchForm.js)
https://user-images.githubusercontent.com/2801252/132189338-e09aa220-b773-43ed-803b-fa6c7449bf44.mp4
### Plain Links
To add a link in your application, you use the native `` element instead of having to import a different component. What's more, this makes links a lot more consistent than in React Router. Some examples:
```js
// Crossroad// Normal link
Hello// Open in same page with refresh
Hello // http(s): links open with refresh
Hello // self to open with refresh// Open in new page
Hello // Traditional target blank
Hello
```The same in React Router are like this, note the inconsistencies of some times using `` and some times using ``
```js
// React Router// Normal link
Hello// Open in same page with refresh
Hello
Hello// Open in new page
Hello
Hello // Broken
```