react-router
Advanced tools
Comparing version 1.0.0-beta4 to 1.0.0-rc1
@@ -0,1 +1,24 @@ | ||
v1.0.0-rc1 - Fri, 11 Sep 2015 21:35:18 GMT | ||
------------------------------------------ | ||
- [5fbe933](../../commit/5fbe933) [changed] Do not add "active" class by default | ||
- [85c699c](../../commit/85c699c) [changed] State -> IsActive | ||
- [94509e7](../../commit/94509e7) [added] IndexLink | ||
- [adc0a2f](../../commit/adc0a2f) [added] IndexRoute | ||
- [b86509a](../../commit/b86509a) [added] useRoutes history enhancer [added] RoutingContext component [added] RouteContext mixin [added] Lifecycle mixin | ||
- [e72812d](../../commit/e72812d) [added] <Router initialState> | ||
- [4c6dc1b](../../commit/4c6dc1b) [fixed] Installing on Windows | ||
- [042cffc](../../commit/042cffc) [changed] Removed histories/added history dep | ||
- [af7eb55](../../commit/af7eb55) [added] History.onBeforeChange | ||
- [f4ed900](../../commit/f4ed900) [fixed] correctly updates the window scroll position | ||
- [587e54f](../../commit/587e54f) [added] static `history` singleton getter for `HashHistory` and `BrowserHistory` | ||
- [5bd62b5](../../commit/5bd62b5) [fixed] errors in examples | ||
- [4e2ca3c](../../commit/4e2ca3c) [fixed] URI escape path components with special chars | ||
- [0630488](../../commit/0630488) [fixed] Link module adds extra space | ||
- [26400c1](../../commit/26400c1) [fixed] Use encodeURI for splat params | ||
- [178efc3](../../commit/178efc3) [fixed] <Link href> when using HashHistory | ||
- [41bd525](../../commit/41bd525) [fixed] Properly escape splats | ||
- [4759961](../../commit/4759961) [fixed] URLUtils recognize values containing \n | ||
v1.0.0-beta4 - Mon, 31 Aug 2015 06:19:34 GMT | ||
@@ -2,0 +25,0 @@ -------------------------------------------- |
@@ -1,8 +0,10 @@ | ||
## Glossary | ||
# Glossary | ||
This is a glossary of common terms used in the React Router codebase and documentation listed in alphabetical order, along with their [type signatures](http://flowtype.org/docs/quick-reference.html). | ||
### Action | ||
## Action | ||
type Action = 'PUSH' | 'REPLACE' | 'POP'; | ||
```js | ||
type Action = 'PUSH' | 'REPLACE' | 'POP'; | ||
``` | ||
@@ -15,13 +17,17 @@ An *action* describes the type of change to a URL. Possible values are: | ||
### Component | ||
## Component | ||
type Component = ReactClass | string; | ||
```js | ||
type Component = ReactClass | string; | ||
``` | ||
A *component* is a React component class or a string (e.g. "div"). Basically, it's anything that can be used as the first argument to [`React.createElement`](https://facebook.github.io/react/docs/top-level-api.html#react.createelement). | ||
### EnterHook | ||
## EnterHook | ||
type EnterHook = (nextState: RouterState, redirectTo: RedirectFunction, callback?: Function) => any; | ||
```js | ||
type EnterHook = (nextState: RouterState, replaceState: RedirectFunction, callback?: Function) => any; | ||
``` | ||
An *enter hook* is a user-defined function that is called when a route is about to be rendered. It receives the next [router state](#routerstate) as its first argument. The [`redirectTo` function](#redirectfunction) may be used to trigger a transition to a different URL. | ||
An *enter hook* is a user-defined function that is called when a route is about to be rendered. It receives the next [router state](#routerstate) as its first argument. The [`replaceState` function](#redirectfunction) may be used to trigger a transition to a different URL. | ||
@@ -32,18 +38,22 @@ If an enter hook needs to execute asynchronously, it may list a 3rd `callback` argument that it must call in order to cause the transition to proceed. | ||
### LeaveHook | ||
## LeaveHook | ||
type LeaveHook = () => any; | ||
```js | ||
type LeaveHook = () => any; | ||
``` | ||
A *leave hook* is a user-defined function that is called when a route is about to be unmounted. | ||
### Location | ||
## Location | ||
type Location = { | ||
pathname: Pathname; | ||
search: QueryString; | ||
query: Query; | ||
state: LocationState; | ||
action: Action; | ||
key: LocationKey; | ||
}; | ||
```js | ||
type Location = { | ||
pathname: Pathname; | ||
search: QueryString; | ||
query: Query; | ||
state: LocationState; | ||
action: Action; | ||
key: LocationKey; | ||
}; | ||
``` | ||
@@ -57,11 +67,15 @@ A *location* answers two important (philosophical) questions: | ||
### LocationKey | ||
## LocationKey | ||
type LocationKey = string; | ||
```js | ||
type LocationKey = string; | ||
``` | ||
A *location key* is a string that is unique to a particular [`location`](#location). It is the one piece of data that most accurately answers the question "Where am I?". | ||
### LocationState | ||
## LocationState | ||
type LocationState = ?Object; | ||
```js | ||
type LocationState = ?Object; | ||
``` | ||
@@ -75,46 +89,60 @@ A *location state* is an arbitrary object of data associated with a particular [`location`](#location). This is basically a way to tie extra state to a location that is not contained in the URL. | ||
### Path | ||
## Path | ||
type Path = Pathname + QueryString; | ||
```js | ||
type Path = Pathname + QueryString; | ||
``` | ||
A *path* represents a URL path. | ||
### Pathname | ||
## Pathname | ||
type Pathname = string; | ||
```js | ||
type Pathname = string; | ||
``` | ||
A *pathname* is the portion of a URL that describes a hierarchical path, including the preceeding `/`. For example, in `http://example.com/the/path?the=query`, `/the/path` is the pathname. It is synonymous with `window.location.pathname` in web browsers. | ||
### QueryString | ||
## QueryString | ||
type QueryString = string; | ||
```js | ||
type QueryString = string; | ||
``` | ||
A *query string* is the portion of the URL that follows the [pathname](#pathname), including any preceeding `?`. For example, in `http://example.com/the/path?the=query`, `?the=query` is the query string. It is synonymous with `window.location.search` in web browsers. | ||
### Query | ||
## Query | ||
type Query = Object; | ||
```js | ||
type Query = Object; | ||
``` | ||
A *query* is the parsed version of a [query string](#querystring). | ||
### Params | ||
## Params | ||
type Params = Object; | ||
```js | ||
type Params = Object; | ||
``` | ||
The word *params* refers to an object of key/value pairs that were parsed out of the original URL's [pathname](#pathname). The values of this object are typically strings, unless there is more than one param with the same name in which case the value is an array. | ||
### RedirectFunction | ||
## RedirectFunction | ||
type RedirectFunction = (pathname: Pathname | Path, query: ?Query, state: ?LocationState) => void; | ||
```js | ||
type RedirectFunction = (pathname: Pathname | Path, query: ?Query, state: ?LocationState) => void; | ||
``` | ||
A *redirect function* is used in [`onEnter` hooks](#enterhook) to trigger a transition to a new URL. | ||
### Route | ||
## Route | ||
type Route = { | ||
component: RouteComponent; | ||
path: ?RoutePattern; | ||
onEnter: ?EnterHook; | ||
onLeave: ?LeaveHook; | ||
}; | ||
```js | ||
type Route = { | ||
component: RouteComponent; | ||
path: ?RoutePattern; | ||
onEnter: ?EnterHook; | ||
onLeave: ?LeaveHook; | ||
}; | ||
``` | ||
@@ -125,5 +153,7 @@ A *route* specifies a [component](#component) that is part of the user interface (UI). Routes should be nested in a tree-like structure that follows the hierarchy of your components. | ||
### RouteComponent | ||
## RouteComponent | ||
type RouteComponent = Component; | ||
```js | ||
type RouteComponent = Component; | ||
``` | ||
@@ -133,3 +163,3 @@ The term *route component* refers to a [component](#component) that is directly rendered by a [route](#route) (i.e. the `<Route component>`). The router creates elements from route components and provides them as `this.props.children` to route components further up the hierarchy. In addition to `children`, route components receive the following props: | ||
- `router` – The [router](#router) instance | ||
- `location` – The current [location](#location) | ||
- `location` – The current [location](#location) | ||
- `params` – The current [params](#params) | ||
@@ -139,17 +169,23 @@ - `route` – The [route](#route) that declared this component | ||
### RouteConfig | ||
## RouteConfig | ||
type RouteConfig = Array<Route>; | ||
```js | ||
type RouteConfig = Array<Route>; | ||
``` | ||
A *route config* is an array of [route](#route)s that specifies the order in which routes should be tried when the router attempts to match a URL. | ||
### RouteHook | ||
## RouteHook | ||
type RouteHook = (nextLocation?: Location) => any; | ||
```js | ||
type RouteHook = (nextLocation?: Location) => any; | ||
``` | ||
A *route hook* is a function that is used to prevent the user from leaving a route. On normal transitions, it receives the next [location](#location) as an argument and must either `return false` to cancel the transition or `return` a prompt message to show the user. When invoked during the `beforeunload` event in web browsers, it does not receive any arguments and must `return` a prompt message to cancel the transition. | ||
### RoutePattern | ||
## RoutePattern | ||
type RoutePattern = string; | ||
```js | ||
type RoutePattern = string; | ||
``` | ||
@@ -164,12 +200,14 @@ A *route pattern* (or "path") is a string that describes a portion of a URL. Patterns are compiled into functions that are used to try and match a URL. Patterns may use the following special characters: | ||
### Router | ||
## Router | ||
type Router = { | ||
transitionTo: (location: Location) => void; | ||
pushState: (state: ?LocationState, pathname: Pathname | Path, query?: Query) => void; | ||
replaceState: (state: ?LocationState, pathname: Pathname | Path, query?: Query) => void; | ||
go(n: Number) => void; | ||
listen(listener: RouterListener) => Function; | ||
match(location: Location, callback: RouterListener) => void; | ||
}; | ||
```js | ||
type Router = { | ||
transitionTo: (location: Location) => void; | ||
pushState: (state: ?LocationState, pathname: Pathname | Path, query?: Query) => void; | ||
replaceState: (state: ?LocationState, pathname: Pathname | Path, query?: Query) => void; | ||
go(n: Number) => void; | ||
listen(listener: RouterListener) => Function; | ||
match(location: Location, callback: RouterListener) => void; | ||
}; | ||
``` | ||
@@ -183,16 +221,20 @@ A *router* is a [`history`](http://rackt.github.io/history) object (akin to `window.history` in web browsers) that is used to modify and listen for changes to the URL. | ||
### RouterListener | ||
## RouterListener | ||
type RouterListener = (error: ?Error, nextState: RouterState) => void; | ||
```js | ||
type RouterListener = (error: ?Error, nextState: RouterState) => void; | ||
``` | ||
A *router listener* is a function that is used to listen for changes to a [router](#router)'s [state](#routerstate). | ||
### RouterState | ||
## RouterState | ||
type RouterState = { | ||
location: Location; | ||
routes: Array<Route>; | ||
params: Params; | ||
components: Array<Component>; | ||
}; | ||
```js | ||
type RouterState = { | ||
location: Location; | ||
routes: Array<Route>; | ||
params: Params; | ||
components: Array<Component>; | ||
}; | ||
``` | ||
@@ -204,2 +246,2 @@ A *router state* represents the current state of a router. It contains: | ||
- an object of [`params`](#params) that were parsed out of the URL, and | ||
- an array of [`components`](#component) that will be rendered to the page in hierarchical order. | ||
- an array of [`components`](#component) that will be rendered to the page in hierarchical order. |
## Table of Contents | ||
- [Introduction](Introduction.md) | ||
- [Route Configuration](RouteConfiguration.md) | ||
- [Route Matching](RouteMatching.md) | ||
- [Dynamic Routing](DynamicRouting.md) | ||
- [Confirming Navigation](ConfirmingNavigation.md) | ||
- [Glossary](Glossary.md) | ||
- [Route](Route.md) | ||
- [Server Rendering](ServerRendering.md) | ||
* [Read Me](/README.md) | ||
* [Introduction](/docs/introduction/README.md) | ||
* [Motivation](/docs/introduction/Motivation.md) | ||
* [Principles](/docs/introduction/Principles.md) | ||
* [Basics](/docs/basics/README.md) | ||
* [Route Configuration](/docs/basics/RouteConfiguration.md) | ||
* [Route Matching](/docs/basics/RouteMatching.md) | ||
* [Histories](/docs/basics/Histories.md) | ||
* [Advanced](/docs/advanced/README.md) | ||
* [Dynamic Routing](/docs/advanced/DynamicRouting.md) | ||
* [Confirming Navigation](/docs/advanced/ConfirmingNavigation.md) | ||
* [Server Rendering](/docs/advanced/ServerRendering.md) | ||
* [Component Lifecycle](/docs/advanced/ComponentLifecycle.md) | ||
* [Recipes](/docs/recipes/README.md) | ||
* [Upgrade Guide](/UPGRADE_GUIDE.md) | ||
* [Troubleshooting](/docs/Troubleshooting.md) | ||
* [Glossary](/docs/Glossary.md) | ||
* [API Reference](/docs/api/README.md) | ||
* [Components](/docs/api/README.md#components) | ||
* [Router](/docs/api/Router.md) | ||
* [Link](/docs/api/Link.md) | ||
* [IndexLink](/docs/api/IndexLink.md) | ||
* [RoutingContext](/docs/api/RoutingContext.md) | ||
* [Configuration components](/docs/api/README.md#configuration-components) | ||
* [Route](/docs/api/Route.md) | ||
* [Redirect](/docs/api/Redirect.md) | ||
* [IndexRoute](/docs/api/IndexRoute.md) | ||
* [Mixins](/docs/api/README.md#mixins) | ||
* [Lifecycle](/docs/api/Lifecycle.md) | ||
* [Navigation](/docs/api/Navigation.md) | ||
* [RouteContext](/docs/api/RouteContext.md) | ||
* [IsActive](/docs/api/IsActive.md) | ||
* [ScrollManagmentMixin](/docs/api/ScrollManagmentMixin.md) | ||
* [Utilities](/docs/api/README.md#utilities) | ||
* [useRoutes](/docs/api/useRoutes.md) | ||
* [createRoutes](/docs/api/createRoutes.md) | ||
* [PropTypes](/docs/api/PropTypes.md) |
@@ -7,9 +7,9 @@ 'use strict'; | ||
function getComponentsForRoute(route, callback) { | ||
function getComponentsForRoute(location, route, callback) { | ||
if (route.component || route.components) { | ||
callback(null, route.component || route.components); | ||
} else if (route.getComponent) { | ||
route.getComponent(callback); | ||
route.getComponent(location, callback); | ||
} else if (route.getComponents) { | ||
route.getComponents(callback); | ||
route.getComponents(location, callback); | ||
} else { | ||
@@ -27,5 +27,5 @@ callback(); | ||
*/ | ||
function getComponents(routes, callback) { | ||
_AsyncUtils.mapAsync(routes, function (route, index, callback) { | ||
getComponentsForRoute(route, callback); | ||
function getComponents(nextState, callback) { | ||
_AsyncUtils.mapAsync(nextState.routes, function (route, index, callback) { | ||
getComponentsForRoute(nextState.location, route, callback); | ||
}, callback); | ||
@@ -32,0 +32,0 @@ } |
@@ -42,2 +42,8 @@ /* components */ | ||
var _History2 = require('./History'); | ||
var _History3 = _interopRequireDefault(_History2); | ||
exports.History = _History3['default']; | ||
var _Lifecycle2 = require('./Lifecycle'); | ||
@@ -49,8 +55,2 @@ | ||
var _Navigation2 = require('./Navigation'); | ||
var _Navigation3 = _interopRequireDefault(_Navigation2); | ||
exports.Navigation = _Navigation3['default']; | ||
var _RouteContext2 = require('./RouteContext'); | ||
@@ -62,8 +62,2 @@ | ||
var _State2 = require('./State'); | ||
var _State3 = _interopRequireDefault(_State2); | ||
exports.State = _State3['default']; | ||
/* utils */ | ||
@@ -79,4 +73,10 @@ | ||
exports.createRoutesFromReactChildren = _RouteUtils.createRoutesFromReactChildren; | ||
exports.createRoutes = _RouteUtils.createRoutes; | ||
var _RoutingContext2 = require('./RoutingContext'); | ||
var _RoutingContext3 = _interopRequireDefault(_RoutingContext2); | ||
exports.RoutingContext = _RoutingContext3['default']; | ||
var _PropTypes2 = require('./PropTypes'); | ||
@@ -88,4 +88,10 @@ | ||
var _match2 = require('./match'); | ||
var _match3 = _interopRequireDefault(_match2); | ||
exports.match = _match3['default']; | ||
var _Router4 = _interopRequireDefault(_Router2); | ||
exports['default'] = _Router4['default']; |
@@ -13,4 +13,2 @@ 'use strict'; | ||
var _PropTypes = require('./PropTypes'); | ||
var _Link = require('./Link'); | ||
@@ -17,0 +15,0 @@ |
@@ -15,2 +15,6 @@ 'use strict'; | ||
var _warning = require('warning'); | ||
var _warning2 = _interopRequireDefault(_warning); | ||
var _RouteUtils = require('./RouteUtils'); | ||
@@ -37,3 +41,3 @@ | ||
} else { | ||
warning(false, 'An <IndexRoute> does not make sense at the root of your route config'); | ||
_warning2['default'](false, 'An <IndexRoute> does not make sense at the root of your route config'); | ||
} | ||
@@ -40,0 +44,0 @@ } |
@@ -18,2 +18,3 @@ 'use strict'; | ||
route = activeRoutes[i]; | ||
if (!route.path) return false; | ||
pattern = route.path || ''; | ||
@@ -20,0 +21,0 @@ |
@@ -21,3 +21,3 @@ 'use strict'; | ||
* the user for confirmation. | ||
* | ||
* | ||
* On standard transitions, routerWillLeave receives a single argument: the | ||
@@ -24,0 +24,0 @@ * location we're transitioning to. To cancel the transition, return false. |
@@ -9,2 +9,4 @@ 'use strict'; | ||
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } | ||
var _react = require('react'); | ||
@@ -32,2 +34,8 @@ | ||
function isEmptyObject(object) { | ||
for (var p in object) if (object.hasOwnProperty(p)) return false; | ||
return true; | ||
} | ||
/** | ||
@@ -70,5 +78,4 @@ * A <Link> is used to create an <a> element that links to a route. | ||
return { | ||
onlyActiveOnIndex: false, | ||
className: '', | ||
activeClassName: 'active', | ||
onlyActiveOnIndex: false, | ||
style: {} | ||
@@ -98,12 +105,15 @@ }; | ||
render: function render() { | ||
var history = this.context.history; | ||
var _props = this.props; | ||
var activeClassName = _props.activeClassName; | ||
var activeStyle = _props.activeStyle; | ||
var onlyActiveOnIndex = _props.onlyActiveOnIndex; | ||
var to = _props.to; | ||
var query = _props.query; | ||
var onlyActiveOnIndex = _props.onlyActiveOnIndex; | ||
var state = _props.state; | ||
var onClick = _props.onClick; | ||
var props = _extends({}, this.props, { | ||
onClick: this.handleClick | ||
}); | ||
var props = _objectWithoutProperties(_props, ['activeClassName', 'activeStyle', 'onlyActiveOnIndex', 'to', 'query', 'state', 'onClick']); | ||
var history = this.context.history; | ||
props.onClick = this.handleClick; | ||
@@ -115,6 +125,8 @@ // Ignore if rendered outside the context | ||
if (history.isActive(to, query, onlyActiveOnIndex)) { | ||
if (props.activeClassName) props.className += props.className !== '' ? ' ' + props.activeClassName : props.activeClassName; | ||
if (activeClassName || activeStyle != null && !isEmptyObject(activeStyle)) { | ||
if (history.isActive(to, query, onlyActiveOnIndex)) { | ||
if (activeClassName) props.className += props.className === '' ? activeClassName : ' ' + activeClassName; | ||
if (props.activeStyle) props.style = _extends({}, props.style, props.activeStyle); | ||
if (activeStyle) props.style = _extends({}, props.style, activeStyle); | ||
} | ||
} | ||
@@ -121,0 +133,0 @@ } |
@@ -9,2 +9,4 @@ 'use strict'; | ||
var _RouteUtils = require('./RouteUtils'); | ||
function getChildRoutes(route, location, callback) { | ||
@@ -14,3 +16,5 @@ if (route.childRoutes) { | ||
} else if (route.getChildRoutes) { | ||
route.getChildRoutes(location, callback); | ||
route.getChildRoutes(location, function (error, childRoutes) { | ||
callback(error, !error && _RouteUtils.createRoutes(childRoutes)); | ||
}); | ||
} else { | ||
@@ -25,3 +29,5 @@ callback(); | ||
} else if (route.getIndexRoute) { | ||
route.getIndexRoute(location, callback); | ||
route.getIndexRoute(location, function (error, indexRoute) { | ||
callback(error, !error && _RouteUtils.createRoutes(indexRoute)[0]); | ||
}); | ||
} else { | ||
@@ -28,0 +34,0 @@ callback(); |
@@ -44,2 +44,11 @@ 'use strict'; | ||
var routes = oneOfType([route, arrayOf(route)]); | ||
exports.routes = routes; | ||
exports.routes = routes; | ||
exports['default'] = { | ||
falsy: falsy, | ||
history: history, | ||
location: location, | ||
component: component, | ||
components: components, | ||
route: route | ||
}; |
@@ -45,3 +45,3 @@ 'use strict'; | ||
route.onEnter = function (nextState, redirectTo) { | ||
route.onEnter = function (nextState, replaceState) { | ||
var location = nextState.location; | ||
@@ -52,3 +52,3 @@ var params = nextState.params; | ||
redirectTo(pathname, route.query || location.query, route.state || location.state); | ||
replaceState(route.state || location.state, pathname, route.query || location.query); | ||
}; | ||
@@ -55,0 +55,0 @@ |
@@ -5,4 +5,2 @@ 'use strict'; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
@@ -14,2 +12,6 @@ | ||
var _warning = require('warning'); | ||
var _warning2 = _interopRequireDefault(_warning); | ||
var _historyLibCreateHashHistory = require('history/lib/createHashHistory'); | ||
@@ -101,2 +103,6 @@ | ||
componentWillReceiveProps: function componentWillReceiveProps(nextProps) { | ||
_warning2['default'](nextProps.history === this.props.history, "The `history` provided to <Router/> has changed, it will be ignored."); | ||
}, | ||
componentWillUnmount: function componentWillUnmount() { | ||
@@ -107,6 +113,19 @@ if (this._unlisten) this._unlisten(); | ||
render: function render() { | ||
return _react2['default'].createElement(_RoutingContext2['default'], _extends({}, this.state, { | ||
var _state = this.state; | ||
var location = _state.location; | ||
var routes = _state.routes; | ||
var params = _state.params; | ||
var components = _state.components; | ||
var createElement = this.props.createElement; | ||
if (location == null) return null; // Async match | ||
return _react2['default'].createElement(_RoutingContext2['default'], { | ||
history: this.history, | ||
createElement: this.props.createElement | ||
})); | ||
createElement: createElement, | ||
location: location, | ||
routes: routes, | ||
params: params, | ||
components: components | ||
}); | ||
} | ||
@@ -113,0 +132,0 @@ |
@@ -26,3 +26,3 @@ 'use strict'; | ||
* A <RoutingContext> renders the component tree for a given router state | ||
* and sets the router object and the current location in context. | ||
* and sets the history object and the current location in context. | ||
*/ | ||
@@ -86,3 +86,4 @@ var RoutingContext = _react2['default'].createClass({ | ||
route: route, | ||
routeParams: routeParams | ||
routeParams: routeParams, | ||
routes: routes | ||
}; | ||
@@ -89,0 +90,0 @@ |
@@ -31,5 +31,5 @@ 'use strict'; | ||
* Runs all onEnter hooks in the given array of routes in order | ||
* with onEnter(nextState, redirectTo, callback) and calls | ||
* with onEnter(nextState, replaceState, callback) and calls | ||
* callback(error, redirectInfo) when finished. The first hook | ||
* to use redirectTo short-circuits the loop. | ||
* to use replaceState short-circuits the loop. | ||
* | ||
@@ -50,3 +50,3 @@ * If a hook needs to run asynchronously, it may use the callback | ||
var redirectInfo; | ||
function redirectTo(pathname, query, state) { | ||
function replaceState(state, pathname, query) { | ||
redirectInfo = { pathname: pathname, query: query, state: state }; | ||
@@ -56,3 +56,3 @@ } | ||
_AsyncUtils.loopAsync(hooks.length, function (index, next, done) { | ||
hooks[index](nextState, redirectTo, function (error) { | ||
hooks[index](nextState, replaceState, function (error) { | ||
if (error || redirectInfo) { | ||
@@ -59,0 +59,0 @@ done(error, redirectInfo); // No need to continue. |
@@ -15,2 +15,4 @@ 'use strict'; | ||
var _historyLibActions = require('history/lib/Actions'); | ||
var _historyLibUseQueries = require('history/lib/useQueries'); | ||
@@ -20,2 +22,6 @@ | ||
var _historyLibCreateLocation = require('history/lib/createLocation'); | ||
var _historyLibCreateLocation2 = _interopRequireDefault(_historyLibCreateLocation); | ||
var _computeChangedRoutes2 = require('./computeChangedRoutes'); | ||
@@ -39,4 +45,11 @@ | ||
function hasAnyProperties(object) { | ||
for (var p in object) if (object.hasOwnProperty(p)) return true; | ||
return false; | ||
} | ||
/** | ||
* Enhances a history object with the following methods: | ||
* Returns a new createHistory function that may be used to create | ||
* history objects that know about routing. | ||
* | ||
@@ -46,4 +59,4 @@ * - isActive(pathname, query) | ||
* - unregisterRouteHook(route, (location) => {}) | ||
* - listen((error, state) => {}) | ||
* - match(location, (error, state) => {}) | ||
* - match(location, (error, nextState, nextLocation) => {}) | ||
* - listen((error, nextState) => {}) | ||
*/ | ||
@@ -66,17 +79,4 @@ function useRoutes(createHistory) { | ||
function matchRoutesWithGuaranteedState(routes, location, callback) { | ||
_matchRoutes2['default'](routes, location, function (error, nextState) { | ||
if (error || nextState) { | ||
callback(error, nextState); | ||
} else { | ||
_warning2['default'](false, 'Location "%s" did not match any routes', location.pathname + location.search); | ||
} | ||
}); | ||
} | ||
var partialNextState = undefined; | ||
// TODO: If we had a way to uniquely identify a route, | ||
// we could use a plain object here instead... | ||
var routeHooks = new Map(); | ||
var partialNextState; | ||
function match(location, callback) { | ||
@@ -87,7 +87,12 @@ if (partialNextState && partialNextState.location === location) { | ||
} else { | ||
matchRoutesWithGuaranteedState(routes, location, function (error, nextState) { | ||
_matchRoutes2['default'](routes, location, function (error, nextState) { | ||
if (error) { | ||
callback(error); | ||
callback(error, null, null); | ||
} else if (nextState) { | ||
finishMatch(_extends({}, nextState, { location: location }), function (err, nextLocation, nextState) { | ||
if (nextState) state = nextState; | ||
callback(err, nextLocation, nextState); | ||
}); | ||
} else { | ||
finishMatch(_extends({}, nextState, { location: location }), callback); | ||
callback(null, null, null); | ||
} | ||
@@ -98,2 +103,10 @@ }); | ||
function createLocationFromRedirectInfo(_ref) { | ||
var pathname = _ref.pathname; | ||
var query = _ref.query; | ||
var state = _ref.state; | ||
return _historyLibCreateLocation2['default'](history.createPath(pathname, query), state, _historyLibActions.REPLACE, history.createKey()); | ||
} | ||
function finishMatch(nextState, callback) { | ||
@@ -111,15 +124,10 @@ var _computeChangedRoutes = _computeChangedRoutes3['default'](state, nextState); | ||
} else if (redirectInfo) { | ||
var pathname = redirectInfo.pathname; | ||
var query = redirectInfo.query; | ||
var state = redirectInfo.state; | ||
history.replaceState(state, pathname, query); | ||
callback(); | ||
callback(null, createLocationFromRedirectInfo(redirectInfo), null); | ||
} else { | ||
// TODO: Fetch components after state is updated. | ||
_getComponents2['default'](nextState.routes, function (error, components) { | ||
_getComponents2['default'](nextState, function (error, components) { | ||
if (error) { | ||
callback(error); | ||
} else { | ||
callback(null, _extends({}, nextState, { components: components })); | ||
callback(null, null, _extends({}, nextState, { components: components })); | ||
} | ||
@@ -131,5 +139,13 @@ }); | ||
var RouteHooks = {}; | ||
var RouteGuid = 1; | ||
function getRouteID(route) { | ||
return route.__id__ || (route.__id__ = RouteGuid++); | ||
} | ||
function getRouteHooksForRoutes(routes) { | ||
return routes.reduce(function (hooks, route) { | ||
hooks.push.apply(hooks, routeHooks.get(route)); | ||
hooks.push.apply(hooks, RouteHooks[getRouteID(route)]); | ||
return hooks; | ||
@@ -140,22 +156,25 @@ }, []); | ||
function transitionHook(location, callback) { | ||
matchRoutesWithGuaranteedState(routes, location, function (error, nextState) { | ||
if (error) { | ||
// TODO: Handle the error. | ||
callback(false); // Cancel the transition. | ||
} else { | ||
// Cache some state here so we don't have to | ||
// matchRoutes() again in the listen callback. | ||
partialNextState = _extends({}, nextState, { location: location }); | ||
_matchRoutes2['default'](routes, location, function (error, nextState) { | ||
if (nextState == null) { | ||
// TODO: We didn't actually match anything, but hang | ||
// onto error/nextState so we don't have to matchRoutes | ||
// again in the listen callback. | ||
callback(); | ||
return; | ||
} | ||
var hooks = getRouteHooksForRoutes(_computeChangedRoutes3['default'](state, nextState).leaveRoutes); | ||
// Cache some state here so we don't have to | ||
// matchRoutes() again in the listen callback. | ||
partialNextState = _extends({}, nextState, { location: location }); | ||
var result; | ||
for (var i = 0, len = hooks.length; result == null && i < len; ++i) { | ||
// Passing the location arg here indicates to | ||
// the user that this is a transition hook. | ||
result = hooks[i](location); | ||
} | ||
var hooks = getRouteHooksForRoutes(_computeChangedRoutes3['default'](state, nextState).leaveRoutes); | ||
callback(result); | ||
} | ||
var result = undefined; | ||
for (var i = 0, len = hooks.length; result == null && i < len; ++i) { | ||
// Passing the location arg here indicates to | ||
// the user that this is a transition hook. | ||
result = hooks[i](location); | ||
} | ||
callback(result); | ||
}); | ||
@@ -167,6 +186,6 @@ } | ||
// prevent the current window/tab from closing. | ||
if (state && state.routes) { | ||
if (state.routes) { | ||
var hooks = getRouteHooksForRoutes(state.routes); | ||
var message; | ||
var message = undefined; | ||
for (var i = 0, len = hooks.length; typeof message !== 'string' && i < len; ++i) { | ||
@@ -186,8 +205,11 @@ // Passing no args indicates to the user that this is a | ||
// route objects on every location change. | ||
var hooks = routeHooks.get(route); | ||
var routeID = getRouteID(route); | ||
var hooks = RouteHooks[routeID]; | ||
if (hooks == null) { | ||
routeHooks.set(route, hooks = [hook]); | ||
var thereWereNoRouteHooks = !hasAnyProperties(RouteHooks); | ||
if (routeHooks.size === 1) { | ||
hooks = RouteHooks[routeID] = [hook]; | ||
if (thereWereNoRouteHooks) { | ||
history.registerTransitionHook(transitionHook); | ||
@@ -203,3 +225,4 @@ | ||
function unregisterRouteHook(route, hook) { | ||
var hooks = routeHooks.get(route); | ||
var routeID = getRouteID(route); | ||
var hooks = RouteHooks[routeID]; | ||
@@ -212,5 +235,5 @@ if (hooks != null) { | ||
if (newHooks.length === 0) { | ||
routeHooks['delete'](route); | ||
delete RouteHooks[routeID]; | ||
if (routeHooks.size === 0) { | ||
if (!hasAnyProperties(RouteHooks)) { | ||
history.unregisterTransitionHook(transitionHook); | ||
@@ -221,3 +244,3 @@ | ||
} else { | ||
routeHooks.set(route, newHooks); | ||
RouteHooks[routeID] = newHooks; | ||
} | ||
@@ -227,20 +250,27 @@ } | ||
function dispatch(location, callback) { | ||
if (state && state.location === location) { | ||
callback(null, state); | ||
return; | ||
} | ||
match(location, function (error, nextState) { | ||
if (error) { | ||
callback(error); | ||
} else if (nextState) { | ||
callback(null, state = nextState); | ||
} | ||
}); | ||
} | ||
/** | ||
* This is the API for stateful environments. As the location changes, | ||
* we update state and call the listener. Benefits of this API are: | ||
* | ||
* - We automatically manage state on the client | ||
* - We automatically handle redirects on the client | ||
* - We warn when the location doesn't match any routes | ||
*/ | ||
function listen(listener) { | ||
return history.listen(function (location) { | ||
dispatch(location, listener); | ||
if (state.location === location) { | ||
listener(null, state); | ||
} else { | ||
match(location, function (error, nextLocation, nextState) { | ||
if (error) { | ||
listener(error); | ||
} else if (nextState) { | ||
listener(null, state); // match mutates state to nextState | ||
} else if (nextLocation) { | ||
history.transitionTo(nextLocation); | ||
} else { | ||
_warning2['default'](false, 'Location "%s" did not match any routes', location.pathname + location.search); | ||
} | ||
}); | ||
} | ||
}); | ||
@@ -247,0 +277,0 @@ } |
import { mapAsync } from './AsyncUtils'; | ||
function getComponentsForRoute(route, callback) { | ||
function getComponentsForRoute(location, route, callback) { | ||
if (route.component || route.components) { | ||
callback(null, route.component || route.components); | ||
} else if (route.getComponent) { | ||
route.getComponent(callback); | ||
route.getComponent(location, callback); | ||
} else if (route.getComponents) { | ||
route.getComponents(callback); | ||
route.getComponents(location, callback); | ||
} else { | ||
@@ -22,5 +22,5 @@ callback(); | ||
*/ | ||
function getComponents(routes, callback) { | ||
mapAsync(routes, function (route, index, callback) { | ||
getComponentsForRoute(route, callback); | ||
function getComponents(nextState, callback) { | ||
mapAsync(nextState.routes, function (route, index, callback) { | ||
getComponentsForRoute(nextState.location, route, callback); | ||
}, callback); | ||
@@ -27,0 +27,0 @@ } |
@@ -11,12 +11,13 @@ /* components */ | ||
/* mixins */ | ||
export History from './History'; | ||
export Lifecycle from './Lifecycle'; | ||
export Navigation from './Navigation'; | ||
export RouteContext from './RouteContext'; | ||
export State from './State'; | ||
/* utils */ | ||
export useRoutes from './useRoutes'; | ||
export { createRoutesFromReactChildren } from './RouteUtils'; | ||
export { createRoutes } from './RouteUtils'; | ||
export RoutingContext from './RoutingContext'; | ||
export PropTypes from './PropTypes'; | ||
export match from './match'; | ||
export default from './Router'; |
import React from 'react'; | ||
import { component } from './PropTypes'; | ||
import Link from './Link'; | ||
var IndexLink = React.createClass({ | ||
render() { | ||
@@ -8,0 +7,0 @@ return <Link {...this.props} onlyActiveOnIndex={true} /> |
import React from 'react'; | ||
import invariant from 'invariant'; | ||
import warning from 'warning'; | ||
import { createRouteFromReactElement } from './RouteUtils'; | ||
@@ -4,0 +5,0 @@ import { component, components, falsy } from './PropTypes'; |
@@ -15,2 +15,3 @@ import { matchPattern } from './PatternUtils'; | ||
route = activeRoutes[i]; | ||
if (!route.path) return false; | ||
pattern = route.path || ''; | ||
@@ -17,0 +18,0 @@ |
@@ -10,3 +10,3 @@ import React from 'react'; | ||
* the user for confirmation. | ||
* | ||
* | ||
* On standard transitions, routerWillLeave receives a single argument: the | ||
@@ -48,3 +48,3 @@ * location we're transitioning to. To cancel the transition, return false. | ||
}, | ||
componentWillMount() { | ||
@@ -51,0 +51,0 @@ invariant( |
@@ -14,2 +14,10 @@ import React from 'react'; | ||
function isEmptyObject(object) { | ||
for (var p in object) | ||
if (object.hasOwnProperty(p)) | ||
return false; | ||
return true; | ||
} | ||
/** | ||
@@ -51,5 +59,4 @@ * A <Link> is used to create an <a> element that links to a route. | ||
return { | ||
onlyActiveOnIndex: false, | ||
className: '', | ||
activeClassName: 'active', | ||
onlyActiveOnIndex: false, | ||
style: {} | ||
@@ -88,11 +95,7 @@ }; | ||
render() { | ||
var { to, query, onlyActiveOnIndex } = this.props; | ||
var { history } = this.context; | ||
var { activeClassName, activeStyle, onlyActiveOnIndex, to, query, state, onClick, ...props } = this.props; | ||
var props = { | ||
...this.props, | ||
onClick: this.handleClick | ||
}; | ||
props.onClick = this.handleClick; | ||
var { history } = this.context; | ||
// Ignore if rendered outside the context | ||
@@ -103,8 +106,10 @@ // of history, simplifies unit testing. | ||
if (history.isActive(to, query, onlyActiveOnIndex)) { | ||
if (props.activeClassName) | ||
props.className += props.className !== '' ? ` ${props.activeClassName}` : props.activeClassName; | ||
if (activeClassName || (activeStyle != null && !isEmptyObject(activeStyle))) { | ||
if (history.isActive(to, query, onlyActiveOnIndex)) { | ||
if (activeClassName) | ||
props.className += props.className === '' ? activeClassName : ` ${activeClassName}`; | ||
if (props.activeStyle) | ||
props.style = { ...props.style, ...props.activeStyle }; | ||
if (activeStyle) | ||
props.style = { ...props.style, ...activeStyle }; | ||
} | ||
} | ||
@@ -111,0 +116,0 @@ } |
import { loopAsync } from './AsyncUtils'; | ||
import { matchPattern } from './PatternUtils'; | ||
import { createRoutes } from './RouteUtils'; | ||
@@ -8,3 +9,5 @@ function getChildRoutes(route, location, callback) { | ||
} else if (route.getChildRoutes) { | ||
route.getChildRoutes(location, callback); | ||
route.getChildRoutes(location, function(error, childRoutes) { | ||
callback(error, !error && createRoutes(childRoutes)); | ||
}); | ||
} else { | ||
@@ -19,3 +22,5 @@ callback(); | ||
} else if (route.getIndexRoute) { | ||
route.getIndexRoute(location, callback); | ||
route.getIndexRoute(location, function(error, indexRoute) { | ||
callback(error, !error && createRoutes(indexRoute)[0]); | ||
}); | ||
} else { | ||
@@ -22,0 +27,0 @@ callback(); |
@@ -17,3 +17,3 @@ import invariant from 'invariant'; | ||
var match, lastIndex = 0, matcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|\*|\(|\)/g; | ||
while (match = matcher.exec(pattern)) { | ||
while ((match = matcher.exec(pattern))) { | ||
if (match.index !== lastIndex) { | ||
@@ -20,0 +20,0 @@ tokens.push(pattern.slice(lastIndex, match.index)); |
@@ -29,1 +29,10 @@ import { PropTypes } from 'react'; | ||
export var routes = oneOfType([ route, arrayOf(route) ]); | ||
export default { | ||
falsy, | ||
history, | ||
location, | ||
component, | ||
components, | ||
route | ||
}; |
@@ -32,10 +32,10 @@ import React from 'react'; | ||
route.onEnter = function (nextState, redirectTo) { | ||
route.onEnter = function (nextState, replaceState) { | ||
var { location, params } = nextState; | ||
var pathname = route.to ? formatPattern(route.to, params) : location.pathname; | ||
redirectTo( | ||
replaceState( | ||
route.state || location.state, | ||
pathname, | ||
route.query || location.query, | ||
route.state || location.state | ||
route.query || location.query | ||
); | ||
@@ -48,3 +48,3 @@ }; | ||
}, | ||
propTypes: { | ||
@@ -66,5 +66,5 @@ path: string, | ||
} | ||
}); | ||
export default Redirect; |
@@ -16,3 +16,3 @@ import React from 'react'; | ||
}, | ||
childContextTypes: { | ||
@@ -19,0 +19,0 @@ route: object.isRequired |
import React from 'react'; | ||
import warning from 'warning'; | ||
import createHashHistory from 'history/lib/createHashHistory'; | ||
@@ -8,3 +9,3 @@ import { createRoutes } from './RouteUtils'; | ||
var { func, object } = React.PropTypes; | ||
const { func, object } = React.PropTypes; | ||
@@ -16,4 +17,4 @@ /** | ||
*/ | ||
var Router = React.createClass({ | ||
const Router = React.createClass({ | ||
propTypes: { | ||
@@ -49,4 +50,4 @@ history: object, | ||
componentWillMount() { | ||
var { history, children, routes, parseQueryString, stringifyQuery } = this.props; | ||
var createHistory = history ? () => history : createHashHistory; | ||
let { history, children, routes, parseQueryString, stringifyQuery } = this.props; | ||
let createHistory = history ? () => history : createHashHistory; | ||
@@ -68,2 +69,9 @@ this.history = useRoutes(createHistory)({ | ||
componentWillReceiveProps(nextProps) { | ||
warning( | ||
nextProps.history === this.props.history, | ||
"The `history` provided to <Router/> has changed, it will be ignored." | ||
); | ||
}, | ||
componentWillUnmount() { | ||
@@ -75,6 +83,15 @@ if (this._unlisten) | ||
render() { | ||
let { location, routes, params, components } = this.state; | ||
let { createElement } = this.props; | ||
if (location == null) | ||
return null; // Async match | ||
return React.createElement(RoutingContext, { | ||
...this.state, | ||
history: this.history, | ||
createElement: this.props.createElement | ||
createElement, | ||
location, | ||
routes, | ||
params, | ||
components | ||
}); | ||
@@ -81,0 +98,0 @@ } |
@@ -9,3 +9,3 @@ import React from 'react'; | ||
* A <RoutingContext> renders the component tree for a given router state | ||
* and sets the router object and the current location in context. | ||
* and sets the history object and the current location in context. | ||
*/ | ||
@@ -61,3 +61,4 @@ var RoutingContext = React.createClass({ | ||
route, | ||
routeParams | ||
routeParams, | ||
routes | ||
}; | ||
@@ -64,0 +65,0 @@ |
@@ -26,5 +26,5 @@ import { loopAsync } from './AsyncUtils'; | ||
* Runs all onEnter hooks in the given array of routes in order | ||
* with onEnter(nextState, redirectTo, callback) and calls | ||
* with onEnter(nextState, replaceState, callback) and calls | ||
* callback(error, redirectInfo) when finished. The first hook | ||
* to use redirectTo short-circuits the loop. | ||
* to use replaceState short-circuits the loop. | ||
* | ||
@@ -44,3 +44,3 @@ * If a hook needs to run asynchronously, it may use the callback | ||
var redirectInfo; | ||
function redirectTo(pathname, query, state) { | ||
function replaceState(state, pathname, query) { | ||
redirectInfo = { pathname, query, state }; | ||
@@ -50,3 +50,3 @@ } | ||
loopAsync(hooks.length, function (index, next, done) { | ||
hooks[index](nextState, redirectTo, function (error) { | ||
hooks[index](nextState, replaceState, function (error) { | ||
if (error || redirectInfo) { | ||
@@ -53,0 +53,0 @@ done(error, redirectInfo); // No need to continue. |
import warning from 'warning'; | ||
import { REPLACE } from 'history/lib/Actions'; | ||
import useQueries from 'history/lib/useQueries'; | ||
import createLocation from 'history/lib/createLocation'; | ||
import computeChangedRoutes from './computeChangedRoutes'; | ||
@@ -9,4 +11,13 @@ import { runEnterHooks, runLeaveHooks } from './TransitionUtils'; | ||
function hasAnyProperties(object) { | ||
for (var p in object) | ||
if (object.hasOwnProperty(p)) | ||
return true; | ||
return false; | ||
} | ||
/** | ||
* Enhances a history object with the following methods: | ||
* Returns a new createHistory function that may be used to create | ||
* history objects that know about routing. | ||
* | ||
@@ -16,10 +27,10 @@ * - isActive(pathname, query) | ||
* - unregisterRouteHook(route, (location) => {}) | ||
* - listen((error, state) => {}) | ||
* - match(location, (error, state) => {}) | ||
* - match(location, (error, nextState, nextLocation) => {}) | ||
* - listen((error, nextState) => {}) | ||
*/ | ||
function useRoutes(createHistory) { | ||
return function (options={}) { | ||
var { routes, ...historyOptions } = options; | ||
var history = useQueries(createHistory)(historyOptions); | ||
var state = {}; | ||
let { routes, ...historyOptions } = options; | ||
let history = useQueries(createHistory)(historyOptions); | ||
let state = {}; | ||
@@ -30,21 +41,4 @@ function isActive(pathname, query, indexOnly=false) { | ||
function matchRoutesWithGuaranteedState(routes, location, callback) { | ||
matchRoutes(routes, location, function (error, nextState) { | ||
if (error || nextState) { | ||
callback(error, nextState); | ||
} else { | ||
warning( | ||
false, | ||
'Location "%s" did not match any routes', | ||
location.pathname + location.search | ||
); | ||
} | ||
}); | ||
} | ||
let partialNextState; | ||
// TODO: If we had a way to uniquely identify a route, | ||
// we could use a plain object here instead... | ||
var routeHooks = new Map(); | ||
var partialNextState; | ||
function match(location, callback) { | ||
@@ -55,7 +49,13 @@ if (partialNextState && partialNextState.location === location) { | ||
} else { | ||
matchRoutesWithGuaranteedState(routes, location, function (error, nextState) { | ||
matchRoutes(routes, location, function (error, nextState) { | ||
if (error) { | ||
callback(error); | ||
callback(error, null, null); | ||
} else if (nextState) { | ||
finishMatch({ ...nextState, location }, function (err, nextLocation, nextState) { | ||
if (nextState) | ||
state = nextState; | ||
callback(err, nextLocation, nextState); | ||
}); | ||
} else { | ||
finishMatch({ ...nextState, location }, callback); | ||
callback(null, null, null); | ||
} | ||
@@ -66,4 +66,10 @@ }); | ||
function createLocationFromRedirectInfo({ pathname, query, state }) { | ||
return createLocation( | ||
history.createPath(pathname, query), state, REPLACE, history.createKey() | ||
); | ||
} | ||
function finishMatch(nextState, callback) { | ||
var { leaveRoutes, enterRoutes } = computeChangedRoutes(state, nextState); | ||
let { leaveRoutes, enterRoutes } = computeChangedRoutes(state, nextState); | ||
@@ -76,12 +82,10 @@ runLeaveHooks(leaveRoutes); | ||
} else if (redirectInfo) { | ||
var { pathname, query, state } = redirectInfo; | ||
history.replaceState(state, pathname, query); | ||
callback(); | ||
callback(null, createLocationFromRedirectInfo(redirectInfo), null); | ||
} else { | ||
// TODO: Fetch components after state is updated. | ||
getComponents(nextState.routes, function (error, components) { | ||
getComponents(nextState, function (error, components) { | ||
if (error) { | ||
callback(error); | ||
} else { | ||
callback(null, { ...nextState, components }); | ||
callback(null, null, { ...nextState, components }); | ||
} | ||
@@ -93,5 +97,13 @@ }); | ||
const RouteHooks = {}; | ||
let RouteGuid = 1; | ||
function getRouteID(route) { | ||
return route.__id__ || (route.__id__ = RouteGuid++); | ||
} | ||
function getRouteHooksForRoutes(routes) { | ||
return routes.reduce(function (hooks, route) { | ||
hooks.push.apply(hooks, routeHooks.get(route)); | ||
hooks.push.apply(hooks, RouteHooks[getRouteID(route)]); | ||
return hooks; | ||
@@ -102,24 +114,27 @@ }, []); | ||
function transitionHook(location, callback) { | ||
matchRoutesWithGuaranteedState(routes, location, function (error, nextState) { | ||
if (error) { | ||
// TODO: Handle the error. | ||
callback(false); // Cancel the transition. | ||
} else { | ||
// Cache some state here so we don't have to | ||
// matchRoutes() again in the listen callback. | ||
partialNextState = { ...nextState, location }; | ||
matchRoutes(routes, location, function (error, nextState) { | ||
if (nextState == null) { | ||
// TODO: We didn't actually match anything, but hang | ||
// onto error/nextState so we don't have to matchRoutes | ||
// again in the listen callback. | ||
callback(); | ||
return; | ||
} | ||
var hooks = getRouteHooksForRoutes( | ||
computeChangedRoutes(state, nextState).leaveRoutes | ||
); | ||
// Cache some state here so we don't have to | ||
// matchRoutes() again in the listen callback. | ||
partialNextState = { ...nextState, location }; | ||
var result; | ||
for (var i = 0, len = hooks.length; result == null && i < len; ++i) { | ||
// Passing the location arg here indicates to | ||
// the user that this is a transition hook. | ||
result = hooks[i](location); | ||
} | ||
let hooks = getRouteHooksForRoutes( | ||
computeChangedRoutes(state, nextState).leaveRoutes | ||
); | ||
callback(result); | ||
let result; | ||
for (let i = 0, len = hooks.length; result == null && i < len; ++i) { | ||
// Passing the location arg here indicates to | ||
// the user that this is a transition hook. | ||
result = hooks[i](location); | ||
} | ||
callback(result); | ||
}); | ||
@@ -129,9 +144,9 @@ } | ||
function beforeUnloadHook() { | ||
// Synchronously check to see if any route hooks want to | ||
// Synchronously check to see if any route hooks want to | ||
// prevent the current window/tab from closing. | ||
if (state && state.routes) { | ||
var hooks = getRouteHooksForRoutes(state.routes); | ||
if (state.routes) { | ||
let hooks = getRouteHooksForRoutes(state.routes); | ||
var message; | ||
for (var i = 0, len = hooks.length; typeof message !== 'string' && i < len; ++i) { | ||
let message; | ||
for (let i = 0, len = hooks.length; typeof message !== 'string' && i < len; ++i) { | ||
// Passing no args indicates to the user that this is a | ||
@@ -150,10 +165,13 @@ // beforeunload hook. We don't know the next location. | ||
// route objects on every location change. | ||
var hooks = routeHooks.get(route); | ||
let routeID = getRouteID(route); | ||
let hooks = RouteHooks[routeID]; | ||
if (hooks == null) { | ||
routeHooks.set(route, (hooks = [ hook ])); | ||
let thereWereNoRouteHooks = !hasAnyProperties(RouteHooks); | ||
if (routeHooks.size === 1) { | ||
hooks = RouteHooks[routeID] = [ hook ]; | ||
if (thereWereNoRouteHooks) { | ||
history.registerTransitionHook(transitionHook); | ||
if (history.registerBeforeUnloadHook) | ||
@@ -168,11 +186,12 @@ history.registerBeforeUnloadHook(beforeUnloadHook); | ||
function unregisterRouteHook(route, hook) { | ||
var hooks = routeHooks.get(route); | ||
let routeID = getRouteID(route); | ||
let hooks = RouteHooks[routeID]; | ||
if (hooks != null) { | ||
var newHooks = hooks.filter(item => item !== hook); | ||
let newHooks = hooks.filter(item => item !== hook); | ||
if (newHooks.length === 0) { | ||
routeHooks.delete(route); | ||
delete RouteHooks[routeID]; | ||
if (routeHooks.size === 0) { | ||
if (!hasAnyProperties(RouteHooks)) { | ||
history.unregisterTransitionHook(transitionHook); | ||
@@ -184,3 +203,3 @@ | ||
} else { | ||
routeHooks.set(route, newHooks); | ||
RouteHooks[routeID] = newHooks; | ||
} | ||
@@ -190,20 +209,31 @@ } | ||
function dispatch(location, callback) { | ||
if (state && state.location === location) { | ||
callback(null, state); | ||
return; | ||
} | ||
match(location, function (error, nextState) { | ||
if (error) { | ||
callback(error); | ||
} else if (nextState) { | ||
callback(null, (state = nextState)); | ||
} | ||
}); | ||
} | ||
/** | ||
* This is the API for stateful environments. As the location changes, | ||
* we update state and call the listener. Benefits of this API are: | ||
* | ||
* - We automatically manage state on the client | ||
* - We automatically handle redirects on the client | ||
* - We warn when the location doesn't match any routes | ||
*/ | ||
function listen(listener) { | ||
return history.listen(function (location) { | ||
dispatch(location, listener); | ||
if (state.location === location) { | ||
listener(null, state); | ||
} else { | ||
match(location, function (error, nextLocation, nextState) { | ||
if (error) { | ||
listener(error); | ||
} else if (nextState) { | ||
listener(null, state); // match mutates state to nextState | ||
} else if (nextLocation) { | ||
history.transitionTo(nextLocation); | ||
} else { | ||
warning( | ||
false, | ||
'Location "%s" did not match any routes', | ||
location.pathname + location.search | ||
); | ||
} | ||
}); | ||
} | ||
}); | ||
@@ -210,0 +240,0 @@ } |
## Confirming Navigation | ||
Sometimes you may want to prevent the user from going to a different page. For example, if they are halfway finished filling out a long form, and they click the back button, you may want to prompt them to confirm they actually want to leave the page before they lose the information they've already entered. For these cases, `history` lets you register transition hooks that return a prompt message you can show the user before the location changes. For example, you could do something like this: | ||
Sometimes you may want to prevent the user from going to a different page. For example, if they are halfway finished filling out a long form, and they click the back button, you may want to prompt them to confirm they actually want to leave the page before they lose the information they've already entered. For these cases, `history` lets you register [transition hooks](Terms.md#transitionhook) that return a prompt message you can show the user before the [location](Terms.md#location) changes. For example, you could do something like this: | ||
@@ -8,7 +8,7 @@ ```js | ||
if (input.value !== '') | ||
return 'Are you sure you want to leave this page?'; | ||
}); | ||
return 'Are you sure you want to leave this page?' | ||
}) | ||
``` | ||
You can also simply `return false` to prevent a transition. | ||
You can also simply `return false` to prevent a [transition](Terms.md#transition). | ||
@@ -19,4 +19,4 @@ If your transition hook needs to execute asynchronously, you can provide a second `callback` argument to your transition hook function that you must call when you're done with async work. | ||
history.registerTransitionHook(function (location, callback) { | ||
doSomethingAsync().then(callback); | ||
}); | ||
doSomethingAsync().then(callback) | ||
}) | ||
``` | ||
@@ -29,7 +29,7 @@ | ||
```js | ||
var history = createHistory({ | ||
let history = createHistory({ | ||
getUserConfirmation: function (message, callback) { | ||
callback(window.confirm(message)); // The default behavior | ||
callback(window.confirm(message)) // The default behavior | ||
} | ||
}); | ||
}) | ||
``` | ||
@@ -42,11 +42,11 @@ | ||
```js | ||
import { createHistory, useBeforeUnload } from 'history'; | ||
import { createHistory, useBeforeUnload } from 'history' | ||
var history = useBeforeUnload(createHistory)(); | ||
let history = useBeforeUnload(createHistory)() | ||
history.registerBeforeUnloadHook(function () { | ||
return 'Are you sure you want to leave this page?'; | ||
}); | ||
return 'Are you sure you want to leave this page?' | ||
}) | ||
``` | ||
Note that because of the nature of the `beforeunload` event all hooks must `return` synchronously. `history` runs all hooks in the order they were registered and displays the first message that is returned. |
## Getting Started | ||
The first thing you'll need to do is create a history object. The main `history` module exports several different `create*` methods that you can use depending on your environment. | ||
The first thing you'll need to do is create a [history object](Terms.md#history). The main `history` module exports several different [`create*` methods](Terms.md#createhistory) that you can use depending on your environment. | ||
@@ -12,14 +12,14 @@ - `createHistory` is for use in modern web browsers that support the [HTML5 history API](http://diveintohtml5.info/history.html) (see [cross-browser compatibility](http://caniuse.com/#feat=history)) | ||
```js | ||
import { createHistory } from 'history'; | ||
import { createHistory } from 'history' | ||
var history = createHistory(); | ||
let history = createHistory() | ||
// Listen for changes to the current location. The | ||
// listener is called once immediately. | ||
var unlisten = history.listen(function (location) { | ||
console.log(location.pathname); | ||
}); | ||
let unlisten = history.listen(function (location) { | ||
console.log(location.pathname) | ||
}) | ||
// When you're finished, stop the listener. | ||
unlisten(); | ||
unlisten() | ||
``` | ||
@@ -38,15 +38,15 @@ | ||
The `path` argument to `pushState` and `replaceState` represents a complete URL path, including the query string. The `state` argument should be a JSON-serializable object. In `setState`, the properties in `state` are shallowly merged into the current state. | ||
The [`path`](Terms.md#path) argument to `pushState` and `replaceState` represents a complete URL path, including the [query string](Terms.md#querystring). The [`state`](Terms.md#locationstate) argument should be a JSON-serializable object. In `setState`, the properties in `state` are shallowly merged into the current state. | ||
```js | ||
// Push a new entry onto the history stack. | ||
history.pushState({ some: 'state' }, '/home'); | ||
history.pushState({ some: 'state' }, '/home') | ||
// Replace the current entry on the history stack. | ||
history.replaceState({ some: 'other state' }, '/profile'); | ||
history.replaceState({ some: 'other state' }, '/profile') | ||
// Go back to the previous history entry. The following | ||
// two lines are synonymous. | ||
history.go(-1); | ||
history.goBack(); | ||
history.go(-1) | ||
history.goBack() | ||
``` | ||
@@ -61,3 +61,3 @@ | ||
```js | ||
var href = history.createHref('/the/path'); | ||
let href = history.createHref('/the/path') | ||
``` | ||
@@ -71,9 +71,9 @@ | ||
// Browser history | ||
import createHistory from 'history/lib/createBrowserHistory'; | ||
import createHistory from 'history/lib/createBrowserHistory' | ||
// Hash history | ||
import createHistory from 'history/lib/createHashHistory'; | ||
import createHistory from 'history/lib/createHashHistory' | ||
// Memory history | ||
import createHistory from 'history/lib/createMemoryHistory'; | ||
import createHistory from 'history/lib/createMemoryHistory' | ||
``` |
@@ -8,15 +8,15 @@ ## Caveats of Using Hash History | ||
```js | ||
import createHistory from 'history/lib/createHashHistory'; | ||
import createHistory from 'history/lib/createHashHistory' | ||
// Use _key instead of _k. | ||
var history = createHistory({ | ||
let history = createHistory({ | ||
queryKey: '_key' | ||
}); | ||
}) | ||
// Opt-out of persistent state, not recommended. | ||
var history = createHistory({ | ||
let history = createHistory({ | ||
queryKey: false | ||
}); | ||
}) | ||
``` | ||
One other thing to keep in mind when using hash history is that you cannot also use `window.location.hash` as it was originally intended, to link an anchor point within your HTML document. |
## Location | ||
A `location` object is conceptually similar to [`document.location` in web browsers](https://developer.mozilla.org/en-US/docs/Web/API/Document/location), with a few extra goodies. `location` objects have the following properties: | ||
A [`location` object](Terms.md#location) is conceptually similar to [`document.location` in web browsers](https://developer.mozilla.org/en-US/docs/Web/API/Document/location), with a few extra goodies. `location` objects have the following properties: | ||
@@ -20,5 +20,5 @@ ``` | ||
```js | ||
import createLocation from 'history/lib/createLocation'; | ||
import createLocation from 'history/lib/createLocation' | ||
var location = createLocation('/a/path?a=query', { the: 'state' }); | ||
let location = createLocation('/a/path?a=query', { the: 'state' }) | ||
``` |
## Query Support | ||
Support for parsing and serializing URL queries is provided by the `useQueries` enhancer function. Simply use a wrapped version of your `createHistory` function to create your `history` object and you'll have a parsed `location.query` object inside `listen`. | ||
Support for parsing and serializing [URL queries](Terms.md#query) is provided by the `useQueries` [enhancer](Terms.md#createhistoryenhancer) function. Simply use a wrapped version of your `createHistory` function to create your `history` object and you'll have a parsed `location.query` object inside `listen`. | ||
```js | ||
import { createHistory, useQueries } from 'history'; | ||
import { createHistory, useQueries } from 'history' | ||
// Use the built-in query parsing/serialization. | ||
var history = useQueries(createHistory)(); | ||
let history = useQueries(createHistory)() | ||
// Use custom query parsing/serialization. | ||
var history = useQueries(createHistory)({ | ||
let history = useQueries(createHistory)({ | ||
parseQueryString: function (queryString) { | ||
return qs.parse(queryString); | ||
return qs.parse(queryString) | ||
}, | ||
stringifyQuery: function (query) { | ||
return qs.stringify(query, { arrayFormat: 'brackets' }); | ||
return qs.stringify(query, { arrayFormat: 'brackets' }) | ||
} | ||
}); | ||
}) | ||
history.listen(function (location) { | ||
console.log(location.query); | ||
}); | ||
console.log(location.query) | ||
}) | ||
``` | ||
@@ -29,4 +29,4 @@ | ||
```js | ||
history.createPath('/the/path', { the: 'query' }); | ||
history.pushState(null, '/the/path', { the: 'query' }); | ||
history.createPath('/the/path', { the: 'query' }) | ||
history.pushState(null, '/the/path', { the: 'query' }) | ||
``` |
@@ -8,1 +8,2 @@ ## Table of Contents | ||
- [Caveats of Using Hash History](HashHistoryCaveats.md) | ||
- [Glossary](Terms.md) |
@@ -50,3 +50,3 @@ 'use strict'; | ||
var state; | ||
var state = undefined; | ||
if (key) { | ||
@@ -57,3 +57,3 @@ state = _DOMStateStorage.readState(key); | ||
key = history.createKey(); | ||
window.history.replaceState(_extends({}, historyState, { key: key }), path); | ||
window.history.replaceState(_extends({}, historyState, { key: key }), null, path); | ||
} | ||
@@ -119,3 +119,3 @@ | ||
var listenerCount = 0, | ||
stopPopStateListener; | ||
stopPopStateListener = undefined; | ||
@@ -122,0 +122,0 @@ function listen(listener) { |
@@ -74,3 +74,4 @@ 'use strict'; | ||
var key, state; | ||
var key = undefined, | ||
state = undefined; | ||
if (queryKey) { | ||
@@ -99,3 +100,3 @@ key = getQueryStringValueFromPath(path, queryKey); | ||
transitionTo(getCurrentLocation()); | ||
}; | ||
} | ||
@@ -149,3 +150,3 @@ ensureSlash(); | ||
var listenerCount = 0, | ||
stopHashChangeListener; | ||
stopHashChangeListener = undefined; | ||
@@ -165,3 +166,3 @@ function listen(listener) { | ||
function pushState(state, path) { | ||
_warning2['default'](queryKey || state == null, 'You cannot use state without a queryKey; it will be dropped'); | ||
_warning2['default'](queryKey || state == null, 'You cannot use state without a queryKey it will be dropped'); | ||
@@ -172,3 +173,3 @@ history.pushState(state, path); | ||
function replaceState(state, path) { | ||
_warning2['default'](queryKey || state == null, 'You cannot use state without a queryKey; it will be dropped'); | ||
_warning2['default'](queryKey || state == null, 'You cannot use state without a queryKey it will be dropped'); | ||
@@ -175,0 +176,0 @@ history.replaceState(state, path); |
@@ -54,3 +54,3 @@ 'use strict'; | ||
var changeListeners = []; | ||
var location; | ||
var location = undefined; | ||
@@ -101,5 +101,5 @@ var allKeys = []; | ||
} else { | ||
var location = getCurrentLocation(); | ||
allKeys = [location.key]; | ||
updateLocation(location); | ||
var _location = getCurrentLocation(); | ||
allKeys = [_location.key]; | ||
updateLocation(_location); | ||
} | ||
@@ -130,3 +130,3 @@ | ||
} else { | ||
_warning2['default'](result === undefined, 'You may not use `return` in a transition hook with a callback argument; call the callback instead'); | ||
_warning2['default'](result === undefined, 'You should not "return" in a transition hook with a callback argument call the callback instead'); | ||
} | ||
@@ -155,3 +155,3 @@ } | ||
var pendingLocation; | ||
var pendingLocation = undefined; | ||
@@ -158,0 +158,0 @@ function transitionTo(nextLocation) { |
@@ -31,13 +31,18 @@ 'use strict'; | ||
var index = path.indexOf('?'); | ||
var pathname = path; | ||
var search = ''; | ||
var hash = ''; | ||
var pathname, search; | ||
if (index !== -1) { | ||
pathname = path.substring(0, index); | ||
search = path.substring(index); | ||
} else { | ||
pathname = path; | ||
search = ''; | ||
var hashIndex = pathname.indexOf('#'); | ||
if (hashIndex !== -1) { | ||
hash = pathname.substring(hashIndex); | ||
pathname = pathname.substring(0, hashIndex); | ||
} | ||
var searchIndex = pathname.indexOf('?'); | ||
if (searchIndex !== -1) { | ||
search = pathname.substring(searchIndex); | ||
pathname = pathname.substring(0, searchIndex); | ||
} | ||
if (pathname === '') pathname = '/'; | ||
@@ -48,2 +53,3 @@ | ||
search: search, | ||
hash: hash, | ||
state: state, | ||
@@ -50,0 +56,0 @@ action: action, |
@@ -92,3 +92,3 @@ 'use strict'; | ||
var state; | ||
var state = undefined; | ||
if (key) { | ||
@@ -112,3 +112,3 @@ state = readState(key); | ||
if (n) { | ||
_invariant2['default'](canGo(n), 'Cannot go(%s); there is not enough history', n); | ||
_invariant2['default'](canGo(n), 'Cannot go(%s) there is not enough history', n); | ||
@@ -131,3 +131,3 @@ current += n; | ||
// remove rest and push new | ||
if (current < entries.length - 1) { | ||
if (current < entries.length) { | ||
entries.splice(current); | ||
@@ -134,0 +134,0 @@ } |
@@ -28,2 +28,7 @@ 'use strict'; | ||
/** | ||
* Returns a new createHistory function that can be used to create | ||
* history objects that know how to use the beforeunload event in web | ||
* browsers to cancel navigation. | ||
*/ | ||
function useBeforeUnload(createHistory) { | ||
@@ -33,11 +38,11 @@ return function (options) { | ||
var stopBeforeUnloadListener; | ||
var stopBeforeUnloadListener = undefined; | ||
var beforeUnloadHooks = []; | ||
function getBeforeUnloadPromptMessage() { | ||
var message; | ||
var message = undefined; | ||
for (var i = 0, len = beforeUnloadHooks.length; message == null && i < len; ++i) message = beforeUnloadHooks[i].call(); | ||
return message; | ||
for (var i = 0, len = beforeUnloadHooks.length; message == null && i < len; ++i) { | ||
message = beforeUnloadHooks[i].call(); | ||
}return message; | ||
} | ||
@@ -44,0 +49,0 @@ |
@@ -23,2 +23,6 @@ 'use strict'; | ||
/** | ||
* Returns a new createHistory function that may be used to create | ||
* history objects that know how to handle URL queries. | ||
*/ | ||
function useQueries(createHistory) { | ||
@@ -55,3 +59,3 @@ return function () { | ||
function createPath(pathname, query) { | ||
var queryString; | ||
var queryString = undefined; | ||
if (query == null || (queryString = stringifyQuery(query)) === '') return pathname; | ||
@@ -58,0 +62,0 @@ |
@@ -16,3 +16,3 @@ var pSlice = Array.prototype.slice; | ||
// equivalence is determined by ==. | ||
} else if (typeof actual != 'object' && typeof expected != 'object') { | ||
} else if (!actual || !expected || typeof actual != 'object' && typeof expected != 'object') { | ||
return opts.strict ? actual === expected : actual == expected; | ||
@@ -19,0 +19,0 @@ |
{ | ||
"name": "deep-equal", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"description": "node's assert.deepEqual algorithm", | ||
@@ -19,3 +19,3 @@ "main": "index.js", | ||
"type": "git", | ||
"url": "http://github.com/substack/node-deep-equal.git" | ||
"url": "git+ssh://git@github.com/substack/node-deep-equal.git" | ||
}, | ||
@@ -59,16 +59,20 @@ "keywords": [ | ||
}, | ||
"gitHead": "39c740ebdafed9443912a4ef1493b18693934daf", | ||
"gitHead": "59c511f5aeae19e3dd1de054077a789d7302be34", | ||
"bugs": { | ||
"url": "https://github.com/substack/node-deep-equal/issues" | ||
}, | ||
"homepage": "https://github.com/substack/node-deep-equal", | ||
"_id": "deep-equal@1.0.0", | ||
"_shasum": "d4564f07d2f0ab3e46110bec16592abd7dc2e326", | ||
"_from": "deep-equal@>=1.0.0 <2.0.0", | ||
"_npmVersion": "2.3.0", | ||
"_nodeVersion": "0.10.35", | ||
"homepage": "https://github.com/substack/node-deep-equal#readme", | ||
"_id": "deep-equal@1.0.1", | ||
"_shasum": "f5d260292b660e084eff4cdbc9f08ad3247448b5", | ||
"_from": "deep-equal@>=1.0.1 <2.0.0", | ||
"_npmVersion": "3.2.2", | ||
"_nodeVersion": "2.4.0", | ||
"_npmUser": { | ||
"name": "substack", | ||
"email": "mail@substack.net" | ||
"email": "substack@gmail.com" | ||
}, | ||
"dist": { | ||
"shasum": "f5d260292b660e084eff4cdbc9f08ad3247448b5", | ||
"tarball": "http://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz" | ||
}, | ||
"maintainers": [ | ||
@@ -80,8 +84,4 @@ { | ||
], | ||
"dist": { | ||
"shasum": "d4564f07d2f0ab3e46110bec16592abd7dc2e326", | ||
"tarball": "http://registry.npmjs.org/deep-equal/-/deep-equal-1.0.0.tgz" | ||
}, | ||
"_resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.0.tgz", | ||
"_resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", | ||
"readme": "ERROR: No README data found!" | ||
} |
@@ -90,1 +90,7 @@ var test = require('tape'); | ||
}) | ||
test('null == undefined', function (t) { | ||
t.ok(equal(null, undefined)) | ||
t.notOk(equal(null, undefined, { strict: true })) | ||
t.end() | ||
}) |
{ | ||
"name": "history", | ||
"version": "1.8.1", | ||
"version": "1.9.0", | ||
"description": "A minimal, functional history implementation for JavaScript", | ||
@@ -15,4 +15,4 @@ "main": "lib/index", | ||
"build": "babel ./modules -d lib --ignore '__tests__'", | ||
"build-umd": "NODE_ENV=production webpack modules/index.js lib/umd/History.js", | ||
"build-min": "NODE_ENV=production webpack -p modules/index.js lib/umd/History.min.js", | ||
"build-umd": "NODE_ENV=production webpack modules/index.js umd/History.js", | ||
"build-min": "NODE_ENV=production webpack -p modules/index.js umd/History.min.js", | ||
"start": "webpack-dev-server -d --content-base ./ --history-api-fallback --inline modules/index.js", | ||
@@ -30,3 +30,2 @@ "test": "eslint modules && karma start", | ||
"qs": "^4.0.0", | ||
"tough-cookie": "^2.0.0", | ||
"warning": "^2.0.0" | ||
@@ -62,12 +61,12 @@ }, | ||
], | ||
"gitHead": "de1850f70b6674cc6445fae692660ae4208a293e", | ||
"gitHead": "e9b16da106f1567f81c4f17a709ac287d1396735", | ||
"homepage": "https://github.com/rackt/history", | ||
"_id": "history@1.8.1", | ||
"_shasum": "bb8155ce711db359cf0467e300eae9dc17306dfc", | ||
"_from": "history@>=1.8.0 <2.0.0", | ||
"_npmVersion": "2.7.5", | ||
"_nodeVersion": "0.10.28", | ||
"_id": "history@1.9.0", | ||
"_shasum": "0527bc87de22e9f11ccf33e8bb8b1a9a89b2d6d7", | ||
"_from": "history@>=1.9.0 <2.0.0", | ||
"_npmVersion": "2.5.1", | ||
"_nodeVersion": "0.12.0", | ||
"_npmUser": { | ||
"name": "mjackson", | ||
"email": "mjijackson@gmail.com" | ||
"name": "ryanflorence", | ||
"email": "rpflorence@gmail.com" | ||
}, | ||
@@ -78,11 +77,14 @@ "maintainers": [ | ||
"email": "mjijackson@gmail.com" | ||
}, | ||
{ | ||
"name": "ryanflorence", | ||
"email": "rpflorence@gmail.com" | ||
} | ||
], | ||
"dist": { | ||
"shasum": "bb8155ce711db359cf0467e300eae9dc17306dfc", | ||
"tarball": "http://registry.npmjs.org/history/-/history-1.8.1.tgz" | ||
"shasum": "0527bc87de22e9f11ccf33e8bb8b1a9a89b2d6d7", | ||
"tarball": "http://registry.npmjs.org/history/-/history-1.9.0.tgz" | ||
}, | ||
"directories": {}, | ||
"_resolved": "https://registry.npmjs.org/history/-/history-1.8.1.tgz", | ||
"readme": "ERROR: No README data found!" | ||
"_resolved": "https://registry.npmjs.org/history/-/history-1.9.0.tgz" | ||
} |
@@ -18,16 +18,16 @@ [![build status](https://img.shields.io/travis/rackt/history/master.svg?style=flat-square)](https://travis-ci.org/rackt/history) | ||
```js | ||
import { createHistory } from 'history'; | ||
import { createHistory } from 'history' | ||
var history = createHistory(); | ||
let history = createHistory() | ||
// Listen for changes to the current location. The | ||
// listener is called once immediately. | ||
var unlisten = history.listen(function (location) { | ||
console.log(location.pathname); | ||
}); | ||
let unlisten = history.listen(function (location) { | ||
console.log(location.pathname) | ||
}) | ||
history.pushState({ the: 'state' }, '/the/path?a=query'); | ||
history.pushState({ the: 'state' }, '/the/path?a=query') | ||
// When you're finished, stop the listener. | ||
unlisten(); | ||
unlisten() | ||
``` | ||
@@ -34,0 +34,0 @@ |
{ | ||
"name": "react-router", | ||
"version": "1.0.0-beta4", | ||
"version": "1.0.0-rc1", | ||
"description": "A complete routing library for React.js", | ||
@@ -14,7 +14,7 @@ "main": "lib/index", | ||
"build": "babel ./modules -d lib --ignore '__tests__'", | ||
"build-umd": "NODE_ENV=production webpack modules/index.js lib/umd/History.js", | ||
"build-min": "NODE_ENV=production webpack -p modules/index.js lib/umd/History.min.js", | ||
"build-website": "scripts/build-website.sh", | ||
"build-umd": "NODE_ENV=production webpack modules/index.js umd/ReactRouter.js", | ||
"build-min": "NODE_ENV=production webpack -p modules/index.js umd/ReactRouter.min.js", | ||
"start": "webpack-dev-server --config examples/webpack.config.js --content-base examples --inline", | ||
"test": "eslint modules && karma start", | ||
"lint": "eslint modules", | ||
"prepublish": "npm run build" | ||
@@ -28,3 +28,3 @@ }, | ||
"dependencies": { | ||
"history": "^1.8.0", | ||
"history": "^1.9.0", | ||
"invariant": "^2.0.0", | ||
@@ -40,3 +40,4 @@ "warning": "^2.0.0" | ||
"bundle-loader": "^0.5.2", | ||
"eslint": "^1.0.0", | ||
"eslint": "1.4.0", | ||
"eslint-plugin-react": "3.3.2", | ||
"events": "1.0.2", | ||
@@ -43,0 +44,0 @@ "expect": "^1.6.0", |
@@ -7,3 +7,3 @@ [![build status](https://img.shields.io/travis/rackt/react-router/master.svg?style=flat-square)](https://travis-ci.org/rackt/react-router) | ||
A complete routing library for React. https://rackt.github.io/react-router | ||
A complete routing library for React | ||
@@ -17,3 +17,3 @@ React Router keeps your UI in sync with the URL. It has a simple API | ||
- [Guides and API Docs](https://rackt.github.io/react-router) | ||
- [Guides and API Docs](/docs) | ||
- [Upgrade Guide](/UPGRADE_GUIDE.md) | ||
@@ -23,5 +23,2 @@ - [Changelog](/CHANGELOG.md) | ||
**Note: the docs and the examples in master refer to the 1.0 Beta and may be incomplete.** | ||
**Browse [the website](http://rackt.github.io/react-router/) and [the 0.13.3 tag](https://github.com/rackt/react-router/tree/v0.13.3) for the information about the latest stable version.** | ||
### Browser Support | ||
@@ -40,3 +37,3 @@ | ||
```js | ||
// using an ES6 transpiler | ||
// using an ES6 transpiler, like babel | ||
import { Router, Route, Link } from 'react-router'; | ||
@@ -51,10 +48,16 @@ | ||
There's also a `lib/umd` folder containing a UMD version. | ||
You can require only the pieces you need straight from the `lib` directory: | ||
#### bower + who knows what | ||
```js | ||
import { Router } from 'react-router/lib/Router'; | ||
``` | ||
$ bower install react-router | ||
There's also a UMD build in the `umd` directory: | ||
Find the UMD/global build in `lib/umd`, and the library on `window.ReactRouter`. Best of luck to you. :) | ||
```js | ||
import ReactRouter from 'react-router/umd/ReactRouter'; | ||
``` | ||
If you're using globals, you can find the library on `window.ReactRouter`. | ||
#### CDN | ||
@@ -128,10 +131,9 @@ | ||
See more in the [overview guide](/doc/00 Guides/0 Overview.md) and [Advanced | ||
Usage](/doc/00 Guides/Advanced Usage.md) | ||
See more in the [Introduction](/docs/introduction/README.md) and [Advanced | ||
Usage](/docs/advanced/README.md). | ||
### Thanks | ||
React Router was initially inspired by Ember's fantastic router. Many | ||
thanks to the Ember team. | ||
React Router was initially inspired by Ember's fantastic router. Many thanks to the Ember team. | ||
Also, thanks to [BrowserStack](https://www.browserstack.com/) for providing the infrastructure that allows us to run our build in real browsers. |
@@ -8,2 +8,232 @@ Upgrade Guide | ||
0.13.3 -> 1.0.0 | ||
--------------- | ||
Thanks for your patience :) Big changes. While on the surface a lot of | ||
this just looks like shuffling around API, the entire codebase has been | ||
rewritten to handle some really great use cases, like loading routes and | ||
components on demand, session-based route matching, server rendering, | ||
integration with libs like redux and relay, and lots more. | ||
But for now, here's how to translate the old API to the new one. | ||
### Rendering | ||
```js | ||
// v0.13.x | ||
Router.run(routes, (Handler) => { | ||
React.render(<Handler/>, el); | ||
}) | ||
// v1.0 | ||
React.render(<Router>{routes}</Router>, el) | ||
// looks more like this: | ||
React.render(( | ||
<Router> | ||
<Route path="/" component={App}/> | ||
</Router> | ||
), el); | ||
// or if you'd rather | ||
React.render(<Router routes={routes}/>, el) | ||
``` | ||
### Route Config | ||
You can still nest your routes as before, paths are inherited from | ||
parents just like before but prop names have changed. | ||
```js | ||
// v0.13.x | ||
<Route name="about" handler={About}/> | ||
// v1.0 | ||
<Route path="about" component={About}/> | ||
``` | ||
Named routes are gone (for now, [see discussion](https://github.com/rackt/react-router/issues/1840)) | ||
### NotFound route | ||
Not found really confused people, mistaking not finding resources | ||
from your API for not matching a route. We've removed it completely | ||
since its simple with a `*` path. | ||
```js | ||
// v0.13.x | ||
<NotFoundRoute handler={NoMatch}/> | ||
// v1.0 | ||
<Route path="*" component={NoMatch}/> | ||
``` | ||
### Redirect route | ||
- no more params | ||
- must have absolute "from" (for now) | ||
```js | ||
// v0.13.x | ||
<Redirect from="some/where/:id" to="somewhere/else/:id" params={{id: 2}}/> | ||
// v1.0 | ||
// Works the same as before, except no params, just put them in the path | ||
<Redirect from="/some/where/:id" to="/somewhere/else/2"/> | ||
``` | ||
### Links | ||
#### path / params | ||
```js | ||
// v0.13.x | ||
<Link to="user" params={{userId: user.id}}>Mateusz</Link> | ||
// v1.0 | ||
// because named routes are gone, link to full paths, you no longer need | ||
// to know the names of the parameters, and string templates are quite | ||
// nice. Note that `query` has not changed. | ||
<Link to={`/users/${user.id}`}>Mateusz</Link> | ||
``` | ||
#### "active" class | ||
In 0.13.x links added the "active" class by default which you could | ||
override with `activeClassName`, or provide `activeStyles`. Most links | ||
don't need this and the check is (currently) expensive. | ||
Links no longer add the "active" class by default, you opt-in by | ||
providing one; if no `activeClassName` or `activeStyles` are provided, | ||
the link will not check if its active. | ||
```js | ||
// v0.13.x | ||
<Link to="about">About</Link> | ||
// v1.0 | ||
<Link to="/about" activeClassName="active">About</Link> | ||
``` | ||
#### Linking to Default/Index routes | ||
Because named routes are gone, a link to `/` with an index route at `/` | ||
will always be active. So we've introduced `IndexLink` that is only | ||
active when the index route is active. | ||
```js | ||
// v0.13.x | ||
// with this route config | ||
<Route path="/" handler={App}> | ||
<DefaultRoute name="home" handler={Home}/> | ||
<Route name="about" handler={About}/> | ||
</Route> | ||
// will be active only when home is active, not when about is active | ||
<Link to="home">Home</Link> | ||
// v1.0 | ||
<Route path="/" component={App}> | ||
<IndexRoute handler={Home}/> | ||
<Route path="about" handler={About}/> | ||
</Route> | ||
// will be active only when home is active, not when about is active | ||
<IndexLink to="/">Home</Link> | ||
``` | ||
### Navigation Mixin | ||
If you were using the navigation, instead use the `History` mixin. | ||
```js | ||
// v0.13.x | ||
var Assignment = React.createClass({ | ||
mixins: [ Navigation ], | ||
navigateAfterSomethingHappened () { | ||
this.transitionTo('/users', { userId: user.id }, query); | ||
// this.replaceWith('/users', { userId: user.id }, query); | ||
} | ||
}) | ||
// v1.0 | ||
var Assignment = React.createClass({ | ||
mixins: [ History ], | ||
navigateAfterSomethingHappened () { | ||
// the router is not built on rackt/history, and it is a first class | ||
// API in the router for navigating | ||
this.history.pushState(null, `/users/${user.id}`, query); | ||
// this.history.replaceState(null, `/users/${user.id}`, query); | ||
} | ||
}) | ||
``` | ||
The following `Navigation` methods are now also found on the history | ||
object, main difference again is there are no params or route names, | ||
just pathnames. | ||
| v0.13 | v1.0 | | ||
|--------------------------------------|-------------------------------| | ||
| `go(n)` | `go(n)` | | ||
| `goBack()` | `goBack()` | | ||
| `goForward()` | `goForward()` | | ||
| `makeHref(routeName, params, query)` | `createHref(pathname, query)` | | ||
| `makePath(routeName, params, query)` | `createPath(pathname, query)` | | ||
### State mixin | ||
```js | ||
// v0.13.x | ||
var Assignment = React.createClass({ | ||
mixins: [ State ], | ||
foo () { | ||
this.getPath() | ||
this.getParams() | ||
// etc... | ||
} | ||
}) | ||
// v1.0 | ||
// if you are a route component... | ||
<Route component={Assignment/> | ||
var Assignment = React.createClass({ | ||
foo () { | ||
this.props.location // contains path information | ||
this.props.params // contains params | ||
this.props.history.isActive | ||
} | ||
}) | ||
// if you're not a route component, you need to pass location down the | ||
// tree or get the location from context, we will probably provide a | ||
// higher order component that will do this for you but haven't yet | ||
var Assignment = React.createClass({ | ||
contextTypes: { | ||
location: React.PropTypes.object | ||
}, | ||
foo () { | ||
this.context.location | ||
} | ||
}) | ||
``` | ||
Here's a table of where you used to get stuff with the `State` mixin, | ||
and where you get it now if you're a route component (`this.props`) | ||
| v0.13 (this) | v1.0 (this.props) | | ||
|-----------------|------------------------------------| | ||
| `getPath()` | `location.pathname+location.query` | | ||
| `getPathname()` | `location.pathname` | | ||
| `getParams()` | `params` | | ||
| `getQuery()` | `query` | | ||
| `getRoutes()` | `routes` | | ||
### We'll keep updating this | ||
There's a lot of the old API we've missed, please give the [new | ||
docs](/docs) a read and help us fill this guide in. Thansk! | ||
0.13.2 -> 0.13.3 | ||
@@ -10,0 +240,0 @@ ---------------- |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license
Found 1 instance in 1 package
2
100
3
134
0
1
569171
27
149
9587
Updatedhistory@^1.9.0