12 | Open the network tab as you navigate. Notice that only the amount of
13 | your app that is required is actually downloaded as you navigate
14 | around. Even the route configuration objects are loaded on the fly.
15 | This way, a new route added deep in your app will not affect the
16 | initial bundle of your application.
17 |
18 |
Courses
{' '}
19 |
20 | {courses.map(course => (
21 |
22 | {course.name}
23 |
24 | ))}
25 |
26 |
27 | )
28 | }
29 | }
30 |
31 | export default Dashboard
32 |
--------------------------------------------------------------------------------
/modules/index.js:
--------------------------------------------------------------------------------
1 | /* components */
2 | export Router from './Router'
3 | export Link from './Link'
4 | export IndexLink from './IndexLink'
5 |
6 | /* components (configuration) */
7 | export IndexRedirect from './IndexRedirect'
8 | export IndexRoute from './IndexRoute'
9 | export Redirect from './Redirect'
10 | export Route from './Route'
11 |
12 | /* mixins */
13 | export History from './History'
14 | export Lifecycle from './Lifecycle'
15 | export RouteContext from './RouteContext'
16 |
17 | /* utils */
18 | export useRoutes from './useRoutes'
19 | export { createRoutes } from './RouteUtils'
20 | export RouterContext from './RouterContext'
21 | export RoutingContext from './RoutingContext'
22 | export PropTypes from './PropTypes'
23 | export match from './match'
24 | export useRouterHistory from './useRouterHistory'
25 | export { formatPattern } from './PatternUtils'
26 |
27 | /* histories */
28 | export browserHistory from './browserHistory'
29 | export hashHistory from './hashHistory'
30 | export createMemoryHistory from './createMemoryHistory'
31 |
--------------------------------------------------------------------------------
/modules/PropTypes.js:
--------------------------------------------------------------------------------
1 | import { PropTypes } from 'react'
2 |
3 | const { func, object, arrayOf, oneOfType, element, shape, string } = PropTypes
4 |
5 | export function falsy(props, propName, componentName) {
6 | if (props[propName])
7 | return new Error(`<${componentName}> should not have a "${propName}" prop`)
8 | }
9 |
10 | export const history = shape({
11 | listen: func.isRequired,
12 | pushState: func.isRequired,
13 | replaceState: func.isRequired,
14 | go: func.isRequired
15 | })
16 |
17 | export const location = shape({
18 | pathname: string.isRequired,
19 | search: string.isRequired,
20 | state: object,
21 | action: string.isRequired,
22 | key: string
23 | })
24 |
25 | export const component = oneOfType([ func, string ])
26 | export const components = oneOfType([ component, object ])
27 | export const route = oneOfType([ object, element ])
28 | export const routes = oneOfType([ route, arrayOf(route) ])
29 |
30 | export default {
31 | falsy,
32 | history,
33 | location,
34 | component,
35 | components,
36 | route
37 | }
38 |
--------------------------------------------------------------------------------
/modules/useRoutes.js:
--------------------------------------------------------------------------------
1 | import useQueries from 'history/lib/useQueries'
2 |
3 | import createTransitionManager from './createTransitionManager'
4 | import warning from './routerWarning'
5 |
6 | /**
7 | * Returns a new createHistory function that may be used to create
8 | * history objects that know about routing.
9 | *
10 | * Enhances history objects with the following methods:
11 | *
12 | * - listen((error, nextState) => {})
13 | * - listenBeforeLeavingRoute(route, (nextLocation) => {})
14 | * - match(location, (error, redirectLocation, nextState) => {})
15 | * - isActive(pathname, query, indexOnly=false)
16 | */
17 | function useRoutes(createHistory) {
18 | warning(
19 | false,
20 | '`useRoutes` is deprecated. Please use `createTransitionManager` instead.'
21 | )
22 |
23 | return function ({ routes, ...options } = {}) {
24 | const history = useQueries(createHistory)(options)
25 | const transitionManager = createTransitionManager(history, routes)
26 | return { ...history, ...transitionManager }
27 | }
28 | }
29 |
30 | export default useRoutes
31 |
--------------------------------------------------------------------------------
/modules/deprecateObjectProperties.js:
--------------------------------------------------------------------------------
1 | /*eslint no-empty: 0*/
2 | import warning from './routerWarning'
3 |
4 | let useMembrane = false
5 |
6 | if (__DEV__) {
7 | try {
8 | if (Object.defineProperty({}, 'x', { get() { return true } }).x) {
9 | useMembrane = true
10 | }
11 | } catch(e) { }
12 | }
13 |
14 | // wraps an object in a membrane to warn about deprecated property access
15 | export default function deprecateObjectProperties(object, message) {
16 | if (!useMembrane)
17 | return object
18 |
19 | const membrane = {}
20 |
21 | for (let prop in object) {
22 | if (typeof object[prop] === 'function') {
23 | membrane[prop] = function () {
24 | warning(false, message)
25 | return object[prop].apply(object, arguments)
26 | }
27 | } else {
28 | Object.defineProperty(membrane, prop, {
29 | configurable: false,
30 | enumerable: false,
31 | get() {
32 | warning(false, message)
33 | return object[prop]
34 | }
35 | })
36 | }
37 | }
38 |
39 | return membrane
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Ryan Florence, Michael Jackson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/huge-apps/routes/Course/components/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 |
4 | const styles = {}
5 |
6 | styles.nav = {
7 | borderBottom: '1px solid #aaa'
8 | }
9 |
10 | styles.link = {
11 | display: 'inline-block',
12 | padding: 10,
13 | textDecoration: 'none'
14 | }
15 |
16 | styles.activeLink = {
17 | ...styles.link,
18 | color: 'red'
19 | }
20 |
21 | class Nav extends React.Component {
22 | render() {
23 | const { course } = this.props
24 | const pages = [
25 | [ 'announcements', 'Announcements' ],
26 | [ 'assignments', 'Assignments' ],
27 | [ 'grades', 'Grades' ]
28 | ]
29 |
30 | return (
31 |
41 | )
42 | }
43 | }
44 |
45 | export default Nav
46 |
--------------------------------------------------------------------------------
/examples/auth-flow/auth.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | login(email, pass, cb) {
3 | cb = arguments[arguments.length - 1]
4 | if (localStorage.token) {
5 | if (cb) cb(true)
6 | this.onChange(true)
7 | return
8 | }
9 | pretendRequest(email, pass, (res) => {
10 | if (res.authenticated) {
11 | localStorage.token = res.token
12 | if (cb) cb(true)
13 | this.onChange(true)
14 | } else {
15 | if (cb) cb(false)
16 | this.onChange(false)
17 | }
18 | })
19 | },
20 |
21 | getToken() {
22 | return localStorage.token
23 | },
24 |
25 | logout(cb) {
26 | delete localStorage.token
27 | if (cb) cb()
28 | this.onChange(false)
29 | },
30 |
31 | loggedIn() {
32 | return !!localStorage.token
33 | },
34 |
35 | onChange() {}
36 | }
37 |
38 | function pretendRequest(email, pass, cb) {
39 | setTimeout(() => {
40 | if (email === 'joe@example.com' && pass === 'password1') {
41 | cb({
42 | authenticated: true,
43 | token: Math.random().toString(36).substring(7)
44 | })
45 | } else {
46 | cb({ authenticated: false })
47 | }
48 | }, 0)
49 | }
50 |
--------------------------------------------------------------------------------
/examples/webpack.config.js:
--------------------------------------------------------------------------------
1 | /*eslint-disable no-var */
2 | var fs = require('fs')
3 | var path = require('path')
4 | var webpack = require('webpack')
5 |
6 | module.exports = {
7 |
8 | devtool: 'inline-source-map',
9 |
10 | entry: fs.readdirSync(__dirname).reduce(function (entries, dir) {
11 | if (fs.statSync(path.join(__dirname, dir)).isDirectory())
12 | entries[dir] = path.join(__dirname, dir, 'app.js')
13 |
14 | return entries
15 | }, {}),
16 |
17 | output: {
18 | path: __dirname + '/__build__',
19 | filename: '[name].js',
20 | chunkFilename: '[id].chunk.js',
21 | publicPath: '/__build__/'
22 | },
23 |
24 | module: {
25 | loaders: [
26 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
27 | { test: /\.css$/, loader: 'style!css' }
28 | ]
29 | },
30 |
31 | resolve: {
32 | alias: {
33 | 'react-router': path.join(__dirname, '..', 'modules')
34 | }
35 | },
36 |
37 | plugins: [
38 | new webpack.optimize.CommonsChunkPlugin('shared.js'),
39 | new webpack.DefinePlugin({
40 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
41 | })
42 | ]
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/examples/huge-apps/stubs/COURSES.js:
--------------------------------------------------------------------------------
1 | global.COURSES = [
2 | {
3 | id: 0,
4 | name: 'React Fundamentals',
5 | grade: 'B',
6 | announcements: [
7 | {
8 | id: 0,
9 | title: 'No class tomorrow',
10 | body: 'There is no class tomorrow, please do not show up'
11 | }
12 | ],
13 | assignments: [
14 | {
15 | id: 0,
16 | title: 'Build a router',
17 | body: 'It will be easy, seriously, like 2 hours, 100 lines of code, no biggie',
18 | grade: 'N/A'
19 | }
20 | ]
21 |
22 | },
23 |
24 | {
25 | id: 1,
26 | name: 'Reusable React Components',
27 | grade: 'A-',
28 | announcements: [
29 | {
30 | id: 0,
31 | title: 'Final exam next wednesday',
32 | body: 'You had better prepare'
33 | }
34 | ],
35 | assignments: [
36 | {
37 | id: 0,
38 | title: 'PropTypes',
39 | body: 'They aren\'t for you.',
40 | grade: '80%'
41 | },
42 | {
43 | id: 1,
44 | title: 'Iterating and Cloning Children',
45 | body: 'You can totally do it.',
46 | grade: '95%'
47 | }
48 | ]
49 | }
50 | ]
51 |
--------------------------------------------------------------------------------
/modules/__tests__/_bc-History-test.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect'
2 | import React from 'react'
3 | import { render, unmountComponentAtNode } from 'react-dom'
4 | import History from '../History'
5 | import Router from '../Router'
6 | import Route from '../Route'
7 | import createHistory from 'history/lib/createMemoryHistory'
8 |
9 | // skipping to remove warnings, and we don't intent to update this mixin
10 | // keeping tests here just in-case
11 | describe('v1 History Mixin', function () {
12 |
13 | let node
14 | beforeEach(function () {
15 | node = document.createElement('div')
16 | })
17 |
18 | afterEach(function () {
19 | unmountComponentAtNode(node)
20 | })
21 |
22 | it('assigns the history to the component instance', function (done) {
23 | const history = createHistory('/')
24 |
25 | const Component = React.createClass({
26 | mixins: [ History ],
27 | componentWillMount() {
28 | expect(this.history).toExist()
29 | },
30 | render() { return null }
31 | })
32 |
33 | render((
34 |
35 |
36 |
37 | ), node, done)
38 | })
39 |
40 | })
41 |
--------------------------------------------------------------------------------
/examples/huge-apps/routes/Course/components/Course.js:
--------------------------------------------------------------------------------
1 | /*globals COURSES:true */
2 | import React from 'react'
3 | import Dashboard from './Dashboard'
4 | import Nav from './Nav'
5 |
6 | const styles = {}
7 |
8 | styles.sidebar = {
9 | float: 'left',
10 | width: 200,
11 | padding: 20,
12 | borderRight: '1px solid #aaa',
13 | marginRight: 20
14 | }
15 |
16 | class Course extends React.Component {
17 | render() {
18 | let { sidebar, main, children, params } = this.props
19 | let course = COURSES[params.courseId]
20 |
21 | let content
22 | if (sidebar && main) {
23 | content = (
24 |
73 | )
74 | }
75 | })
76 |
77 | render((
78 |
79 |
80 |
81 |
82 |
83 |
84 | ), document.getElementById('example'))
85 |
--------------------------------------------------------------------------------
/modules/match.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant'
2 |
3 | import createMemoryHistory from './createMemoryHistory'
4 | import createTransitionManager from './createTransitionManager'
5 | import { createRoutes } from './RouteUtils'
6 | import { createRouterObject, createRoutingHistory } from './RouterUtils'
7 |
8 | /**
9 | * A high-level API to be used for server-side rendering.
10 | *
11 | * This function matches a location to a set of routes and calls
12 | * callback(error, redirectLocation, renderProps) when finished.
13 | *
14 | * Note: You probably don't want to use this in a browser unless you're using
15 | * server-side rendering with async routes.
16 | */
17 | function match({ history, routes, location, ...options }, callback) {
18 | invariant(
19 | history || location,
20 | 'match needs a history or a location'
21 | )
22 |
23 | history = history ? history : createMemoryHistory(options)
24 | const transitionManager = createTransitionManager(
25 | history,
26 | createRoutes(routes)
27 | )
28 |
29 | let unlisten
30 |
31 | if (location) {
32 | // Allow match({ location: '/the/path', ... })
33 | location = history.createLocation(location)
34 | } else {
35 | // Pick up the location from the history via synchronous history.listen
36 | // call if needed.
37 | unlisten = history.listen(historyLocation => {
38 | location = historyLocation
39 | })
40 | }
41 |
42 | const router = createRouterObject(history, transitionManager)
43 | history = createRoutingHistory(history, transitionManager)
44 |
45 | transitionManager.match(location, function (error, redirectLocation, nextState) {
46 | callback(
47 | error,
48 | redirectLocation,
49 | nextState && {
50 | ...nextState,
51 | history,
52 | router,
53 | matchContext: { history, transitionManager, router }
54 | }
55 | )
56 |
57 | // Defer removing the listener to here to prevent DOM histories from having
58 | // to unwind DOM event listeners unnecessarily, in case callback renders a
59 | // and attaches another history listener.
60 | if (unlisten) {
61 | unlisten()
62 | }
63 | })
64 | }
65 |
66 | export default match
67 |
--------------------------------------------------------------------------------
/modules/Lifecycle.js:
--------------------------------------------------------------------------------
1 | import warning from './routerWarning'
2 | import React from 'react'
3 | import invariant from 'invariant'
4 |
5 | const { object } = React.PropTypes
6 |
7 | /**
8 | * The Lifecycle mixin adds the routerWillLeave lifecycle method to a
9 | * component that may be used to cancel a transition or prompt the user
10 | * for confirmation.
11 | *
12 | * On standard transitions, routerWillLeave receives a single argument: the
13 | * location we're transitioning to. To cancel the transition, return false.
14 | * To prompt the user for confirmation, return a prompt message (string).
15 | *
16 | * During the beforeunload event (assuming you're using the useBeforeUnload
17 | * history enhancer), routerWillLeave does not receive a location object
18 | * because it isn't possible for us to know the location we're transitioning
19 | * to. In this case routerWillLeave must return a prompt message to prevent
20 | * the user from closing the window/tab.
21 | */
22 | const Lifecycle = {
23 |
24 | contextTypes: {
25 | history: object.isRequired,
26 | // Nested children receive the route as context, either
27 | // set by the route component using the RouteContext mixin
28 | // or by some other ancestor.
29 | route: object
30 | },
31 |
32 | propTypes: {
33 | // Route components receive the route object as a prop.
34 | route: object
35 | },
36 |
37 | componentDidMount() {
38 | warning(false, 'the `Lifecycle` mixin is deprecated, please use `context.router.setRouteLeaveHook(route, hook)`. http://tiny.cc/router-lifecyclemixin')
39 | invariant(
40 | this.routerWillLeave,
41 | 'The Lifecycle mixin requires you to define a routerWillLeave method'
42 | )
43 |
44 | const route = this.props.route || this.context.route
45 |
46 | invariant(
47 | route,
48 | 'The Lifecycle mixin must be used on either a) a or ' +
49 | 'b) a descendant of a that uses the RouteContext mixin'
50 | )
51 |
52 | this._unlistenBeforeLeavingRoute = this.context.history.listenBeforeLeavingRoute(
53 | route,
54 | this.routerWillLeave
55 | )
56 | },
57 |
58 | componentWillUnmount() {
59 | if (this._unlistenBeforeLeavingRoute)
60 | this._unlistenBeforeLeavingRoute()
61 | }
62 |
63 | }
64 |
65 | export default Lifecycle
66 |
--------------------------------------------------------------------------------
/docs/guides/RouteMatching.md:
--------------------------------------------------------------------------------
1 | # Route Matching
2 |
3 | A [route](/docs/Glossary.md#route) has three attributes that determine whether or not it "matches" the URL:
4 |
5 | 1. [nesting](#nesting) and
6 | 2. its [`path`](#path-syntax)
7 | 3. its [precedence](#precedence)
8 |
9 | ### Nesting
10 | React Router uses the concept of nested routes to let you declare nested sets of views that should be rendered when a given URL is invoked. Nested routes are arranged in a tree-like structure. To find a match, React Router traverses the [route config](/docs/Glossary.md#routeconfig) depth-first searching for a route that matches the URL.
11 |
12 | ### Path Syntax
13 | A route path is [a string pattern](/docs/Glossary.md#routepattern) that is used to match a URL (or a portion of one). Route paths are interpreted literally, except for the following special symbols:
14 |
15 | - `:paramName` – matches a URL segment up to the next `/`, `?`, or `#`. The matched string is called a [param](/docs/Glossary.md#params)
16 | - `()` – Wraps a portion of the URL that is optional
17 | - `*` – Matches all characters (non-greedy) up to the next character in the pattern, or to the end of the URL if there is none, and creates a `splat` [param](/docs/Glossary.md#params)
18 | - `**` - Matches all characters (greedy) until the next `/`, `?`, or `#` and creates a `splat` [param](/docs/Glossary.md#params)
19 |
20 | ```js
21 | // matches /hello/michael and /hello/ryan
22 | // matches /hello, /hello/michael, and /hello/ryan
23 | // matches /files/hello.jpg and /files/hello.html
24 | // matches /files/hello.jpg and /files/path/to/file.jpg
25 | ```
26 |
27 | If a route uses a relative `path`, it builds upon the accumulated `path` of its ancestors. Nested routes may opt-out of this behavior by [using an absolute `path`](RouteConfiguration.md#decoupling-the-ui-from-the-url).
28 |
29 | ### Precedence
30 | Finally, the routing algorithm attempts to match routes in the order they are defined, top to bottom. So, when you have two sibling routes you should be sure the first doesn't match all possible `path`s that can be matched by the later sibling. For example, **don't** do this:
31 |
32 | ```js
33 |
34 |
35 | ```
36 |
--------------------------------------------------------------------------------
/modules/computeChangedRoutes.js:
--------------------------------------------------------------------------------
1 | import { getParamNames } from './PatternUtils'
2 |
3 | function routeParamsChanged(route, prevState, nextState) {
4 | if (!route.path)
5 | return false
6 |
7 | const paramNames = getParamNames(route.path)
8 |
9 | return paramNames.some(function (paramName) {
10 | return prevState.params[paramName] !== nextState.params[paramName]
11 | })
12 | }
13 |
14 | /**
15 | * Returns an object of { leaveRoutes, changeRoutes, enterRoutes } determined by
16 | * the change from prevState to nextState. We leave routes if either
17 | * 1) they are not in the next state or 2) they are in the next state
18 | * but their params have changed (i.e. /users/123 => /users/456).
19 | *
20 | * leaveRoutes are ordered starting at the leaf route of the tree
21 | * we're leaving up to the common parent route. enterRoutes are ordered
22 | * from the top of the tree we're entering down to the leaf route.
23 | *
24 | * changeRoutes are any routes that didn't leave or enter during
25 | * the transition.
26 | */
27 | function computeChangedRoutes(prevState, nextState) {
28 | const prevRoutes = prevState && prevState.routes
29 | const nextRoutes = nextState.routes
30 |
31 | let leaveRoutes, changeRoutes, enterRoutes
32 | if (prevRoutes) {
33 | let parentIsLeaving = false
34 | leaveRoutes = prevRoutes.filter(function (route) {
35 | if (parentIsLeaving) {
36 | return true
37 | } else {
38 | const isLeaving = nextRoutes.indexOf(route) === -1 || routeParamsChanged(route, prevState, nextState)
39 | if (isLeaving)
40 | parentIsLeaving = true
41 | return isLeaving
42 | }
43 | })
44 |
45 | // onLeave hooks start at the leaf route.
46 | leaveRoutes.reverse()
47 |
48 | enterRoutes = []
49 | changeRoutes = []
50 |
51 | nextRoutes.forEach(function (route) {
52 | const isNew = prevRoutes.indexOf(route) === -1
53 | const paramsChanged = leaveRoutes.indexOf(route) !== -1
54 |
55 | if (isNew || paramsChanged)
56 | enterRoutes.push(route)
57 | else
58 | changeRoutes.push(route)
59 | })
60 |
61 | } else {
62 | leaveRoutes = []
63 | changeRoutes = []
64 | enterRoutes = nextRoutes
65 | }
66 |
67 | return {
68 | leaveRoutes,
69 | changeRoutes,
70 | enterRoutes
71 | }
72 | }
73 |
74 | export default computeChangedRoutes
75 |
--------------------------------------------------------------------------------
/examples/sidebar/data.js:
--------------------------------------------------------------------------------
1 | const data = [
2 | {
3 | name: 'Tacos',
4 | description: 'A taco (/ˈtækoʊ/ or /ˈtɑːkoʊ/) is a traditional Mexican dish composed of a corn or wheat tortilla folded or rolled around a filling. A taco can be made with a variety of fillings, including beef, pork, chicken, seafood, vegetables and cheese, allowing for great versatility and variety. A taco is generally eaten without utensils and is often accompanied by garnishes such as salsa, avocado or guacamole, cilantro (coriander), tomatoes, minced meat, onions and lettuce.',
5 | items: [
6 | { name: 'Carne Asada', price: 7 },
7 | { name: 'Pollo', price: 6 },
8 | { name: 'Carnitas', price: 6 }
9 | ]
10 | },
11 | {
12 | name: 'Burgers',
13 | description: 'A hamburger (also called a beef burger, hamburger sandwich, burger or hamburg) is a sandwich consisting of one or more cooked patties of ground meat, usually beef, placed inside a sliced bun. Hamburgers are often served with lettuce, bacon, tomato, onion, pickles, cheese and condiments such as mustard, mayonnaise, ketchup, relish, and green chile.',
14 | items: [
15 | { name: 'Buffalo Bleu', price: 8 },
16 | { name: 'Bacon', price: 8 },
17 | { name: 'Mushroom and Swiss', price: 6 }
18 | ]
19 | },
20 | {
21 | name: 'Drinks',
22 | description: 'Drinks, or beverages, are liquids intended for human consumption. In addition to basic needs, beverages form part of the culture of human society. Although all beverages, including juice, soft drinks, and carbonated drinks, have some form of water in them, water itself is often not classified as a beverage, and the word beverage has been recurrently defined as not referring to water.',
23 | items: [
24 | { name: 'Lemonade', price: 3 },
25 | { name: 'Root Beer', price: 4 },
26 | { name: 'Iron Port', price: 5 }
27 | ]
28 | }
29 | ]
30 |
31 | const dataMap = data.reduce(function (map, category) {
32 | category.itemsMap = category.items.reduce(function (itemsMap, item) {
33 | itemsMap[item.name] = item
34 | return itemsMap
35 | }, {})
36 | map[category.name] = category
37 | return map
38 | }, {})
39 |
40 | exports.getAll = function () {
41 | return data
42 | }
43 |
44 | exports.lookupCategory = function (name) {
45 | return dataMap[name]
46 | }
47 |
48 | exports.lookupItem = function (category, item) {
49 | return dataMap[category].itemsMap[item]
50 | }
51 |
--------------------------------------------------------------------------------
/examples/active-links/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { Router, Route, IndexRoute, Link, IndexLink, browserHistory } from 'react-router'
4 |
5 | const ACTIVE = { color: 'red' }
6 |
7 | class App extends React.Component {
8 | render() {
9 | return (
10 |
79 | )
80 | }
81 | }
82 |
83 | render((
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | ), document.getElementById('example'))
95 |
96 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, and in the interest of
4 | fostering an open and welcoming community, we pledge to respect all people who
5 | contribute through reporting issues, posting feature requests, updating
6 | documentation, submitting pull requests or patches, and other activities.
7 |
8 | We are committed to making participation in this project a harassment-free
9 | experience for everyone, regardless of level of experience, gender, gender
10 | identity and expression, sexual orientation, disability, personal appearance,
11 | body size, race, ethnicity, age, religion, or nationality.
12 |
13 | Examples of unacceptable behavior by participants include:
14 |
15 | * The use of sexualized language or imagery
16 | * Personal attacks
17 | * Trolling or insulting/derogatory comments
18 | * Public or private harassment
19 | * Publishing other's private information, such as physical or electronic
20 | addresses, without explicit permission
21 | * Other unethical or unprofessional conduct
22 |
23 | Project maintainers have the right and responsibility to remove, edit, or
24 | reject comments, commits, code, wiki edits, issues, and other contributions
25 | that are not aligned to this Code of Conduct, or to ban temporarily or
26 | permanently any contributor for other behaviors that they deem inappropriate,
27 | threatening, offensive, or harmful.
28 |
29 | By adopting this Code of Conduct, project maintainers commit themselves to
30 | fairly and consistently applying these principles to every aspect of managing
31 | this project. Project maintainers who do not follow or enforce the Code of
32 | Conduct may be permanently removed from the project team.
33 |
34 | This Code of Conduct applies both within project spaces and in public spaces
35 | when an individual is representing the project or its community.
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
38 | reported by contacting a project maintainer at rpflorence@gmail.com. All
39 | complaints will be reviewed and investigated and will result in a response that
40 | is deemed necessary and appropriate to the circumstances. Maintainers are
41 | obligated to maintain confidentiality with regard to the reporter of an
42 | incident.
43 |
44 |
45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
46 | version 1.3.0, available at
47 | [http://contributor-covenant.org/version/1/3/0/][version]
48 |
49 | [homepage]: http://contributor-covenant.org
50 | [version]: http://contributor-covenant.org/version/1/3/0/
51 |
52 |
--------------------------------------------------------------------------------
/modules/Redirect.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import invariant from 'invariant'
3 | import { createRouteFromReactElement } from './RouteUtils'
4 | import { formatPattern } from './PatternUtils'
5 | import { falsy } from './PropTypes'
6 |
7 | const { string, object } = React.PropTypes
8 |
9 | /**
10 | * A is used to declare another URL path a client should
11 | * be sent to when they request a given URL.
12 | *
13 | * Redirects are placed alongside routes in the route configuration
14 | * and are traversed in the same manner.
15 | */
16 | const Redirect = React.createClass({
17 |
18 | statics: {
19 |
20 | createRouteFromReactElement(element) {
21 | const route = createRouteFromReactElement(element)
22 |
23 | if (route.from)
24 | route.path = route.from
25 |
26 | route.onEnter = function (nextState, replace) {
27 | const { location, params } = nextState
28 |
29 | let pathname
30 | if (route.to.charAt(0) === '/') {
31 | pathname = formatPattern(route.to, params)
32 | } else if (!route.to) {
33 | pathname = location.pathname
34 | } else {
35 | let routeIndex = nextState.routes.indexOf(route)
36 | let parentPattern = Redirect.getRoutePattern(nextState.routes, routeIndex - 1)
37 | let pattern = parentPattern.replace(/\/*$/, '/') + route.to
38 | pathname = formatPattern(pattern, params)
39 | }
40 |
41 | replace({
42 | pathname,
43 | query: route.query || location.query,
44 | state: route.state || location.state
45 | })
46 | }
47 |
48 | return route
49 | },
50 |
51 | getRoutePattern(routes, routeIndex) {
52 | let parentPattern = ''
53 |
54 | for (let i = routeIndex; i >= 0; i--) {
55 | const route = routes[i]
56 | const pattern = route.path || ''
57 |
58 | parentPattern = pattern.replace(/\/*$/, '/') + parentPattern
59 |
60 | if (pattern.indexOf('/') === 0)
61 | break
62 | }
63 |
64 | return '/' + parentPattern
65 | }
66 |
67 | },
68 |
69 | propTypes: {
70 | path: string,
71 | from: string, // Alias for path
72 | to: string.isRequired,
73 | query: object,
74 | state: object,
75 | onEnter: falsy,
76 | children: falsy
77 | },
78 |
79 | /* istanbul ignore next: sanity check */
80 | render() {
81 | invariant(
82 | false,
83 | ' elements are for router configuration only and should not be rendered'
84 | )
85 | }
86 |
87 | })
88 |
89 | export default Redirect
90 |
--------------------------------------------------------------------------------
/docs/guides/IndexRoutes.md:
--------------------------------------------------------------------------------
1 | # Index Routes and Index Links
2 |
3 | ## Index Routes
4 |
5 | To illustrate the use case for `IndexRoute`, imagine the following route
6 | config without it:
7 |
8 | ```js
9 |
10 |
11 |
12 |
13 |
14 |
15 | ```
16 |
17 | When the user visits `/`, the App component is rendered, but none of the
18 | children are, so `this.props.children` inside of `App` will be undefined.
19 | To render some default UI you could easily do `{this.props.children ||
20 | }`.
21 |
22 | But now `Home` can't participate in routing, like the `onEnter` hooks,
23 | etc. You render in the same position as `Accounts` and `Statements`, so
24 | the router allows you to have `Home` be a first class route component with
25 | `IndexRoute`.
26 |
27 | ```js
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ```
36 |
37 | Now `App` can render `{this.props.children}` and we have a first-class
38 | route for `Home` that can participate in routing.
39 |
40 | ## Index Redirects
41 |
42 | Suppose your basic route configuration looks like:
43 |
44 | ```js
45 |
46 |
47 |
48 |
49 | ```
50 |
51 | Suppose you want to redirect `/` to `/welcome`. To do this, you need to set up
52 | an index route that does the redirect. To do this, use the ``
53 | component:
54 |
55 | ```js
56 |
57 |
58 |
59 |
60 |
61 | ```
62 |
63 | This is equivalent to setting up an index route with just an `onEnter` hook
64 | that redirects the user. You would set this up with plain routes as:
65 |
66 | ```js
67 | const routes = [{
68 | path: '/',
69 | component: App,
70 | indexRoute: { onEnter: (nextState, replace) => replace('/welcome') },
71 | childRoutes: [
72 | { path: 'welcome', component: Welcome },
73 | { path: 'about', component: About }
74 | ]
75 | }]
76 | ```
77 |
78 | ## Index Links
79 |
80 | If you were to `Home` in this app, it would always
81 | be active since every URL starts with `/`. This is a problem because
82 | we'd like to link to `Home` but only be active if `Home` is rendered.
83 |
84 | To have a link to `/` that is only active when the `Home` route is
85 | rendered, use `Home`.
86 |
--------------------------------------------------------------------------------
/examples/animations/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
4 | import { browserHistory, Router, Route, IndexRoute, Link } from 'react-router'
5 | import './app.css'
6 |
7 | class App extends React.Component {
8 | render() {
9 | return (
10 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.