15 | )
16 | ```
17 |
--------------------------------------------------------------------------------
/examples/crud/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/examples/crud/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return "module.exports = {};";
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return "cssTransform";
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/examples/crud/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/website/src/markdown/api/useLocation.md:
--------------------------------------------------------------------------------
1 | # useLocation
2 |
3 | Returns the location to any component.
4 |
5 | This API requires a hook-compatible version of React.
6 |
7 | ```jsx
8 | import { useLocation } from "@reach/router"
9 |
10 | const useAnalytics = (props) => {
11 | const location = useLocation();
12 |
13 | useEffect(() => {
14 | ga.send(['pageview', location.pathname]);
15 | }, [])
16 | )
17 | ```
18 |
--------------------------------------------------------------------------------
/website/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "syntax-dynamic-import",
4 | "transform-class-properties",
5 | "react-hot-loader/babel",
6 | ["transform-react-jsx", { "pragma": "Glamor.createElement" }]
7 | ],
8 | "presets": [
9 | "@babel/preset-react",
10 | [
11 | "@babel/preset-env",
12 | {
13 | "browsers": [">0.25%", "not ie 11", "not op_mini all"]
14 | }
15 | ]
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/website/src/markdown/tutorial/04-router.md:
--------------------------------------------------------------------------------
1 | # Tutorial - Router
2 |
3 | Next we import [Router](../api/Router), render it, and render our [Route Components](../api/RouteComponent) as children. All that's left is to add a `path` prop to each child.
4 |
5 | ```jsx
6 | import { Link, Router } from "@reach/router"
7 |
8 | // under the `nav`
9 |
10 |
11 |
12 |
13 | ```
14 |
--------------------------------------------------------------------------------
/website/src/markdown/tutorial/10-next-steps.md:
--------------------------------------------------------------------------------
1 | # Tutorial - Next Steps
2 |
3 | Congratulations! You now know everything you need to know to get started with Reach Router. Above this text is a the full application we just built.
4 |
5 | So what's next? Go build something, of course. Then come back here to browse the examples, read the guides, and give the API references a skim. Shouldn't take more than 30 minutes to read it all.
6 |
7 | Let us know how it goes!
8 |
--------------------------------------------------------------------------------
/website/src/markdown/tutorial/03-link.md:
--------------------------------------------------------------------------------
1 | # Tutorial - Link
2 |
3 | The first thing we want to do is add a Link. Here we import it, and then render a couple of them. Go ahead and click them and watch the URL change. This is the primary way users navigate around your app.
4 |
5 | ```jsx
6 | import { Link } from "@reach/router";
7 |
8 | // ...
9 |
Tutorial!
10 |
14 | ```
15 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Running Examples
2 |
3 | Each example uses create-react-app. To develop against the examples you just need to link @reach/router.
4 |
5 | ```
6 | # from the root of the repository
7 | yarn link @reach/router
8 | cd examples/crud
9 | yarn && yarn start
10 | ```
11 |
12 | There's a postinstall hook in each example that consumes the link.
13 |
14 | Next you'll want to open a new terminal tab and watch:
15 |
16 | ```
17 | # from the root of the repository
18 | yarn watch
19 | ```
20 |
--------------------------------------------------------------------------------
/website/src/markdown/api/createMemorySource.md:
--------------------------------------------------------------------------------
1 | # createMemorySource(initialPath)
2 |
3 | Creates a source for [`createHistory`](createHistory) that manages a history stack in memory. Mostly for testing.
4 |
5 | ```jsx
6 | import {
7 | createMemorySource,
8 | createHistory
9 | } from "@reach/router"
10 |
11 | // for some types of tests you want a memory source
12 | let source = createMemorySource("/starting/url")
13 | let history = createHistory(source)
14 | ```
15 |
16 | ## initialPath: string
17 |
18 | The initial path of the history.
19 |
--------------------------------------------------------------------------------
/website/src/markdown/api/createHistory.md:
--------------------------------------------------------------------------------
1 | # createHistory(source)
2 |
3 | Creates a history object that enables you to listen for location changes. You don't typically need this outside of some types of testing.
4 |
5 | ```jsx
6 | import {
7 | createMemorySource,
8 | createHistory
9 | } from "@reach/router"
10 |
11 | // listen to the browser history
12 | let history = createHistory(window)
13 |
14 | // for some types of tests you want a memory source
15 | let source = createMemorySource("/starting/url")
16 | let history = createHistory(source)
17 | ```
18 |
--------------------------------------------------------------------------------
/website/src/markdown/api/useNavigate.md:
--------------------------------------------------------------------------------
1 | # useNavigate
2 |
3 | If you need to navigate programmatically (like after a form submits), this hook gives you an API to do so with a signature like this:
4 |
5 | ```js
6 | navigate(to, { state={}, replace=false })
7 | ```
8 |
9 | This API requires a hook-compatible version of React.
10 |
11 | ```jsx
12 | import { useNavigate } from "@reach/router"
13 |
14 | const AnalyticTracker = (props) => {
15 | const navigate = useNavigate();
16 |
17 | return (
18 |
21 | )
22 | )
23 | ```
24 |
--------------------------------------------------------------------------------
/website/src/polyfills.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | if (typeof Promise === "undefined") {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require("promise/lib/rejection-tracking").enable();
8 | window.Promise = require("promise/lib/es6-extensions.js");
9 | }
10 |
11 | // Object.assign() is commonly used with React.
12 | // It will use the native implementation if it's present and isn't buggy.
13 | Object.assign = require("object-assign");
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Pull Requests
2 |
3 | Please don't send pull requests for new features, open an issue to discuss.
4 |
5 | ## Running tests
6 |
7 | This repo uses Jest.
8 |
9 | ```sh
10 | yarn test
11 | # or
12 | yarn test --watch
13 | ```
14 |
15 | ## Developing the examples
16 |
17 | First you have to link the lib.
18 |
19 | ```sh
20 | # from the root
21 | yarn build
22 | yarn link
23 | ```
24 |
25 | Then in one tab compile on file changes
26 |
27 | ```
28 | yarn watch
29 | ```
30 |
31 | And in another tab run the example
32 |
33 | ```sh
34 | # in a new tab
35 | cd examples/
36 | yarn link "@reach/router"
37 | yarn start
38 | ```
39 |
--------------------------------------------------------------------------------
/website/src/markdown/tutorial/08-default-routes.md:
--------------------------------------------------------------------------------
1 | # Tutorial - Default Routes
2 |
3 | When users end up at a URL that doesn't match any of your routes we'd like to indicate that, just like a 404. We accomplish this with a Default Route.
4 |
5 | First create a new component to render:
6 |
7 | ```jsx
8 | const NotFound = () =>
Sorry, nothing here
9 | ```
10 |
11 | Next, add it to `` with a `default` prop. This indicates to `Router` that it should be rendered if no match is found.
12 |
13 | ```jsx
14 |
15 |
16 | {/*...*/}
17 |
18 | ```
19 |
20 | Now type some random junk into the URL, like "/flakjsd" and you'll see the `NotFound` component renders.
21 |
--------------------------------------------------------------------------------
/website/src/markdown/api/redirectTo.md:
--------------------------------------------------------------------------------
1 | # redirectTo(uri)
2 |
3 | React 16+ only. For React < 16 use [`navigate`](navigate) or [Redirect](Redirect).
4 |
5 | Imperatively redirects to a new location by throwing a redirect request.
6 |
7 | ```jsx
8 | import { redirectTo } from "@reach/router"
9 |
10 | class User extends React.Component {
11 | componentDidMount() {
12 | fetchUser().then(user => {
13 | if (user.optedIntoNewUI) {
14 | redirectTo("/the/new/digs")
15 | }
16 | })
17 | }
18 |
19 | // ...
20 | }
21 | ```
22 |
23 | ## uri: string
24 |
25 | The uri to redirect to. Must be absolute, it does not support relative paths.
26 |
27 | ```jsx
28 | redirectTo("/somewhere/else")
29 | ```
30 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = "test";
5 | process.env.NODE_ENV = "test";
6 | process.env.PUBLIC_URL = "";
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on("unhandledRejection", err => {
12 | throw err;
13 | });
14 |
15 | const jest = require("jest");
16 | const argv = process.argv.slice(2);
17 |
18 | // Watch unless on CI or in coverage mode
19 | if (!process.env.CI && argv.indexOf("--coverage") < 0) {
20 | argv.push("--watch");
21 | }
22 |
23 | jest.run(argv);
24 |
--------------------------------------------------------------------------------
/examples/crud/scripts/test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = "test";
5 | process.env.NODE_ENV = "test";
6 | process.env.PUBLIC_URL = "";
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on("unhandledRejection", err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require("../config/env");
17 |
18 | const jest = require("jest");
19 | const argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf("--coverage") < 0) {
23 | argv.push("--watch");
24 | }
25 |
26 | jest.run(argv);
27 |
--------------------------------------------------------------------------------
/website/src/markdown/api/ServerLocation.md:
--------------------------------------------------------------------------------
1 | # ServerLocation
2 |
3 | When server rendering, you need to wrap your app in a `ServerLocation`. This enables your Routers, Links, etc. to match a location on the server where there is no history to listen to.
4 |
5 | ```jsx
6 | const App = () => (
7 |
8 |
9 |
10 |
11 | )
12 |
13 | const markup = renderToString(
14 |
15 |
16 |
17 | )
18 | ```
19 |
20 | Please see the [Server Rendering Guide](../server-rendering) for the complete story on server rendering.
21 |
22 | ## url: string
23 |
24 | The URL from the server.
25 |
26 | ```jsx
27 | createServer((req, res) => {
28 | let markup = renderToString(
29 |
30 | )
31 | }).listen(PORT)
32 | ```
33 |
--------------------------------------------------------------------------------
/website/src/markdown/tutorial/07-index-routes.md:
--------------------------------------------------------------------------------
1 | # Tutorial - Index Routes
2 |
3 | Rather than show a blank page at `/invoices`, let's add an Index Route. Index Routes are just another child of a route, except their path is `/`. An Index Route will render when no other sibling matches the location.
4 |
5 | First create a new component:
6 |
7 | ```jsx
8 | const InvoicesIndex = () => (
9 |
10 |
11 | Maybe put some pretty graphs here or
12 | something.
13 |
14 |
15 | )
16 | ```
17 |
18 | Next, add it as a child of `` with a path of `/`.
19 |
20 | ```jsx
21 |
22 |
23 |
24 |
25 |
26 | {/*...*/}
27 |
28 | ```
29 |
30 | Now click on the "invoices" link. When you visit "/invoices" you'll see the new component.
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Think you found a bug?
2 |
3 | Please do one of these things:
4 |
5 | 1. Don't open an issue, open a pull request with a failing test.
6 | 2. Boil down your issue on codesandbox (https://codesandbox.io/s/lplnp60wnm).
7 | 3. Don't be upset if your bug report is closed without comment. If you don't have the time to prove the bug is not a problem with your own code then we certainly don't either 😅.
8 |
9 | ## Have a question?
10 |
11 | Questions are welcome. They'll usually be answered with a link to some docs.
12 |
13 | Also please join our Spectrum community (https://spectrum.chat/reach)!
14 |
15 | ## Feature Request?
16 |
17 | We keep the API and feature set pretty limited on purpose so it's unlikely we'll want it implemented, but you're welcome to make a suggestion.
18 |
19 | Also feel free to ask about the feature in our Spectrum community (https://spectrum.chat/reach)
20 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from "rollup-plugin-babel";
2 | import uglify from "rollup-plugin-uglify";
3 | import replace from "rollup-plugin-replace";
4 | import commonjs from "rollup-plugin-commonjs";
5 | import resolve from "rollup-plugin-node-resolve";
6 |
7 | const config = {
8 | input: "src/index.js",
9 | output: {
10 | name: "ReachRouter",
11 | globals: {
12 | react: "React",
13 | "react-dom": "ReactDOM"
14 | }
15 | },
16 | external: ["react", "react-dom"],
17 | plugins: [
18 | babel({
19 | exclude: "node_modules/**"
20 | }),
21 | resolve(),
22 | commonjs({
23 | include: /node_modules/
24 | }),
25 | replace({
26 | "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV)
27 | })
28 | ]
29 | };
30 |
31 | if (process.env.NODE_ENV === "production") {
32 | config.plugins.push(uglify());
33 | }
34 |
35 | export default config;
36 |
--------------------------------------------------------------------------------
/examples/crud/config/polyfills.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | if (typeof Promise === "undefined") {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require("promise/lib/rejection-tracking").enable();
8 | window.Promise = require("promise/lib/es6-extensions.js");
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require("whatwg-fetch");
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require("object-assign");
17 |
18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19 | // We don't polyfill it in the browser--this is user's responsibility.
20 | if (process.env.NODE_ENV === "test") {
21 | require("raf").polyfill(global);
22 | }
23 |
--------------------------------------------------------------------------------
/website/src/markdown/api/Location.md:
--------------------------------------------------------------------------------
1 | # Location
2 |
3 | Typically you only have access to the location in Route Components, `Location` provides the location anywhere in your app with a child render prop.
4 |
5 | ```jsx
6 |
7 | {props => {
8 | props.location
9 | props.navigate
10 | }}
11 |
12 |
13 | // usually folks use some destructuring
14 |
15 | {({ location })=> {
16 | // ...
17 | }}
18 |
19 | ```
20 |
21 | The most common use case is using this to pass `location` to a `Router` for animations.
22 |
23 | ```jsx
24 | const FadeTransitionRouter = props => (
25 |
26 | {({ location }) => (
27 |
28 |
33 |
34 | {props.children}
35 |
36 |
37 |
38 | )}
39 |
40 | )
41 | ```
42 |
--------------------------------------------------------------------------------
/website/src/markdown/tutorial/05-url-parameters.md:
--------------------------------------------------------------------------------
1 | # Tutorial - URL Parameters
2 |
3 | Let's create another screen called `Invoice`. It expects a prop called `invoiceId`. You can imagine it being rendered like ``.
4 |
5 | ```jsx
6 | const Invoice = props => (
7 |
8 |
Invoice {props.invoiceId}
9 |
10 | )
11 | ```
12 |
13 | Next add it to the ``. Note the path. That `:invoiceId` is called a "URL Parameter". You'll see what it does next.
14 |
15 | ```jsx
16 |
17 |
18 | {/*...*/}
19 |
20 | ```
21 |
22 | Finally, link to the new route somewhere.
23 |
24 | ```jsx
25 | Invoice 123
26 | Invoice ABC
27 | ```
28 |
29 | The name of the url parameter (`:invoiceId`) becomes a prop by the same name on your route component (`props.invoiceId`). It gets parsed from the URL and passed to you.
30 |
31 | Besides using it to render, it's common to use that prop in `componentDidMount` to fetch some data.
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
16 |
17 | ## Documentation
18 |
19 | [Documentation Site](https://reach.tech/router)
20 |
21 | You can also find the docs in the [website directory](./website/src/markdown).
22 |
23 | ## Community
24 |
25 | [Join us on Spectrum](https://spectrum.chat/reach)
26 |
27 | ## Legal
28 |
29 | MIT License
30 | Copyright (c) 2018-present, Ryan Florence
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
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.
10 |
--------------------------------------------------------------------------------
/website/build/markdown-loader.js:
--------------------------------------------------------------------------------
1 | const markdownIt = require("markdown-it");
2 | const Prism = require("prismjs");
3 | const cheerio = require("cheerio");
4 |
5 | let aliases = {
6 | js: "jsx",
7 | html: "markup",
8 | sh: "bash"
9 | };
10 |
11 | let highlight = (str, lang) => {
12 | if (!lang) {
13 | return str;
14 | } else {
15 | lang = aliases[lang] || lang;
16 | require(`prismjs/components/prism-${lang}.js`);
17 | if (Prism.languages[lang]) {
18 | return Prism.highlight(str, Prism.languages[lang]);
19 | } else {
20 | return str;
21 | }
22 | }
23 | };
24 |
25 | let md = markdownIt({
26 | html: true,
27 | linkify: true,
28 | typographer: true,
29 | highlight
30 | });
31 |
32 | let getTitle = html =>
33 | cheerio
34 | .load(html)("h1")
35 | .text();
36 |
37 | module.exports = markdown => {
38 | let html = md.render(markdown);
39 | return `
40 | import React from 'react'
41 | export const title = ${JSON.stringify(getTitle(html))};
42 | export default function() {
43 | return React.createElement(
44 | 'div',
45 | {
46 | className: 'markdown',
47 | dangerouslySetInnerHTML: { __html: ${JSON.stringify(html)}}
48 | }
49 | )
50 | }
51 | `;
52 | };
53 |
--------------------------------------------------------------------------------
/website/src/markdown/pages/typescript.md:
--------------------------------------------------------------------------------
1 | # TypeScript
2 |
3 | Reach Router may be used with [TypeScript](https://www.typescriptlang.org/).
4 |
5 | ## Install Typings
6 |
7 | ```sh
8 | npm install @types/reach__router --save-dev
9 | # or
10 | yarn add @types/reach__router --dev
11 | ```
12 |
13 | ## Rendering
14 |
15 | To set props like `path` and `default` on routes, use the `RouteComponentProps` interface.
16 |
17 | ```tsx
18 | import * as React from "react"
19 | import { render } from "react-dom"
20 | import { Router, RouteComponentProps, Link } from "@reach/router"
21 |
22 | let Home = (props: RouteComponentProps) =>
Home
23 | let Dash = (props: RouteComponentProps) =>
Dash
24 |
25 | render(
26 |
27 |
28 |
29 |
30 | )
31 | ```
32 |
33 | ## Parse Data From the URL
34 |
35 | To access data parsed from the URL, create a new interface that extends `RouteComponentProps`, and add each of the URL's dynamic segments as a new prop. These props should be optional and typed as `string`.
36 |
37 | ```tsx
38 | // at url "/invoice/23"
39 |
40 | render(
41 |
42 |
43 |
44 |
45 | )
46 |
47 | interface InvoiceProps extends RouteComponentProps
48 | {
49 | invoiceId?: string;
50 | }
51 |
52 | const Invoice = (props: InvoiceProps) => (
53 |
54 |
Invoice {props.invoiceId}
55 |
56 | )
57 | ```
58 |
--------------------------------------------------------------------------------
/website/src/markdown/pages/server-config.md:
--------------------------------------------------------------------------------
1 | # Server Configuration
2 |
3 | If your app works fine until you hit "reload" or manually type in a URL and then get a 404 error, then your server is not configured correctly.
4 |
5 | Whether you are server-rendering or not, all apps using Reach Router need to be configured to deliver the same JavaScript code at every URL.
6 |
7 | For non-server rendered apps, we recommend develping with [create react app](https://github.com/facebook/create-react-app), and for your production file server [we recommend `serve`](https://github.com/zeit/serve#readme).
8 |
9 | If you can't use either of these tools, you will need to learn how to configure your server to serve your `index.html` file at every url.
10 |
11 | Here's an example in express:
12 |
13 | ```js
14 | const path = require("path")
15 | const express = require("express")
16 | const app = new express()
17 |
18 | // requests for static files in the "public" directory
19 | // like JavaScript, CSS, images will be served
20 | app.use(express.static("public"))
21 |
22 | // Every other request will send the index.html file that
23 | // contains your application
24 | app.use("*", function(req, resp) {
25 | resp.sendFile("/public/index.html")
26 | })
27 |
28 | app.listen("8000")
29 | ```
30 |
31 | Paul Sherman has written an in-depth article about this, if you're still unclear we recommend you give it a read: [Single-Page Applications and the Server](https://blog.pshrmn.com/single-page-applications-and-the-server/)
32 |
--------------------------------------------------------------------------------
/website/src/markdown/api/useMatch.md:
--------------------------------------------------------------------------------
1 | # useMatch
2 |
3 | Matches a path to the location. Matching is relative to any parent Routers, but not parent match's, because they render even if they don't match.
4 |
5 | This API requires a hook-compatible version of React.
6 |
7 | ```jsx
8 | import { useMatch } from "@reach/router"
9 |
10 | const App = () => {
11 | const match = useMatch('/hot/:item');
12 |
13 | return match ? (
14 |
Hot {match.item}
15 | ) : (
16 |
Uncool
17 | )
18 | )
19 | ```
20 |
21 | `useMatch` will return `null` if your path does not match the location. If it does match it will contain:
22 |
23 | - `uri`
24 | - `path`
25 | - `:params`
26 |
27 | ## match\[param\]: string
28 |
29 | Any params in your the path will be parsed and passed as `match[param]` to your callback.
30 |
31 | ```jsx
32 | const match = useMatch("events/:eventId")
33 |
34 | props.match ? props.match.eventId : "No match"
35 | ```
36 |
37 | ## match.uri: string
38 |
39 | The portion of the URI that matched. If you pass a wildcard path, the wildcard portion will not be included. Not sure how this is useful for a `Match`, but it's critical for how focus managment works, so we might as well pass it on to Match if we pass it on to Route Components!
40 |
41 | ```jsx
42 | // URL: /somewhere/deep/i/mean/really/deep
43 | const match = useMatch("/somewhere/deep/*")
44 |
45 | return
{match.uri}
46 | ```
47 |
48 | ## match.path: string
49 |
50 | The path you passed in as a prop.
51 |
--------------------------------------------------------------------------------
/website/src/markdown/api/isRedirect.md:
--------------------------------------------------------------------------------
1 | # isRedirect(error)
2 |
3 | Returns true if the error is a redirect request. Useful for server rendering and rethrowing errors in `componentDidCatch`.
4 |
5 | ## componentDidCatch
6 |
7 | If you're using `componentDidCatch` in your app you _must_ check if the error is a redirect, and if it is, rethrow it, otherwise the app will not redirect. Even better, you should check if the error is the kind you want to catch and rethrow if not.
8 |
9 | ```jsx
10 | import { isRedirect } from "@reach/router"
11 |
12 | class Decent extends React.Component {
13 | componentDidCatch(error) {
14 | if (isRedirect(error)) {
15 | throw error
16 | } else {
17 | // do whatever you were going to do
18 | }
19 | }
20 | }
21 | ```
22 |
23 | Maybe one day we'll get pattern matching and a two-pass try/catch but those are just dreams in Sebastian Markbåge's head.
24 |
25 | ## Server Rendering
26 |
27 | If your app redirects while server rendering it will throw an error. Use `isRedirect` to decide how to handle the error. If it's a redirect, then redirect on your server, otherwise do what you normally do with errors.
28 |
29 | ```jsx
30 | let markup
31 | try {
32 | markup = renderToString(
33 |
34 |
35 |
36 | )
37 | } catch (error) {
38 | if (isRedirect(error)) {
39 | res.redirect(error.uri)
40 | } else {
41 | // ..
42 | }
43 | }
44 | ```
45 |
46 | Please see the [Server Rendering](../server-rendering) doc for the full server rendering story.
47 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "website",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "webpack",
8 | "start": "webpack-dev-server --open",
9 | "now-start": "serve dist --single"
10 | },
11 | "devDependencies": {
12 | "@babel/core": "^7.0.0-beta.46",
13 | "@babel/preset-env": "^7.0.0-beta.46",
14 | "babel-core": "^6.26.3",
15 | "babel-loader": "^8.0.0-beta",
16 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
17 | "babel-plugin-transform-class-properties": "^6.24.1",
18 | "babel-plugin-transform-react-jsx": "^6.24.1",
19 | "cheerio": "^1.0.0-rc.2",
20 | "clean-webpack-plugin": "^0.1.19",
21 | "prettier": "^1.12.1",
22 | "react-hot-loader": "^4.1.2",
23 | "webpack": "^4.6.0",
24 | "webpack-cli": "^2.1.2",
25 | "webpack-dev-server": "^3.1.4"
26 | },
27 | "dependencies": {
28 | "@babel/preset-react": "^7.0.0-beta.46",
29 | "@reach/router": "latest",
30 | "@reactions/component": "^2.0.2",
31 | "glamor": "^2.20.40",
32 | "html-webpack-plugin": "^3.2.0",
33 | "markdown-it": "^8.4.1",
34 | "object-assign": "^4.1.1",
35 | "prismjs": "^1.21.0",
36 | "promise": "^8.0.1",
37 | "prop-types": "15",
38 | "react": "16.4",
39 | "react-dom": "16.4",
40 | "react-media": "^1.8.0",
41 | "scroll-into-view-if-needed": "^2.2.7",
42 | "serve": "^10.1.2",
43 | "uglifyjs-webpack-plugin": "^1.2.5",
44 | "unified": "^6.2.0",
45 | "unist-util-visit": "^1.3.1"
46 | },
47 | "prettier": {}
48 | }
49 |
--------------------------------------------------------------------------------
/src/lib/__snapshots__/utils.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`match works 1`] = `
4 | Object {
5 | "params": Object {
6 | "bar": "hello",
7 | },
8 | "route": Object {
9 | "path": "/foo/:bar",
10 | },
11 | "uri": "/foo/hello",
12 | }
13 | `;
14 |
15 | exports[`pick dynamic segments + splat return value 1`] = `
16 | Object {
17 | "params": Object {
18 | "*": "some/deep/path",
19 | "userId": "ryan",
20 | },
21 | "route": Object {
22 | "path": "/users/:userId/files/*",
23 | },
24 | "uri": "/users/ryan/files",
25 | }
26 | `;
27 |
28 | exports[`pick pick /* 1`] = `
29 | Object {
30 | "params": Object {
31 | "*": "whatever/else",
32 | },
33 | "route": Object {
34 | "path": "/*",
35 | },
36 | "uri": "/",
37 | }
38 | `;
39 |
40 | exports[`pick pick return value 1`] = `
41 | Object {
42 | "params": Object {
43 | "five": "five",
44 | "four": "four",
45 | "one": "one",
46 | "three": "three",
47 | "two": "two",
48 | },
49 | "route": Object {
50 | "path": "/:one/:two/:three/:four/:five",
51 | "value": "Fiver",
52 | },
53 | "uri": "/one/two/three/four/five",
54 | }
55 | `;
56 |
57 | exports[`pick query strings 1`] = `
58 | Object {
59 | "params": Object {
60 | "userId": "ryan",
61 | },
62 | "route": Object {
63 | "path": "/users/:userId",
64 | },
65 | "uri": "/users/ryan",
66 | }
67 | `;
68 |
69 | exports[`pick splat return value 1`] = `
70 | Object {
71 | "params": Object {
72 | "*": "some/deep/path",
73 | },
74 | "route": Object {
75 | "path": "/files/*",
76 | "value": "FilesDeep",
77 | },
78 | "uri": "/files",
79 | }
80 | `;
81 |
--------------------------------------------------------------------------------
/website/src/markdown/pages/ranking.md:
--------------------------------------------------------------------------------
1 | # Path Ranking
2 |
3 | Reach router ranks your paths so you don't have to worry about the order of your paths or pass in props to help it know how to match. Generally, you shouldn't have to worry about this at all, just don't think about it and it will do exactly what you want it to.
4 |
5 | However, I know you're a programmer and so you want to know how it works.
6 |
7 | A path is assigned a score based on the value of each segment in the path. The path with the highest score that matches the location wins.
8 |
9 | * Each segment gets 4 points and then...
10 | * Static segments get 3 more points
11 | * Dynamic segments 2 more
12 | * Root segments 1
13 | * and finally wildcard segments get a 1 point penalty
14 |
15 | Here's a "table" showing different paths and their scores:
16 |
17 | | Score | Path |
18 | | ----- | -------------------------------- |
19 | | 5 | `/` |
20 | | 7 | `/groups` |
21 | | 13 | `/groups/:groupId` |
22 | | 14 | `/groups/mine` |
23 | | 19 | `/groups/:groupId/users/\*` |
24 | | 20 | `/groups/:groupId/users` |
25 | | 21 | `/groups/mine/users` |
26 | | 24 | `/:one/:two/:three/:four` |
27 | | 26 | `/groups/:groupId/users/:userId` |
28 | | 27 | `/groups/:groupId/users/me` |
29 | | 28 | `/groups/mine/users/me` |
30 | | 30 | `/:one/:two/:three/:four/:five` |
31 |
32 | A URL like "/groups/mine" matches both paths "/groups/:groupId" and "/groups/mine", but "/groups/mine" has 14 points while the other only has 13, so "/groups/mine" wins.
33 |
--------------------------------------------------------------------------------
/examples/crud/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | margin: 0;
4 | }
5 |
6 | .App {
7 | height: 100vh;
8 | display: flex;
9 | }
10 |
11 | .primary-nav {
12 | width: 200px;
13 | border-right: solid 1px #ccc;
14 | overflow: auto;
15 | position: relative;
16 | }
17 |
18 | .nav-link {
19 | display: block;
20 | padding: 10px 15px;
21 | font-size: 120%;
22 | text-decoration: none;
23 | border-bottom: solid 1px #eee;
24 | color: rgb(0, 0, 238);
25 | }
26 |
27 | .nav-link.active,
28 | .nav-link:active {
29 | color: red;
30 | }
31 |
32 | .main-content {
33 | padding: 0px 20px;
34 | }
35 |
36 | /* heck yes I copy pasted this from stack overflow */
37 | /* https://stackoverflow.com/questions/1367409/how-to-make-button-look-like-a-link */
38 | .text-button {
39 | align-items: normal;
40 | background-color: rgba(0, 0, 0, 0);
41 | border-color: rgb(0, 0, 238);
42 | border-style: none;
43 | box-sizing: content-box;
44 | color: rgb(0, 0, 238);
45 | cursor: pointer;
46 | display: inline;
47 | font: inherit;
48 | height: auto;
49 | padding: 0;
50 | perspective-origin: 0 0;
51 | text-align: start;
52 | text-decoration: underline;
53 | transform-origin: 0 0;
54 | width: auto;
55 | -moz-appearance: none;
56 | -webkit-logical-height: 1em;
57 | -webkit-logical-width: auto;
58 | }
59 |
60 | @supports (-moz-appearance: none) {
61 | button::-moz-focus-inner {
62 | border: none;
63 | padding: 0;
64 | }
65 | button:focus {
66 | outline-style: dotted;
67 | outline-width: 1px;
68 | }
69 | }
70 |
71 | .edit {
72 | color: rgb(0, 0, 238);
73 | }
74 |
75 | .Field {
76 | display: block;
77 | margin: 1.2em 0;
78 | }
79 |
--------------------------------------------------------------------------------
/website/src/markdown/tutorial/09-navigate.md:
--------------------------------------------------------------------------------
1 | # Tutorial - Navigating Imperatively
2 |
3 | Sometimes you need to navigate in response to something other than the user clicking on a link. For this we have `navigate`. Let's import `navigate`.
4 |
5 | ```jsx
6 | import {
7 | Router,
8 | Link,
9 | navigate
10 | } from "@reach/router"
11 | ```
12 |
13 | Probably the most common reason to use `navigate` is a form submission. Perhaps the user submits a form, you save some data, and then navigate to the record. Let's add this form to `Invoices`:
14 |
15 | ```jsx
16 | const Invoices = props => (
17 |
41 | )
42 | ```
43 |
44 | Go ahead and submit the form and watch the router navigate to the new invoice.
45 |
46 | Oh, one more thing. Route Components get a `navigate` prop. This version of the function is aware of its position in the hierarchy. This means you can navigate to relative paths the same way you can link to them.
47 |
48 | Go ahead and remove the spot where we imported navigate and let's use the prop instead:
49 |
50 | ```jsx
51 | navigate(`/invoices/${id}`)
52 | // becomes
53 | props.navigate(id)
54 | ```
55 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v1.3.4
2 |
3 | - d7e4cfc Support forwarding url params with wildcard paths
4 |
5 | ## v1.3.3
6 |
7 | _1.3.2 was a failed publish_
8 |
9 | - 8b93694 Fix an encoding issue with browsers who do not encode urls
10 | - d0962d7 Fix a bug on specific vendors that do not ship `location.pathname`
11 | - 244e2bf Improve compatibility for `createMemorySource`
12 |
13 | ## v1.3.1
14 |
15 | - f40ea53 Fix an accidental breaking change not exposing all location properties
16 |
17 | ## v1.3.0
18 |
19 | - 56d4dca Added 4 new Hook APIs!
20 | - `useLocation`
21 | - `useParams`
22 | - `useNavigate`
23 | - `useMatch`
24 | - 15298df Improved displayName for react context
25 | - 11e9ed6 Fixed a bug that pushed to history when a user clicks the same path
26 | - ccfc3c8 Added support for trailing wildcard names e.g., `path="/files/*filePath"`
27 | - ad52cd3 Upgraded create-react-context to an MIT license compatible version
28 | - 0a8af93 Fixed a bug with checking own property on locations
29 | - 28a79e7 Fixed `search` not being prepended with a `?`
30 | - 77fa233 Added displayName to Link
31 |
32 | ## v1.2.1
33 |
34 | - 1f9f908 replace unstable_deferredUpdates with rAF
35 |
36 | ## v1.2.0
37 |
38 | - a7a1c84 fixed focus being stolen from [autofocus] elements
39 | - 8a56262 Added `hash` and `search` to server location
40 | - 1c13f8a Added history "PUSH" and "POP" actions to listener callback
41 | - 534f67c Added globalHistory to index exports
42 | - 8cb6e83 allow falsey children in Router
43 |
44 | ## v1.1.1
45 |
46 | - e0338b5c Removed vestigial dependencies from package.json
47 | - fix/automated linting
48 |
49 | ## v1.1.0
50 |
51 | - 53f06958 Added ref forwarding and `innerRef` fallback
52 |
53 | ## v1.0.0
54 |
55 | - Added literally everything.
56 |
--------------------------------------------------------------------------------
/examples/crud/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/examples/crud/src/utils.js:
--------------------------------------------------------------------------------
1 | const sleep = (ms = 1000) => new Promise(res => setTimeout(res, ms));
2 |
3 | const API = `https://contacts.now.sh`;
4 | // const API = `http://localhost:5000`;
5 |
6 | let token = null;
7 |
8 | export const login = () =>
9 | new Promise(async (res, rej) => {
10 | token = localStorage.getItem("token");
11 | if (token) {
12 | res(token);
13 | } else {
14 | token = prompt("Give me a token, anything will do!");
15 | if (token.trim() === "")
16 | token = Math.random()
17 | .toString(32)
18 | .substr(2, 8);
19 | localStorage.setItem("token", token);
20 | // fake some async waiting
21 | await sleep();
22 | res(token);
23 | }
24 | });
25 |
26 | const fetchContacts = async (url, opts = { headers: {} }) => {
27 | return fetch(`${API}${url}`, {
28 | ...opts,
29 | headers: { authorization: token, ...opts.headers }
30 | }).then(res => {
31 | if (res.ok) {
32 | return res.json();
33 | } else {
34 | return res;
35 | }
36 | });
37 | };
38 |
39 | export const getContacts = () => fetchContacts("/contacts");
40 |
41 | export const getContact = id => fetchContacts(`/contacts/${id}`);
42 |
43 | export const updateContact = (id, contact) =>
44 | fetchContacts(`/contacts/${id}`, {
45 | method: "put",
46 | headers: { "Content-Type": "application/json" },
47 | body: JSON.stringify({ contact })
48 | });
49 |
50 | export const deleteContact = id =>
51 | fetchContacts(`/contacts/${id}`, {
52 | method: "delete"
53 | });
54 |
55 | export const createContact = contact =>
56 | fetchContacts("/contacts", {
57 | method: "post",
58 | headers: { "Content-Type": "application/json" },
59 | body: JSON.stringify({ contact })
60 | });
61 |
--------------------------------------------------------------------------------
/website/src/markdown/api/LocationProvider.md:
--------------------------------------------------------------------------------
1 | # LocationProvider
2 |
3 | Sets up a listener to location changes. The primary API's that need the location automatically set this up for you. This is mostly useful for testing, and if we ever decide to get into React Native, it'll be useful there too.
4 |
5 | ```jsx
6 | import {
7 | createMemorySource,
8 | createHistory,
9 | LocationProvider
10 | } from "@reach/router"
11 |
12 | let history = createHistory(window)
13 |
14 | // for some types of tests you want a memory source
15 | let source = createMemorySource("/starting/url")
16 | let history = createHistory(source)
17 |
18 | let App = () => (
19 |
20 |
21 | Alright, we've established some location
22 | context
23 |
24 |
25 | )
26 | ```
27 |
28 | ## history: object (optional)
29 |
30 | The history to listen to. Defaults to the browser history or a memory history if a DOM is not found.
31 |
32 | ```jsx
33 | import { createHistory, LocationProvider } from '@reach/router'
34 | let history = createHistory(window)
35 |
36 |
37 | ```
38 |
39 | ## children: element
40 |
41 | You can pass elements as children to wrap an app in location context.
42 |
43 | ```jsx
44 |
45 |
This is fine
46 |
47 | ```
48 |
49 | ## children: func
50 |
51 | If you pass a child render function `LocationProvider` will pass you the context it creates: `location` and `navigate`. If you want access to these values somewhere arbitrary in your app, use [Location](Location) instead.
52 |
53 | ```jsx
54 |
55 | {context => {
56 | console.log(context.location)
57 | console.log(context.navigate)
58 | }}
59 |
60 | ```
61 |
--------------------------------------------------------------------------------
/website/src/markdown/pages/accessibility.md:
--------------------------------------------------------------------------------
1 | # Accessibility
2 |
3 | Accessibility is a first-class concern for Reach Router, so, naturally, it's baked in.
4 |
5 | ## Links
6 |
7 | Links in a client rendered app should behave just like links in a server rendered app. Reach Router does this automatically for you:
8 |
9 | - Keyboard access
10 | - Assistive devices announce correctly
11 | - Command/Control click opens in a new tab
12 | - Right click "open in new window" opens in a new window
13 |
14 | ## Focus Management
15 |
16 | Whenever the content of a page changes in response to a user interaction, the focus should be moved to that content; otherwise, users on assistive devices have to search around the page to find what changed--yuck! Without the help of a router, managing focus on route transitions requires a lot effort and knowledge on your part.
17 |
18 | Reach Router provides out-of-the-box focus management so your apps are significantly more accessible without you breaking a sweat.
19 |
20 | When the location changes, the top-most part of your application that changed is identified and focus is moved to it.
21 |
22 | ## Focus Management
23 |
24 | Prior to version `1.3`, we used `role="group"` on the top-level element so that screen readers would announce to the user the focused group's nested elements similarly to how it works when a user loads a page for the first time. A problem we found is that some screen readers (notably VoiceOver and NVDA with Firefox) will read the group's content as if it's a long string, void of any important context provided by the interior markup.
25 |
26 | While we removed the group role as the router's default setting, if you believe the group role creates a better experience for your application you can still pass it as a prop to the `Router` component and it will be forwarded to the top-level element and function the same as before.
27 |
--------------------------------------------------------------------------------
/website/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const UglifyJSPlugin = require("uglifyjs-webpack-plugin");
3 | const path = require("path");
4 | const HtmlWebpackPlugin = require("html-webpack-plugin");
5 | const CleanWebpackPlugin = require("clean-webpack-plugin");
6 |
7 | const PROD = process.env.NODE_ENV === "production";
8 |
9 | module.exports = {
10 | entry: {
11 | app: [require.resolve("./src/polyfills"), "./src/index.js"],
12 | vendor: ["react", "react-dom"]
13 | },
14 | mode: PROD ? "production" : "development",
15 | devtool: PROD ? undefined : "inline-source-map",
16 | devServer: {
17 | contentBase: "./dist",
18 | hot: true,
19 | historyApiFallback: true
20 | },
21 | output: {
22 | filename: "static/js/[name].[hash:8].js",
23 | chunkFilename: "static/js/[name].[chunkhash:8].chunk.js",
24 | path: path.resolve(__dirname, "dist"),
25 | publicPath: PROD ? "/router/" : "/"
26 | },
27 | optimization: {
28 | splitChunks: {
29 | chunks: "all"
30 | }
31 | },
32 | plugins: [
33 | new CleanWebpackPlugin(["dist"]),
34 | new HtmlWebpackPlugin({
35 | title: "Reach Router: Next Generation Routing for React",
36 | template: "src/index.html"
37 | }),
38 | new webpack.NamedModulesPlugin(),
39 | new webpack.DefinePlugin({
40 | BASEPATH: JSON.stringify(PROD ? "/router" : "/"),
41 | VERSION: JSON.stringify(require("../package.json").version)
42 | }),
43 | new webpack.ProvidePlugin({
44 | Glamor: "glamor/react"
45 | })
46 | ].concat(
47 | PROD ? [new UglifyJSPlugin()] : [new webpack.HotModuleReplacementPlugin()]
48 | ),
49 | module: {
50 | rules: [
51 | {
52 | test: /\.js$/,
53 | exclude: /(node_modules)/,
54 | use: "babel-loader"
55 | },
56 | {
57 | test: /\.md$/,
58 | use: path.resolve("build/markdown-loader.js")
59 | }
60 | ]
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/website/src/markdown/pages/credits.md:
--------------------------------------------------------------------------------
1 | # Credits and Trade-offs
2 |
3 | Hey, it's Ryan Florence here.
4 |
5 | ## Credits
6 |
7 | ### React Router
8 |
9 | In May of 2014 I created the first version of React Router and was involved with the project all the way to the end of 2017 at version 4. Because of that, Reach Router is clearly influenced by React Router and all of the ideas of the contributors who built it.
10 |
11 | To me, Reach Router is everything I missed about v3 and everything I love about v4, plus a few things I've always wanted a router in React to have, particularly focus management and relative links. I want a more accessible web, especially in React.
12 |
13 | ### Ember + Preact Router
14 |
15 | When multiple routes match the location, Ember and Preact Router pick the route that you intend to match, rather than relying on order or props like React Router v4's `exact`. The path ranking feature of Reach Router was inspired by them both.
16 |
17 | ## Trade-offs (mostly compared to React Router)
18 |
19 | * Size. I'm aiming to come in under 4kb for a modern React app (where API polyfills aren't needed). That makes some extra features harder to include.
20 |
21 | * No complex route patterns. There are no optional params, or anything like them, it's just static paths, params, and trailing wildcard.
22 |
23 | * No history blocking. I found that the only use-case I had was preventing the user from navigating away from a half-filled out form. Not only is it pretty easy to just save the form state to session storage and bring it back when they return, but history blocking doesn't happen when you navigate away from the app (say to another domain). This kept me from actually using history blocking and always opting to save the form state to session storage.
24 |
25 | * No React Native support. Not yet anyway. An important goal is "accessible by default". Assuming a DOM makes this goal easier to attain and maintain. Also, there's a sea of navigation projects for React Native that I'm not interested in swimming in! Maybe in the future, though.
26 |
--------------------------------------------------------------------------------
/website/src/markdown/api/Redirect.md:
--------------------------------------------------------------------------------
1 | # Redirect
2 |
3 | Redirects from one path to another. Use this when you want to change a URL without breaking links to the old path.
4 |
5 | ```jsx
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 | ```
17 |
18 | It doesn't have to be a child of a Router, it can also be rendered anywhere in your app when you want to declaratively redirect.
19 |
20 | ```jsx
21 | class Home extends React.Component {
22 | state = {
23 | user: null
24 | }
25 |
26 | async componentDidMount() {
27 | let user = await fetchUser()
28 | this.setState({ user })
29 | }
30 |
31 | render() {
32 | if (this.state.user.hasBetaEnabled) {
33 | return
34 | } else {
35 | return
36 | }
37 | }
38 | }
39 | ```
40 |
41 | ## from: string
42 |
43 | Only used when rendered inside of a ``. This indicates which path to redirect from, note the parameters must match the `to` prop's parameters.
44 |
45 | ```jsx
46 |
50 | ```
51 |
52 | ## to: string
53 |
54 | This indicates which path to redirect to, note the parameters must match the `from` prop's parameters.
55 |
56 | ```jsx
57 |
61 | ```
62 |
63 | ## noThrow
64 |
65 | ```jsx
66 |
67 | ```
68 |
69 | Redirect works with `componentDidCatch` to prevent the tree from rendering and starts over with a new location.
70 |
71 | Because React doesn't swallow the error this might bother you. For example, a redirect will trigger Create React App's error overlay. In production, everything is fine. If it bothers you, add `noThrow` and Redirect will do redirect without using `componentDidCatch`.
72 |
73 | If you're using React < 16 Redirect will not throw at all, regardless of what value you put for this prop.
74 |
75 | If you're using `componentDidCatch` in your app please read the [isRedirect](isRedirect) doc!
76 |
--------------------------------------------------------------------------------
/examples/crud/config/paths.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const path = require("path");
4 | const fs = require("fs");
5 | const url = require("url");
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith("/");
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right