Repository: reactjs/react-router-redux
Branch: master
Commit: afe0aa1b355f
Files: 49
Total size: 56.8 KB
Directory structure:
gitextract_who3sr76/
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── examples/
│ ├── .gitignore
│ ├── basic/
│ │ ├── .babelrc
│ │ ├── README.md
│ │ ├── actions/
│ │ │ └── count.js
│ │ ├── app.js
│ │ ├── components/
│ │ │ ├── App.js
│ │ │ ├── Bar.js
│ │ │ ├── Foo.js
│ │ │ ├── Home.js
│ │ │ └── index.js
│ │ ├── constants.js
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── reducers/
│ │ │ ├── count.js
│ │ │ └── index.js
│ │ └── webpack.config.js
│ └── server/
│ ├── .babelrc
│ ├── client.js
│ ├── index.html
│ ├── package.json
│ ├── routes.js
│ ├── server.js
│ ├── store.js
│ └── webpack.config.js
├── karma.conf.js
├── package.json
├── src/
│ ├── actions.js
│ ├── index.js
│ ├── middleware.js
│ ├── reducer.js
│ └── sync.js
├── test/
│ ├── .eslintrc
│ ├── _createSyncTest.js
│ ├── actions.spec.js
│ ├── browser/
│ │ └── index.js
│ ├── middleware.spec.js
│ ├── reducer.spec.js
│ └── sync.spec.js
├── tests.webpack.js
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["es2015", "react", "stage-1"],
"plugins": [
"transform-es3-member-expression-literals",
"transform-es3-property-literals"
]
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .eslintignore
================================================
examples/*/node_modules/
examples/*/dist/
================================================
FILE: .eslintrc
================================================
{
"extends": "react-app"
}
================================================
FILE: .gitignore
================================================
lib
dist
node_modules
coverage
*.log
================================================
FILE: .travis.yml
================================================
sudo: false
branches:
only:
- master
language: node_js
cache:
directories:
- node_modules
node_js:
- "4"
- "6"
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
script:
- npm run test
================================================
FILE: CHANGELOG.md
================================================
## [4.0.8](https://github.com/reactjs/react-router-redux/compare/v4.0.7...v4.0.8)
- Don't run listeners synchronously with history 3 [`b2c2259`](https://github.com/reactjs/react-router-redux/commit/b2c2259c189cafad3e6e68eac7729e74f2bd56a9)
## [4.0.7](https://github.com/reactjs/react-router-redux/compare/v4.0.6...v4.0.7)
- Support history 3 [#476](https://github.com/reactjs/react-router-redux/pull/476)
## [4.0.6](https://github.com/reactjs/react-router-redux/compare/v4.0.5...v4.0.6)
- Makes sure the state in the store matches the state in history when using SSR [#445](https://github.com/reactjs/react-router-redux/pull/445)
## [4.0.5](https://github.com/reactjs/react-router-redux/compare/v4.0.4...v4.0.5)
- Initialize currentLocation to initial location from the store. [#403](https://github.com/reactjs/react-router-redux/pull/403)
## [4.0.4](https://github.com/reactjs/react-router-redux/compare/v4.0.2...v4.0.4)
- Added a UMD build. [#362](https://github.com/reactjs/react-router-redux/pull/362)
## [4.0.2](https://github.com/reactjs/react-router-redux/compare/v4.0.1...v4.0.2)
- Calling routerReducer() with no args crashes. [#350](https://github.com/reactjs/react-router-redux/pull/350)
## [4.0.1](https://github.com/reactjs/react-router-redux/compare/v4.0.0...v4.0.1)
- Fix IE8 compatbility. [#344](https://github.com/reactjs/react-router-redux/pull/344)
## [4.0.0](https://github.com/reactjs/react-router-redux/compare/3.0.0...v4.0.0)
This is a big breaking release, but the last one for the foreseeable future. The scope of this library has changed, so please re-evaluate its usefulness to you. You may not need it and this is ok!
#### Summary of Changes
The provided action creators and middleware are now separate from the history<->state syncing function. For the vast majority of cases, using action creators to trigger navigation is obsoleted by React Router's [new history singletons](https://github.com/reactjs/react-router/blob/master/upgrade-guides/v2.0.0.md#history-singletons-provided) provided in 2.0. Building this functionality in by default and coupling it to our history syncing logic no longer makes sense.
We now sync by enhancing the history instance to listen for navigation events and dispatch those into the store. The enhanced history has its `listen` method overridden to respond to store changes, rather than directly to navigation events. When this history is provided to `<Router>`, the router will listen to it and receive these store changes. This means if we time travel with the store, the router will receive those store changes and update based on the location in the store, instead of what the browser says. Normal navigation events (hitting your browser back/forward buttons, telling a history singleton to `push` a location) flow through the history's listener like normal, so all the usual stuff works A-OK.
## [3.0.0](https://github.com/reactjs/react-router-redux/compare/2.1.0...3.0.0)
Technically, 2.1.0 broke semver. The appropriate @timdorr's have been flogged. So, we're bumping the major version to catch up.
- Fixed Resets in Redux Dev Tools. [3ae8110f](https://github.com/reactjs/react-router-redux/commit/3ae8110f)
- Ensure the initialState is set properly. [a00acfd4](https://github.com/reactjs/react-router-redux/commit/a00acfd4)
- Support any number of args on action creators [524898b5](https://github.com/reactjs/react-router-redux/commit/524898b5)
## [2.1.0](https://github.com/reactjs/react-router-redux/compare/2.0.4...2.1.0)
- `listenForReplays` has a `selectLocationState` selector. [#218](https://github.com/reactjs/react-router-redux/pull/218)
- Provide unscoped action creators. [#225](https://github.com/reactjs/react-router-redux/pull/225)
- Example updated to use fully ES2015 syntax.
## [2.0.4](https://github.com/reactjs/react-router-redux/compare/2.0.2...2.0.4)
- Remove `history` module published within the tarball. [#133](https://github.com/reactjs/react-router-redux/issues/133)
- Make actions conform to [Flux Standard Action](https://github.com/acdlite/flux-standard-action). [#208](https://github.com/reactjs/react-router-redux/pull/208)
## [2.0.2](https://github.com/reactjs/react-router-redux/compare/1.0.2...2.0.2)
Versions 2.0.0 and 2.0.1 were test releases for the 2.* series. 2.0.2 is the first public release.
**A whole new API, with many breaking changes:**
* `syncReduxAndRouter` is gone. Instead, call `syncHistory` with just the `history` object, which returns a middleware that you need to apply. (#141)
* If you use redux devtools, you need to call `middleware.listenForReplays(store)` on the middleware returned from `syncHistory`. Create the store first with the middleware, then call this function with the store.
* Action creators are now contained in a single object called `routeActions`. `go`, `goBack`, and `goForward` action creators have been added.
* `UPDATE_PATH` is now `UPDATE_LOCATION`.
* The fully parsed [location object](https://github.com/reactjs/history/blob/master/docs/Location.md) is now stored in the state instead of a URL string. To access the path, use `state.routing.location.pathname` instead of `state.routing.path`.
[View the new docs](https://github.com/reactjs/react-router-redux#api)
## [1.0.2](https://github.com/reactjs/react-router-redux/compare/1.0.1...1.0.2)
* Only publish relevant files to npm
## [1.0.1](https://github.com/reactjs/react-router-redux/compare/1.0.0...1.0.1)
* Solve problem with `basename` causing infinite redirects (#103)
* Switched to ES6 imports/exports internally, but should not affect outside users
## [1.0.0](https://github.com/reactjs/react-router-redux/compare/0.0.10...1.0.0)
> 2015-12-09
This release changes quite a bit so you'll have to update your code.
**Breaking Changes:**
* The `updatePath` action creator has been removed in favor of `pushPath` and `replacePath`. Use `pushPath` to get the same behavior as before. (#38)
* We have added support for routing state (#38)
* Our actions are now [FSA compliant](https://github.com/acdlite/flux-standard-action). This means if you are listening for the `UPDATE_PATH` action in a reducer you should get properties off the `payload` property. (#63)
Other fixes:
* Redux DevTools should now work as expected (#73)
* As we no longer depend on `window.location`, `<base href="...">` should now work (#62)
* We've done lots of work on finding the right way to stop cycles, so hopefully we shouldn't have any unnecessary location or store updates (#50)
================================================
FILE: LICENSE
================================================
Copyright (c) 2015-present James Long
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Project Deprecated
This project is no longer maintained. For your Redux <-> Router syncing needs with React Router 4+, please see one of these libraries instead:
* [connected-react-router](https://github.com/supasate/connected-react-router)
---
⚠️ **This repo is for react-router-redux 4.x, which is only compatible with react-router 2.x and 3.x**
# react-router-redux
[](https://www.npmjs.com/package/react-router-redux) [](https://www.npmjs.com/package/react-router-redux) [](https://travis-ci.org/reactjs/react-router-redux)
> **Keep your router in sync with application state** :sparkles:
_Formerly known as redux-simple-router_
You're a smart person. You use [Redux](https://github.com/reactjs/redux) to manage your application state. You use [React Router](https://github.com/reactjs/react-router) to do routing. All is good.
But the two libraries don't coordinate. You want to do time travel with your application state, but React Router doesn't navigate between pages when you replay actions. It controls an important part of application state: the URL.
This library helps you keep that bit of state in sync with your Redux store. We keep a copy of the current location hidden in state. When you rewind your application state with a tool like [Redux DevTools](https://github.com/gaearon/redux-devtools), that state change is propagated to React Router so it can adjust the component tree accordingly. You can jump around in state, rewinding, replaying, and resetting as much as you'd like, and this library will ensure the two stay in sync at all times.
**This library is not _necessary_ for using Redux together with React Router. You can use the two together just fine without any additional libraries. It is useful if you care about recording, persisting, and replaying user actions, using time travel. If you don't care about these features, just [use Redux and React Router directly](http://stackoverflow.com/questions/36722584/how-to-sync-redux-state-and-url-hash-tag-params/36749963#36749963).**
## Installation
```
npm install --save react-router-redux
```
## How It Works
This library allows you to use React Router's APIs as they are documented. And, you can use redux like you normally would, with a single app state. The library simply enhances a history instance to allow it to synchronize any changes it receives into application state.
[history](https://github.com/reactjs/history) + `store` ([redux](https://github.com/reactjs/redux)) → [**react-router-redux**](https://github.com/reactjs/react-router-redux) → enhanced [history](https://github.com/reactjs/history) → [react-router](https://github.com/reactjs/react-router)
## Tutorial
Let's take a look at a simple example.
```js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { syncHistoryWithStore, routerReducer } from 'react-router-redux'
import reducers from '<project-path>/reducers'
// Add the reducer to your store on the `routing` key
const store = createStore(
combineReducers({
...reducers,
routing: routerReducer
})
)
// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(browserHistory, store)
ReactDOM.render(
<Provider store={store}>
{ /* Tell the Router to use our enhanced history */ }
<Router history={history}>
<Route path="/" component={App}>
<Route path="foo" component={Foo}/>
<Route path="bar" component={Bar}/>
</Route>
</Router>
</Provider>,
document.getElementById('mount')
)
```
Now any time you navigate, which can come from pressing browser buttons or navigating in your application code, the enhanced history will first pass the new location through the Redux store and then on to React Router to update the component tree. If you time travel, it will also pass the new state to React Router to update the component tree again.
#### How do I watch for navigation events, such as for analytics?
Simply listen to the enhanced history via `history.listen`. This takes in a function that will receive a `location` any time the store updates. This includes any time travel activity performed on the store.
```js
const history = syncHistoryWithStore(browserHistory, store)
history.listen(location => analyticsService.track(location.pathname))
```
For other kinds of events in your system, you can use middleware on your Redux store like normal to watch any action that is dispatched to the store.
#### What if I use Immutable.js or another state wrapper with my Redux store?
When using a wrapper for your store's state, such as Immutable.js, you will need to change two things from the standard setup:
1. By default, the library expects to find the history state at `state.routing`. If your wrapper prevents accessing properties directly, or you want to put the routing state elsewhere, pass a selector function to access the historystate via the `selectLocationState` option on `syncHistoryWithStore`.
2. Provide your own reducer function that will receive actions of type `LOCATION_CHANGE` and return the payload merged into the `locationBeforeTransitions` property of the routing state. For example, `state.set("routing", {locationBeforeTransitions: action.payload})`.
These two hooks will allow you to store the state that this library uses in whatever format or wrapper you would like.
#### How do I access router state in a container component?
React Router [provides route information via a route component's props](https://github.com/ReactTraining/react-router/blob/v3/docs/Introduction.md#getting-url-parameters). This makes it easy to access them from a container component. When using [react-redux](https://github.com/reactjs/react-redux) to `connect()` your components to state, you can access the router's props from the [2nd argument of `mapStateToProps`](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options):
```js
function mapStateToProps(state, ownProps) {
return {
id: ownProps.params.id,
filter: ownProps.location.query.filter
};
}
```
You should not read the location state directly from the Redux store. This is because React Router operates asynchronously (to handle things such as dynamically-loaded components) and your component tree may not yet be updated in sync with your Redux state. You should rely on the props passed by React Router, as they are only updated after it has processed all asynchronous code.
#### What if I want to issue navigation events via Redux actions?
React Router provides singleton versions of history (`browserHistory` and `hashHistory`) that you can import and use from anywhere in your application. However, if you prefer Redux style actions, the library also provides a set of action creators and a middleware to capture them and redirect them to your history instance.
```js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { routerMiddleware, push } from 'react-router-redux'
// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
reducers,
applyMiddleware(middleware)
)
// Dispatch from anywhere like normal.
store.dispatch(push('/foo'))
```
## Examples
- [examples/basic](/examples/basic) - basic reference implementation
Examples from the community:
* [react-redux-styled-hot-universal](https://github.com/krasevych/react-redux-styled-hot-universal) (SSR, Universal Webpack, Redux, React-router, Webpack 2, Babel, Styled Components and more...)
* [shakacode/react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial) - react-router-redux including **Server Rendering** using [React on Rails](https://github.com/shakacode/react_on_rails/), live at [www.reactrails.com](http://www.reactrails.com/).
* [davezuko/react-redux-starter-kit](https://github.com/davezuko/react-redux-starter-kit) - popular redux starter kit
* **tip**: migrating from react-router-redux `^3.0.0`? use [this commit](https://github.com/davezuko/react-redux-starter-kit/commit/0df26907) as a reference
* [svrcekmichal/universal-react](https://github.com/svrcekmichal/universal-react) - Universal react app with async actions provided by [svrcekmichal/reasync](https://github.com/svrcekmichal/reasync) package
* [steveniseki/react-router-redux-example](https://github.com/StevenIseki/react-router-redux-example) - minimal react-router-redux example includes css modules and universal rendering
* [choonkending/react-webpack-node](https://github.com/choonkending/react-webpack-node) - Full-stack universal Redux App
* [kuy/treemap-with-router](https://github.com/kuy/treemap-with-router) - An example for react-router-redux with d3's treemap.
→ _Have an example to add? Send us a PR!_ ←
## API
#### `routerReducer()`
**You must add this reducer to your store for syncing to work.**
A reducer function that stores location updates from `history`. If you use `combineReducers`, it should be nested under the `routing` key.
#### `history = syncHistoryWithStore(history, store, [options])`
Creates an enhanced history from the provided history. This history changes `history.listen` to pass all location updates through the provided store first. This ensures if the store is updated either from a navigation event or from a time travel action, such as a replay, the listeners of the enhanced history will stay in sync.
**You must provide the enhanced history to your `<Router>` component.** This ensures your routes stay in sync with your location and your store at the same time.
The `options` object takes in the following optional keys:
- `selectLocationState` - (default `state => state.routing`) A selector function to obtain the history state from your store. Useful when not using the provided `routerReducer` to store history state. Allows you to use wrappers, such as Immutable.js.
- `adjustUrlOnReplay` - (default `true`) When `false`, the URL will not be kept in sync during time travel. This is useful when using `persistState` from Redux DevTools and not wanting to maintain the URL state when restoring state.
#### `push(location)`, `replace(location)`, `go(number)`, `goBack()`, `goForward()`
**You must install `routerMiddleware` for these action creators to work.**
Action creators that correspond with the [history methods of the same name](https://github.com/ReactTraining/history/blob/v3/docs/GettingStarted.md#navigation). For reference they are defined as follows:
- `push` - Pushes a new location to history, becoming the current location.
- `replace` - Replaces the current location in history.
- `go` - Moves backwards or forwards a relative number of locations in history.
- `goForward` - Moves forward one location. Equivalent to `go(1)`
- `goBack` - Moves backwards one location. Equivalent to `go(-1)`
Both `push` and `replace` take in a [location descriptor](https://github.com/ReactTraining/history/blob/v3/docs/Location.md), which can be an object describing the URL or a plain string URL.
These action creators are also available in one single object as `routerActions`, which can be used as a convenience when using Redux's `bindActionCreators()`.
#### `routerMiddleware(history)`
A middleware you can apply to your Redux `store` to capture dispatched actions created by the action creators. It will redirect those actions to the provided `history` instance.
#### `LOCATION_CHANGE`
An action type that you can listen for in your reducers to be notified of route updates. Fires *after* any changes to history.
================================================
FILE: examples/.gitignore
================================================
node_modules/
dist/
*.log
================================================
FILE: examples/basic/.babelrc
================================================
{
"presets": ["es2015", "react", "stage-1"]
}
================================================
FILE: examples/basic/README.md
================================================
react-router-redux basic example
=================================
This is a basic example that demonstrates rendering components based
on URLs with `react-router` as well as connecting them to Redux state.
It uses both `<Link>` elements as well as the `push` action creator
provided by react-router-redux.
This example also demonstrates integration with
**[redux-devtools](https://github.com/gaearon/redux-devtools) ^3.0.0**
**To run, follow these steps:**
1. Install dependencies with `npm install` in this directory (make sure it creates a local node_modules)
2. By default, it uses the local version from `src` of react-router-redux, so you need to run `npm install` from there first. If you want to use a version straight from npm, remove the lines in `webpack.config.js` at the bottom.
3. Start build with `npm start`
4. Open [http://localhost:8080/](http://localhost:8080/)
-
If you want to run the example from the npm published version of
**react-router-redux**, remove the alias in `webpack.config`
to the source from line 21.
This example uses the latest version, switch to a specific tag to use a stable version:
e.g. [react-router-redux tag 1.0.2](https://github.com/reactjs/react-router-redux/tree/1.0.2/examples/basic)
================================================
FILE: examples/basic/actions/count.js
================================================
import { INCREASE, DECREASE } from '../constants'
export function increase(n) {
return {
type: INCREASE,
amount: n
}
}
export function decrease(n) {
return {
type: DECREASE,
amount: n
}
}
================================================
FILE: examples/basic/app.js
================================================
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import { syncHistoryWithStore, routerReducer } from 'react-router-redux'
import * as reducers from './reducers'
import { App, Home, Foo, Bar } from './components'
const reducer = combineReducers({
...reducers,
routing: routerReducer
})
const DevTools = createDevTools(
<DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q">
<LogMonitor theme="tomorrow" preserveScrollTop={false} />
</DockMonitor>
)
const store = createStore(
reducer,
DevTools.instrument()
)
const history = syncHistoryWithStore(browserHistory, store)
ReactDOM.render(
<Provider store={store}>
<div>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="foo" component={Foo}/>
<Route path="bar" component={Bar}/>
</Route>
</Router>
<DevTools />
</div>
</Provider>,
document.getElementById('mount')
)
================================================
FILE: examples/basic/components/App.js
================================================
import React from 'react'
import { Link, browserHistory } from 'react-router'
export default function App({ children }) {
return (
<div>
<header>
Links:
{' '}
<Link to="/">Home</Link>
{' '}
<Link to="/foo">Foo</Link>
{' '}
<Link to="/bar">Bar</Link>
</header>
<div>
<button onClick={() => browserHistory.push('/foo')}>Go to /foo</button>
</div>
<div style={{ marginTop: '1.5em' }}>{children}</div>
</div>
)
}
================================================
FILE: examples/basic/components/Bar.js
================================================
import React from 'react'
export default function Bar() {
return <div>And I am Bar!</div>
}
================================================
FILE: examples/basic/components/Foo.js
================================================
import React from 'react'
export default function Foo() {
return <div>I am Foo!</div>
}
================================================
FILE: examples/basic/components/Home.js
================================================
import React from 'react'
import { connect } from 'react-redux'
import { increase, decrease } from '../actions/count'
function Home({ number, increase, decrease }) {
return (
<div>
Some state changes:
{number}
<button onClick={() => increase(1)}>Increase</button>
<button onClick={() => decrease(1)}>Decrease</button>
</div>
)
}
export default connect(
state => ({ number: state.count.number }),
{ increase, decrease }
)(Home)
================================================
FILE: examples/basic/components/index.js
================================================
export App from './App'
export Home from './Home'
export Foo from './Foo'
export Bar from './Bar'
================================================
FILE: examples/basic/constants.js
================================================
export const INCREASE = 'INCREASE'
export const DECREASE = 'DECREASE'
================================================
FILE: examples/basic/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>react-router-redux basic example</title>
<meta charset="utf8"/>
</head>
<body>
<div id="mount"></div>
<script src="/bundle.js"></script>
</body>
</html>
================================================
FILE: examples/basic/package.json
================================================
{
"name": "rrr-basic-example",
"version": "0.0.0",
"repository": "reactjs/react-router-redux",
"license": "MIT",
"dependencies": {
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-redux": "^4.3.0",
"react-router": "^2.0.0",
"redux": "^3.2.1",
"react-router-redux": "^4.0.0"
},
"devDependencies": {
"babel-core": "^6.4.5",
"babel-eslint": "^5.0.0-beta9",
"babel-loader": "^6.2.2",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-1": "^6.3.13",
"eslint": "^1.10.3",
"eslint-config-rackt": "^1.1.1",
"eslint-plugin-react": "^3.16.1",
"redux-devtools": "^3.1.0",
"redux-devtools-dock-monitor": "^1.0.1",
"redux-devtools-log-monitor": "^1.0.4",
"webpack": "^1.12.13",
"webpack-dev-server": "^1.14.1"
},
"scripts": {
"start": "webpack-dev-server --history-api-fallback --no-info --open"
}
}
================================================
FILE: examples/basic/reducers/count.js
================================================
import { INCREASE, DECREASE } from '../constants'
const initialState = {
number: 1
}
export default function update(state = initialState, action) {
if(action.type === INCREASE) {
return { number: state.number + action.amount }
}
else if(action.type === DECREASE) {
return { number: state.number - action.amount }
}
return state
}
================================================
FILE: examples/basic/reducers/index.js
================================================
export count from './count'
================================================
FILE: examples/basic/webpack.config.js
================================================
/* eslint-disable */
const path = require('path');
module.exports = {
entry: './app.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
loaders: [{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
include: __dirname
}]
}
}
// This will make the redux-simpler-router module resolve to the
// latest src instead of using it from npm. Remove this if running
// outside of the source.
var src = path.join(__dirname, '..', '..', 'src')
var fs = require('fs')
if (fs.existsSync(src)) {
// Use the latest src
module.exports.resolve = { alias: { 'react-router-redux': src } }
module.exports.module.loaders.push({
test: /\.js$/,
loaders: ['babel'],
include: src
});
}
================================================
FILE: examples/server/.babelrc
================================================
{
"presets": ["es2015", "react", "stage-1"],
"plugins": [
["babel-plugin-module-alias", [
{ "src": "../../src", "expose": "react-router-redux" }
]]
]
}
================================================
FILE: examples/server/client.js
================================================
import 'babel-polyfill'
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { Router, browserHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import { configureStore, DevTools } from './store'
import routes from './routes'
const store = configureStore(browserHistory, window.__initialState__)
const history = syncHistoryWithStore(browserHistory, store)
render(
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>,
document.getElementById('root')
)
render(
<Provider store={store}>
<DevTools/>
</Provider>,
document.getElementById('devtools')
)
================================================
FILE: examples/server/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>react-router-redux server rendering example</title>
<meta charset="utf8"/>
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
</html>
================================================
FILE: examples/server/package.json
================================================
{
"name": "rrr-server-example",
"version": "0.0.0",
"repository": "reactjs/react-router-redux",
"license": "MIT",
"dependencies": {
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-redux": "^4.3.0",
"react-router": "^2.0.0",
"react-router-redux": "^4.0.0",
"redux": "^3.2.1",
"serialize-javascript": "^1.1.2"
},
"devDependencies": {
"babel-cli": "^6.5.1",
"babel-core": "^6.4.5",
"babel-eslint": "^5.0.0-beta9",
"babel-loader": "^6.2.2",
"babel-plugin-module-alias": "^1.2.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-1": "^6.3.13",
"babel-register": "^6.5.2",
"eslint": "^1.10.3",
"eslint-config-rackt": "^1.1.1",
"eslint-plugin-react": "^3.16.1",
"express": "^4.13.4",
"redux-devtools": "^3.1.1",
"redux-devtools-dock-monitor": "^1.0.1",
"redux-devtools-log-monitor": "^1.0.4",
"webpack": "^1.12.13",
"webpack-dev-middleware": "^1.5.1"
},
"scripts": {
"start": "babel-node server.js"
}
}
================================================
FILE: examples/server/routes.js
================================================
import React from 'react'
import { Route, IndexRoute, Link } from 'react-router'
const App = ({ children }) => (
<div>
<header>
Links:
{' '}
<Link to="/">Home</Link>
{' '}
<Link to="/foo">Foo</Link>
{' '}
<Link to="/bar">Bar</Link>
</header>
{children}
</div>
)
const Home = () => (<div>Home!</div>)
const Foo = () => (<div>Foo!</div>)
const Bar = () => (<div>Bar!</div>)
const routes = (
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="foo" component={Foo}/>
<Route path="bar" component={Bar}/>
</Route>
)
export default routes
================================================
FILE: examples/server/server.js
================================================
/*eslint-disable no-console */
import express from 'express'
import serialize from 'serialize-javascript'
import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackConfig from './webpack.config'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { Provider } from 'react-redux'
import { createMemoryHistory, match, RouterContext } from 'react-router'
import { syncHistoryWithStore } from '../../src'
import { configureStore } from './store'
import routes from './routes'
const app = express()
app.use(webpackDevMiddleware(webpack(webpackConfig), {
publicPath: '/__build__/',
stats: {
colors: true
}
}))
const HTML = ({ content, store }) => (
<html>
<body>
<div id="root" dangerouslySetInnerHTML={{ __html: content }}/>
<div id="devtools"/>
<script dangerouslySetInnerHTML={{ __html: `window.__initialState__=${serialize(store.getState())};` }}/>
<script src="/__build__/bundle.js"/>
</body>
</html>
)
app.use(function (req, res) {
const memoryHistory = createMemoryHistory(req.url)
const store = configureStore(memoryHistory)
const history = syncHistoryWithStore(memoryHistory, store)
match({ history, routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
const content = renderToString(
<Provider store={store}>
<RouterContext {...renderProps}/>
</Provider>
)
res.send('<!doctype html>\n' + renderToString(<HTML content={content} store={store}/>))
}
})
})
app.listen(8080, function () {
console.log('Server listening on http://localhost:8080, Ctrl+C to stop')
})
================================================
FILE: examples/server/store.js
================================================
import React from 'react'
import { createStore, combineReducers, compose, applyMiddleware } from 'redux'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
import { routerReducer, routerMiddleware } from 'react-router-redux'
export const DevTools = createDevTools(
<DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q">
<LogMonitor theme="tomorrow" preserveScrollTop={false} />
</DockMonitor>
)
export function configureStore(history, initialState) {
const reducer = combineReducers({
routing: routerReducer
})
let devTools = []
if (typeof document !== 'undefined') {
devTools = [ DevTools.instrument() ]
}
const store = createStore(
reducer,
initialState,
compose(
applyMiddleware(
routerMiddleware(history)
),
...devTools
)
)
return store
}
================================================
FILE: examples/server/webpack.config.js
================================================
/* eslint-disable */
const path = require('path')
const webpack = require('webpack')
module.exports = {
devtool: 'inline-source-map',
entry: './client.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/__build__/'
},
module: {
loaders: [{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
query: { plugins: [] }
}]
}
}
// This will make the redux-simpler-router module resolve to the
// latest src instead of using it from npm. Remove this if running
// outside of the source.
var src = path.join(__dirname, '../../src')
var fs = require('fs')
if (fs.existsSync(src)) {
// Use the latest src
module.exports.resolve = { alias: { 'react-router-redux': src } }
module.exports.module.loaders.push({
test: /\.js$/,
loaders: ['babel'],
include: src
});
}
================================================
FILE: karma.conf.js
================================================
'use strict'
const path = require('path')
module.exports = function (config) {
let runCoverage = process.env.COVERAGE === 'true'
let coverageLoaders = []
let coverageReporters = []
if (runCoverage) {
coverageLoaders.push({
test: /\.js$/,
include: path.resolve('src/'),
loader: 'isparta'
}),
coverageReporters.push('coverage')
}
config.set({
browsers: [ 'Firefox' ],
frameworks: [ 'mocha' ],
reporters: [ 'mocha' ].concat(coverageReporters),
files: [
'tests.webpack.js'
],
preprocessors: {
'tests.webpack.js': [ 'webpack', 'sourcemap' ]
},
singleRun: true,
webpack: {
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.js$/,
enforce: "pre",
use: 'babel-loader',
include: [
path.resolve('src/'),
path.resolve('test/')
]
}
].concat(coverageLoaders)
}
},
webpackServer: {
noInfo: true
},
coverageReporter: {
reporters: [
{ type: 'text' },
{ type: 'json', subdir: 'browser-coverage', file: 'coverage.json' }
]
}
})
}
================================================
FILE: package.json
================================================
{
"name": "react-router-redux",
"version": "4.0.8",
"description": "Ruthlessly simple bindings to keep react-router and redux in sync",
"main": "lib/index",
"files": [
"*.md",
"dist",
"LICENSE",
"lib",
"src"
],
"repository": "reactjs/react-router-redux",
"authors": [
"James Long",
"Tim Dorr"
],
"license": "MIT",
"scripts": {
"build": "npm run build:commonjs & npm run build:umd & npm run build:umd:min",
"build:commonjs": "mkdir -p lib && babel ./src -d lib",
"build:umd": "webpack dist/ReactRouterRedux.js",
"build:umd:min": "NODE_ENV=production webpack dist/ReactRouterRedux.min.js",
"lint": "eslint examples src test",
"test": "npm run lint && npm run test:node && npm run test:browser",
"test:node": "mocha --compilers js:babel-register --recursive ./test/*.spec.js",
"test:browser": "karma start",
"test:cov": "npm run test:cov:browser && npm run test:cov:node && npm run test:cov:report",
"test:cov:node": "babel-node $(npm bin)/isparta cover $(npm bin)/_mocha report --dir ./coverage/node-coverage -- --recursive ./test/node",
"test:cov:browser": "COVERAGE=true karma start",
"test:cov:report": "$(npm bin)/istanbul report --dir ./coverage --include **/*coverage.json html text",
"prepublish": "npm run build"
},
"tags": [
"react",
"redux"
],
"keywords": [
"react",
"redux",
"router"
],
"devDependencies": {
"babel-cli": "^6.1.2",
"babel-core": "^6.7.4",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.0",
"babel-plugin-transform-es3-member-expression-literals": "^6.5.0",
"babel-plugin-transform-es3-property-literals": "^6.5.0",
"babel-polyfill": "^6.7.4",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-1": "^6.3.13",
"babel-register": "^6.4.3",
"eslint": "^3.15.0",
"eslint-config-react-app": "^0.5.0",
"eslint-plugin-flowtype": "^2.29.2",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.8.0",
"expect": "^1.13.0",
"history": "^3.0.0",
"isparta": "^4.0.0",
"isparta-loader": "^2.0.0",
"karma": "^1.4.1",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.0.0",
"karma-mocha": "^1.3.0",
"karma-mocha-reporter": "^2.2.2",
"karma-sourcemap-loader": "^0.3.5",
"karma-webpack": "^2.0.2",
"mocha": "^3.2.0",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-redux": "^5.0.2",
"react-router": "^3.0.0",
"redux": "^3.0.4",
"redux-devtools": "^3.0.0",
"redux-devtools-dock-monitor": "^1.0.1",
"redux-devtools-log-monitor": "^1.0.1",
"webpack": "^2.2.1"
}
}
================================================
FILE: src/actions.js
================================================
/**
* This action type will be dispatched by the history actions below.
* If you're writing a middleware to watch for navigation events, be sure to
* look for actions of this type.
*/
export const CALL_HISTORY_METHOD = '@@router/CALL_HISTORY_METHOD'
function updateLocation(method) {
return (...args) => ({
type: CALL_HISTORY_METHOD,
payload: { method, args }
})
}
/**
* These actions correspond to the history API.
* The associated routerMiddleware will capture these events before they get to
* your reducer and reissue them as the matching function on your history.
*/
export const push = updateLocation('push')
export const replace = updateLocation('replace')
export const go = updateLocation('go')
export const goBack = updateLocation('goBack')
export const goForward = updateLocation('goForward')
export const routerActions = { push, replace, go, goBack, goForward }
================================================
FILE: src/index.js
================================================
export syncHistoryWithStore from './sync'
export { LOCATION_CHANGE, routerReducer } from './reducer'
export {
CALL_HISTORY_METHOD,
push, replace, go, goBack, goForward,
routerActions
} from './actions'
export routerMiddleware from './middleware'
================================================
FILE: src/middleware.js
================================================
import { CALL_HISTORY_METHOD } from './actions'
/**
* This middleware captures CALL_HISTORY_METHOD actions to redirect to the
* provided history object. This will prevent these actions from reaching your
* reducer or any middleware that comes after this one.
*/
export default function routerMiddleware(history) {
return () => next => action => {
if (action.type !== CALL_HISTORY_METHOD) {
return next(action)
}
const { payload: { method, args } } = action
history[method](...args)
}
}
================================================
FILE: src/reducer.js
================================================
/**
* This action type will be dispatched when your history
* receives a location change.
*/
export const LOCATION_CHANGE = '@@router/LOCATION_CHANGE'
const initialState = {
locationBeforeTransitions: null
}
/**
* This reducer will update the state with the most recent location history
* has transitioned to. This may not be in sync with the router, particularly
* if you have asynchronously-loaded routes, so reading from and relying on
* this state is discouraged.
*/
export function routerReducer(state = initialState, { type, payload } = {}) {
if (type === LOCATION_CHANGE) {
return { ...state, locationBeforeTransitions: payload }
}
return state
}
================================================
FILE: src/sync.js
================================================
import { LOCATION_CHANGE } from './reducer'
const defaultSelectLocationState = state => state.routing
/**
* This function synchronizes your history state with the Redux store.
* Location changes flow from history to the store. An enhanced history is
* returned with a listen method that responds to store updates for location.
*
* When this history is provided to the router, this means the location data
* will flow like this:
* history.push -> store.dispatch -> enhancedHistory.listen -> router
* This ensures that when the store state changes due to a replay or other
* event, the router will be updated appropriately and can transition to the
* correct router state.
*/
export default function syncHistoryWithStore(history, store, {
selectLocationState = defaultSelectLocationState,
adjustUrlOnReplay = true
} = {}) {
// Ensure that the reducer is mounted on the store and functioning properly.
if (typeof selectLocationState(store.getState()) === 'undefined') {
throw new Error(
'Expected the routing state to be available either as `state.routing` ' +
'or as the custom expression you can specify as `selectLocationState` ' +
'in the `syncHistoryWithStore()` options. ' +
'Ensure you have added the `routerReducer` to your store\'s ' +
'reducers via `combineReducers` or whatever method you use to isolate ' +
'your reducers.'
)
}
let initialLocation
let isTimeTraveling
let unsubscribeFromStore
let unsubscribeFromHistory
let currentLocation
// What does the store say about current location?
const getLocationInStore = (useInitialIfEmpty) => {
const locationState = selectLocationState(store.getState())
return locationState.locationBeforeTransitions ||
(useInitialIfEmpty ? initialLocation : undefined)
}
// Init initialLocation with potential location in store
initialLocation = getLocationInStore()
// If the store is replayed, update the URL in the browser to match.
if (adjustUrlOnReplay) {
const handleStoreChange = () => {
const locationInStore = getLocationInStore(true)
if (currentLocation === locationInStore || initialLocation === locationInStore) {
return
}
// Update address bar to reflect store state
isTimeTraveling = true
currentLocation = locationInStore
history.transitionTo({
...locationInStore,
action: 'PUSH'
})
isTimeTraveling = false
}
unsubscribeFromStore = store.subscribe(handleStoreChange)
handleStoreChange()
}
// Whenever location changes, dispatch an action to get it in the store
const handleLocationChange = (location) => {
// ... unless we just caused that location change
if (isTimeTraveling) {
return
}
// Remember where we are
currentLocation = location
// Are we being called for the first time?
if (!initialLocation) {
// Remember as a fallback in case state is reset
initialLocation = location
// Respect persisted location, if any
if (getLocationInStore()) {
return
}
}
// Tell the store to update by dispatching an action
store.dispatch({
type: LOCATION_CHANGE,
payload: location
})
}
unsubscribeFromHistory = history.listen(handleLocationChange)
// History 3.x doesn't call listen synchronously, so fire the initial location change ourselves
if (history.getCurrentLocation) {
handleLocationChange(history.getCurrentLocation())
}
// The enhanced history uses store as source of truth
return {
...history,
// The listeners are subscribed to the store instead of history
listen(listener) {
// Copy of last location.
let lastPublishedLocation = getLocationInStore(true)
// Keep track of whether we unsubscribed, as Redux store
// only applies changes in subscriptions on next dispatch
let unsubscribed = false
const unsubscribeFromStore = store.subscribe(() => {
const currentLocation = getLocationInStore(true)
if (currentLocation === lastPublishedLocation) {
return
}
lastPublishedLocation = currentLocation
if (!unsubscribed) {
listener(lastPublishedLocation)
}
})
// History 2.x listeners expect a synchronous call. Make the first call to the
// listener after subscribing to the store, in case the listener causes a
// location change (e.g. when it redirects)
if (!history.getCurrentLocation) {
listener(lastPublishedLocation)
}
// Let user unsubscribe later
return () => {
unsubscribed = true
unsubscribeFromStore()
}
},
// It also provides a way to destroy internal listeners
unsubscribe() {
if (adjustUrlOnReplay) {
unsubscribeFromStore()
}
unsubscribeFromHistory()
}
}
}
================================================
FILE: test/.eslintrc
================================================
{
"env": {
"mocha": true
}
}
================================================
FILE: test/_createSyncTest.js
================================================
import expect from 'expect'
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, useRouterHistory } from 'react-router'
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux'
import { ActionCreators, instrument } from 'redux-devtools'
import syncHistoryWithStore from '../src/sync'
import { routerReducer } from '../src/reducer'
expect.extend({
toContainLocation({
pathname,
search = '',
hash = '',
state = null,
query,
action = 'PUSH'
}) {
const { locationBeforeTransitions } = this.actual.getState().routing
const location = locationBeforeTransitions
expect(location.pathname).toEqual(pathname)
expect(location.search).toEqual(search)
expect(location.hash).toEqual(hash)
expect(location.state).toEqual(state)
expect(location.query).toEqual(query)
expect(location.action).toEqual(action)
}
})
function createSyncedHistoryAndStore(originalHistory) {
const store = createStore(combineReducers({
routing: routerReducer
}))
const history = syncHistoryWithStore(originalHistory, store)
return { history, store }
}
const defaultReset = () => {}
export default function createTests(createHistory, name, reset = defaultReset) {
describe(name, () => {
beforeEach(reset)
describe('syncHistoryWithStore', () => {
let history, store
beforeEach(() => {
let synced = createSyncedHistoryAndStore(createHistory())
history = synced.history
store = synced.store
})
afterEach(() => {
history.unsubscribe()
})
it('syncs history -> redux', () => {
expect(store).toContainLocation({
pathname: '/',
action: 'POP'
})
history.push('/foo')
expect(store).toContainLocation({
pathname: '/foo'
})
history.push({ state: { bar: 'baz' }, pathname: '/foo' })
expect(store).toContainLocation({
pathname: '/foo',
state: { bar: 'baz' },
action: 'PUSH'
})
history.replace('/bar')
expect(store).toContainLocation({
pathname: '/bar',
action: 'REPLACE'
})
history.push('/bar')
expect(store).toContainLocation({
pathname: '/bar',
action: 'REPLACE' // Converted by history.
})
history.push('/bar?query=1')
expect(store).toContainLocation({
pathname: '/bar',
search: '?query=1'
})
history.push('/bar#baz')
expect(store).toContainLocation({
pathname: '/bar',
hash: '#baz'
})
history.replace({
pathname: '/bar',
search: '?query=1',
state: { bar: 'baz' }
})
expect(store).toContainLocation({
pathname: '/bar',
search: '?query=1',
state: { bar: 'baz' },
action: 'REPLACE'
})
history.replace({
pathname: '/bar',
search: '?query=1',
hash: '#hash=2',
state: { bar: 'baz' }
})
expect(store).toContainLocation({
pathname: '/bar',
search: '?query=1',
hash: '#hash=2',
state: { bar: 'baz' },
action: 'REPLACE'
})
})
it('provides an unsubscribe method to stop listening to history and store', () => {
history.push('/foo')
expect(store).toContainLocation({
pathname: '/foo'
})
history.unsubscribe()
history.push('/bar')
expect(store).toContainLocation({
pathname: '/foo'
})
})
it('updates the router even if path is the same', () => {
history.push('/')
const updates = []
const historyUnsubscribe = history.listen(location => {
updates.push(location.pathname)
})
history.push('/foo')
history.push('/foo')
history.replace('/foo')
expect(updates).toEqual([ '/foo', '/foo', '/foo' ])
historyUnsubscribe()
})
})
describe('Server', () => {
it('handles inital load correctly', () => {
// Server
const { store: serverStore } = createSyncedHistoryAndStore(createHistory('/'))
expect(serverStore).toContainLocation({
pathname: '/',
action: 'POP'
})
// Client
let clientStore = createStore(combineReducers({
routing: routerReducer
}), serverStore.getState())
let clientHistory = useRouterHistory(createHistory)()
const historyListen = expect.createSpy()
const historyUnsubscribe = clientHistory.listen(historyListen)
syncHistoryWithStore(clientHistory, clientStore)
// History v3: Listener should not be called during initialization
expect(historyListen.calls.length).toBe(0)
clientStore.dispatch({
type: 'non-router'
})
// We expect that we still didn't get any call to history after a non-router action is dispatched
expect(historyListen.calls.length).toBe(0)
historyUnsubscribe()
})
})
describe('Redux DevTools', () => {
let originalHistory, history, store, devToolsStore
beforeEach(() => {
originalHistory = createHistory()
// Set initial URL before syncing
originalHistory.push('/foo')
store = createStore(
combineReducers({
routing: routerReducer
}),
instrument()
)
devToolsStore = store.liftedStore
history = syncHistoryWithStore(originalHistory, store)
})
afterEach(() => {
history.unsubscribe()
})
it('resets to the initial url', () => {
let currentPath
const historyUnsubscribe = history.listen(location => {
currentPath = location.pathname
})
history.push('/bar')
devToolsStore.dispatch(ActionCreators.reset())
expect(currentPath).toEqual('/foo')
historyUnsubscribe()
})
it('handles toggle after history change', () => {
let currentPath
const historyUnsubscribe = history.listen(location => {
currentPath = location.pathname
})
history.push('/foo2') // DevTools action #2
history.push('/foo3') // DevTools action #3
// When we toggle an action, the devtools will revert the action
// and we therefore expect the history to update to the previous path
devToolsStore.dispatch(ActionCreators.toggleAction(3))
expect(currentPath).toEqual('/foo2')
historyUnsubscribe()
})
})
if (typeof(document) !== 'undefined') {
describe('Redux Router component', () => {
let store, history, rootElement
beforeEach(() => {
store = createStore(combineReducers({
routing: routerReducer
}))
history = syncHistoryWithStore(useRouterHistory(createHistory)(), store)
rootElement = document.createElement('div')
document.body.appendChild(rootElement)
})
afterEach(() => {
history.unsubscribe()
rootElement.parentNode.removeChild(rootElement)
})
it('syncs history -> components', () => {
history.push('/foo')
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={props => (<span>{props.children}</span>)}>
<Route path="foo" component={() => (<span>at /foo</span>)} />
<Route path="bar" component={() => (<span>at /bar</span>)} />
</Route>
</Router>
</Provider>,
rootElement
)
expect(rootElement.textContent).toEqual('at /foo')
history.push('/bar')
expect(rootElement.textContent).toEqual('at /bar')
})
it('syncs history -> components when the initial route gets replaced', () => {
history.push('/foo')
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={props => (<span>{props.children}</span>)}>
<Route path="foo" onEnter={(nextState, replace) => replace('/bar')} />
<Route path="bar" component={() => (<span>at /bar</span>)} />
</Route>
</Router>
</Provider>,
rootElement
)
expect(rootElement.textContent).toEqual('at /bar')
})
})
}
})
}
================================================
FILE: test/actions.spec.js
================================================
import expect from 'expect'
import {
CALL_HISTORY_METHOD,
push, replace, go, goBack, goForward
} from '../src/actions'
describe('routerActions', () => {
describe('push', () => {
it('creates actions', () => {
expect(push('/foo')).toEqual({
type: CALL_HISTORY_METHOD,
payload: {
method: 'push',
args: [ '/foo' ]
}
})
expect(push({ pathname: '/foo', state: { the: 'state' } })).toEqual({
type: CALL_HISTORY_METHOD,
payload: {
method: 'push',
args: [ {
pathname: '/foo',
state: { the: 'state' }
} ]
}
})
expect(push('/foo', 'baz', 123)).toEqual({
type: CALL_HISTORY_METHOD,
payload: {
method: 'push',
args: [ '/foo' , 'baz', 123 ]
}
})
})
})
describe('replace', () => {
it('creates actions', () => {
expect(replace('/foo')).toEqual({
type: CALL_HISTORY_METHOD,
payload: {
method: 'replace',
args: [ '/foo' ]
}
})
expect(replace({ pathname: '/foo', state: { the: 'state' } })).toEqual({
type: CALL_HISTORY_METHOD,
payload: {
method: 'replace',
args: [ {
pathname: '/foo',
state: { the: 'state' }
} ]
}
})
})
})
describe('go', () => {
it('creates actions', () => {
expect(go(1)).toEqual({
type: CALL_HISTORY_METHOD,
payload: {
method: 'go',
args: [ 1 ]
}
})
})
})
describe('goBack', () => {
it('creates actions', () => {
expect(goBack()).toEqual({
type: CALL_HISTORY_METHOD,
payload: {
method: 'goBack',
args: []
}
})
})
})
describe('goForward', () => {
it('creates actions', () => {
expect(goForward()).toEqual({
type: CALL_HISTORY_METHOD,
payload: {
method: 'goForward',
args: []
}
})
})
})
})
================================================
FILE: test/browser/index.js
================================================
import 'babel-polyfill'
import { createHashHistory, createHistory } from 'history'
import createTests from '../_createSyncTest'
createTests(createHashHistory, 'Hash History', () => window.location = '#/')
createTests(createHistory, 'Browser History', () => window.history.replaceState(null, null, '/'))
================================================
FILE: test/middleware.spec.js
================================================
import expect, { createSpy } from 'expect'
import { push, replace } from '../src/actions'
import routerMiddleware from '../src/middleware'
describe('routerMiddleware', () => {
let history, next, dispatch
beforeEach(() => {
history = {
push: createSpy(),
replace: createSpy()
}
next = createSpy()
dispatch = routerMiddleware(history)()(next)
})
it('calls the appropriate history method', () => {
dispatch(push('/foo'))
expect(history.push).toHaveBeenCalled()
dispatch(replace('/foo'))
expect(history.replace).toHaveBeenCalled()
expect(next).toNotHaveBeenCalled()
})
it('ignores other actions', () => {
dispatch({ type: 'FOO' })
expect(next).toHaveBeenCalled()
})
})
================================================
FILE: test/reducer.spec.js
================================================
import expect from 'expect'
import { LOCATION_CHANGE, routerReducer } from '../src/reducer'
describe('routerReducer', () => {
const state = {
locationBeforeTransitions: {
pathname: '/foo',
action: 'POP'
}
}
it('updates the path', () => {
expect(routerReducer(state, {
type: LOCATION_CHANGE,
payload: {
path: '/bar',
action: 'PUSH'
}
})).toEqual({
locationBeforeTransitions: {
path: '/bar',
action: 'PUSH'
}
})
})
it('works with initialState', () => {
expect(routerReducer(undefined, {
type: LOCATION_CHANGE,
payload: {
path: '/bar',
action: 'PUSH'
}
})).toEqual({
locationBeforeTransitions: {
path: '/bar',
action: 'PUSH'
}
})
})
it('respects replace', () => {
expect(routerReducer(state, {
type: LOCATION_CHANGE,
payload: {
path: '/bar',
action: 'REPLACE'
}
})).toEqual({
locationBeforeTransitions: {
path: '/bar',
action: 'REPLACE'
}
})
})
})
================================================
FILE: test/sync.spec.js
================================================
import { createMemoryHistory } from 'history'
import createTests from './_createSyncTest'
createTests(createMemoryHistory, 'Memory History')
================================================
FILE: tests.webpack.js
================================================
const browserContext = require.context('./test/browser')
browserContext.keys().forEach(browserContext)
================================================
FILE: webpack.config.js
================================================
var webpack = require('webpack')
var config = {
entry: './src/index',
module: {
rules: [
{ test: /\.js$/, use: [ 'babel-loader' ], exclude: /node_modules/ }
]
},
output: {
library: 'ReactRouterRedux',
libraryTarget: 'umd'
}
}
if (process.env.NODE_ENV === 'production') {
config.plugins = [
new webpack.optimize.UglifyJsPlugin({ sourceMap: true }),
new webpack.LoaderOptionsPlugin({ minimize: true })
]
}
module.exports = config
gitextract_who3sr76/ ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples/ │ ├── .gitignore │ ├── basic/ │ │ ├── .babelrc │ │ ├── README.md │ │ ├── actions/ │ │ │ └── count.js │ │ ├── app.js │ │ ├── components/ │ │ │ ├── App.js │ │ │ ├── Bar.js │ │ │ ├── Foo.js │ │ │ ├── Home.js │ │ │ └── index.js │ │ ├── constants.js │ │ ├── index.html │ │ ├── package.json │ │ ├── reducers/ │ │ │ ├── count.js │ │ │ └── index.js │ │ └── webpack.config.js │ └── server/ │ ├── .babelrc │ ├── client.js │ ├── index.html │ ├── package.json │ ├── routes.js │ ├── server.js │ ├── store.js │ └── webpack.config.js ├── karma.conf.js ├── package.json ├── src/ │ ├── actions.js │ ├── index.js │ ├── middleware.js │ ├── reducer.js │ └── sync.js ├── test/ │ ├── .eslintrc │ ├── _createSyncTest.js │ ├── actions.spec.js │ ├── browser/ │ │ └── index.js │ ├── middleware.spec.js │ ├── reducer.spec.js │ └── sync.spec.js ├── tests.webpack.js └── webpack.config.js
SYMBOL INDEX (19 symbols across 13 files)
FILE: examples/basic/actions/count.js
function increase (line 3) | function increase(n) {
function decrease (line 10) | function decrease(n) {
FILE: examples/basic/components/App.js
function App (line 4) | function App({ children }) {
FILE: examples/basic/components/Bar.js
function Bar (line 3) | function Bar() {
FILE: examples/basic/components/Foo.js
function Foo (line 3) | function Foo() {
FILE: examples/basic/components/Home.js
function Home (line 5) | function Home({ number, increase, decrease }) {
FILE: examples/basic/constants.js
constant INCREASE (line 2) | const INCREASE = 'INCREASE'
constant DECREASE (line 3) | const DECREASE = 'DECREASE'
FILE: examples/basic/reducers/count.js
function update (line 7) | function update(state = initialState, action) {
FILE: examples/server/store.js
function configureStore (line 16) | function configureStore(history, initialState) {
FILE: src/actions.js
constant CALL_HISTORY_METHOD (line 6) | const CALL_HISTORY_METHOD = '@@router/CALL_HISTORY_METHOD'
function updateLocation (line 8) | function updateLocation(method) {
FILE: src/middleware.js
function routerMiddleware (line 8) | function routerMiddleware(history) {
FILE: src/reducer.js
constant LOCATION_CHANGE (line 5) | const LOCATION_CHANGE = '@@router/LOCATION_CHANGE'
function routerReducer (line 17) | function routerReducer(state = initialState, { type, payload } = {}) {
FILE: src/sync.js
function syncHistoryWithStore (line 17) | function syncHistoryWithStore(history, store, {
FILE: test/_createSyncTest.js
method toContainLocation (line 14) | toContainLocation({
function createSyncedHistoryAndStore (line 35) | function createSyncedHistoryAndStore(originalHistory) {
function createTests (line 47) | function createTests(createHistory, name, reset = defaultReset) {
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (63K chars).
[
{
"path": ".babelrc",
"chars": 168,
"preview": "{\n \"presets\": [\"es2015\", \"react\", \"stage-1\"],\n \"plugins\": [\n \"transform-es3-member-expression-literals\",\n "
},
{
"path": ".editorconfig",
"chars": 171,
"preview": "root = true\n\n[*]\ninsert_final_newline = true\ncharset = utf-8\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespa"
},
{
"path": ".eslintignore",
"chars": 42,
"preview": "examples/*/node_modules/\nexamples/*/dist/\n"
},
{
"path": ".eslintrc",
"chars": 31,
"preview": "{\n \"extends\": \"react-app\"\n}\n"
},
{
"path": ".gitignore",
"chars": 37,
"preview": "lib\ndist\nnode_modules\ncoverage\n*.log\n"
},
{
"path": ".travis.yml",
"chars": 227,
"preview": "sudo: false\nbranches:\n only:\n - master\nlanguage: node_js\ncache:\n directories:\n - node_modules\nnode_js:\n - \"4\"\n -"
},
{
"path": "CHANGELOG.md",
"chars": 6508,
"preview": "## [4.0.8](https://github.com/reactjs/react-router-redux/compare/v4.0.7...v4.0.8)\n\n- Don't run listeners synchronously w"
},
{
"path": "LICENSE",
"chars": 1062,
"preview": "Copyright (c) 2015-present James Long\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nt"
},
{
"path": "README.md",
"chars": 12034,
"preview": "# Project Deprecated\n\nThis project is no longer maintained. For your Redux <-> Router syncing needs with React Router 4+"
},
{
"path": "examples/.gitignore",
"chars": 26,
"preview": "node_modules/\ndist/\n*.log\n"
},
{
"path": "examples/basic/.babelrc",
"chars": 50,
"preview": "{\n \"presets\": [\"es2015\", \"react\", \"stage-1\"]\n}\n"
},
{
"path": "examples/basic/README.md",
"chars": 1242,
"preview": "react-router-redux basic example\n=================================\n\nThis is a basic example that demonstrates rendering "
},
{
"path": "examples/basic/actions/count.js",
"chars": 214,
"preview": "import { INCREASE, DECREASE } from '../constants'\n\nexport function increase(n) {\n return {\n type: INCREASE,\n amou"
},
{
"path": "examples/basic/app.js",
"chars": 1310,
"preview": "import { createDevTools } from 'redux-devtools'\nimport LogMonitor from 'redux-devtools-log-monitor'\nimport DockMonitor f"
},
{
"path": "examples/basic/components/App.js",
"chars": 516,
"preview": "import React from 'react'\nimport { Link, browserHistory } from 'react-router'\n\nexport default function App({ children })"
},
{
"path": "examples/basic/components/Bar.js",
"chars": 95,
"preview": "import React from 'react'\n\nexport default function Bar() {\n return <div>And I am Bar!</div>\n}\n"
},
{
"path": "examples/basic/components/Foo.js",
"chars": 91,
"preview": "import React from 'react'\n\nexport default function Foo() {\n return <div>I am Foo!</div>\n}\n"
},
{
"path": "examples/basic/components/Home.js",
"chars": 469,
"preview": "import React from 'react'\nimport { connect } from 'react-redux'\nimport { increase, decrease } from '../actions/count'\n\nf"
},
{
"path": "examples/basic/components/index.js",
"chars": 98,
"preview": "export App from './App'\nexport Home from './Home'\nexport Foo from './Foo'\nexport Bar from './Bar'\n"
},
{
"path": "examples/basic/constants.js",
"chars": 71,
"preview": "\nexport const INCREASE = 'INCREASE'\nexport const DECREASE = 'DECREASE'\n"
},
{
"path": "examples/basic/index.html",
"chars": 214,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>react-router-redux basic example</title>\n <meta charset=\"utf8\"/>\n </head>"
},
{
"path": "examples/basic/package.json",
"chars": 930,
"preview": "{\n \"name\": \"rrr-basic-example\",\n \"version\": \"0.0.0\",\n \"repository\": \"reactjs/react-router-redux\",\n \"license\": \"MIT\","
},
{
"path": "examples/basic/reducers/count.js",
"chars": 352,
"preview": "import { INCREASE, DECREASE } from '../constants'\n\nconst initialState = {\n number: 1\n}\n\nexport default function update("
},
{
"path": "examples/basic/reducers/index.js",
"chars": 28,
"preview": "export count from './count'\n"
},
{
"path": "examples/basic/webpack.config.js",
"chars": 775,
"preview": "/* eslint-disable */\nconst path = require('path');\n\nmodule.exports = {\n entry: './app.js',\n output: {\n path: path.j"
},
{
"path": "examples/server/.babelrc",
"chars": 172,
"preview": "{\n \"presets\": [\"es2015\", \"react\", \"stage-1\"],\n \"plugins\": [\n [\"babel-plugin-module-alias\", [\n { \"src\": \"../../"
},
{
"path": "examples/server/client.js",
"chars": 694,
"preview": "import 'babel-polyfill'\n\nimport React from 'react'\nimport { render } from 'react-dom'\n\nimport { Provider } from 'react-r"
},
{
"path": "examples/server/index.html",
"chars": 224,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title>react-router-redux server rendering example</title>\n <meta charset=\"utf8\"/"
},
{
"path": "examples/server/package.json",
"chars": 1062,
"preview": "{\n \"name\": \"rrr-server-example\",\n \"version\": \"0.0.0\",\n \"repository\": \"reactjs/react-router-redux\",\n \"license\": \"MIT\""
},
{
"path": "examples/server/routes.js",
"chars": 636,
"preview": "import React from 'react'\nimport { Route, IndexRoute, Link } from 'react-router'\n\nconst App = ({ children }) => (\n <div"
},
{
"path": "examples/server/server.js",
"chars": 1876,
"preview": "/*eslint-disable no-console */\nimport express from 'express'\nimport serialize from 'serialize-javascript'\n\nimport webpac"
},
{
"path": "examples/server/store.js",
"chars": 942,
"preview": "import React from 'react'\n\nimport { createStore, combineReducers, compose, applyMiddleware } from 'redux'\nimport { creat"
},
{
"path": "examples/server/webpack.config.js",
"chars": 872,
"preview": "/* eslint-disable */\nconst path = require('path')\nconst webpack = require('webpack')\n\nmodule.exports = {\n devtool: 'inl"
},
{
"path": "karma.conf.js",
"chars": 1224,
"preview": "'use strict'\n\nconst path = require('path')\n\nmodule.exports = function (config) {\n\n let runCoverage = process.env.COVERA"
},
{
"path": "package.json",
"chars": 2746,
"preview": "{\n \"name\": \"react-router-redux\",\n \"version\": \"4.0.8\",\n \"description\": \"Ruthlessly simple bindings to keep react-route"
},
{
"path": "src/actions.js",
"chars": 896,
"preview": "/**\n * This action type will be dispatched by the history actions below.\n * If you're writing a middleware to watch for "
},
{
"path": "src/index.js",
"chars": 253,
"preview": "export syncHistoryWithStore from './sync'\nexport { LOCATION_CHANGE, routerReducer } from './reducer'\n\nexport {\n CALL_HI"
},
{
"path": "src/middleware.js",
"chars": 518,
"preview": "import { CALL_HISTORY_METHOD } from './actions'\n\n/**\n * This middleware captures CALL_HISTORY_METHOD actions to redirect"
},
{
"path": "src/reducer.js",
"chars": 677,
"preview": "/**\n * This action type will be dispatched when your history\n * receives a location change.\n */\nexport const LOCATION_CH"
},
{
"path": "src/sync.js",
"chars": 4904,
"preview": "import { LOCATION_CHANGE } from './reducer'\n\nconst defaultSelectLocationState = state => state.routing\n\n/**\n * This func"
},
{
"path": "test/.eslintrc",
"chars": 37,
"preview": "{\n \"env\": {\n \"mocha\": true\n }\n}\n"
},
{
"path": "test/_createSyncTest.js",
"chars": 8711,
"preview": "import expect from 'expect'\n\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport { Router, Route, useRoute"
},
{
"path": "test/actions.spec.js",
"chars": 2081,
"preview": "import expect from 'expect'\n\nimport {\n CALL_HISTORY_METHOD,\n push, replace, go, goBack, goForward\n} from '../src/actio"
},
{
"path": "test/browser/index.js",
"chars": 305,
"preview": "import 'babel-polyfill'\n\nimport { createHashHistory, createHistory } from 'history'\nimport createTests from '../_createS"
},
{
"path": "test/middleware.spec.js",
"chars": 744,
"preview": "import expect, { createSpy } from 'expect'\n\nimport { push, replace } from '../src/actions'\nimport routerMiddleware from "
},
{
"path": "test/reducer.spec.js",
"chars": 1109,
"preview": "import expect from 'expect'\n\nimport { LOCATION_CHANGE, routerReducer } from '../src/reducer'\n\ndescribe('routerReducer', "
},
{
"path": "test/sync.spec.js",
"chars": 142,
"preview": "import { createMemoryHistory } from 'history'\nimport createTests from './_createSyncTest'\n\ncreateTests(createMemoryHisto"
},
{
"path": "tests.webpack.js",
"chars": 103,
"preview": "const browserContext = require.context('./test/browser')\nbrowserContext.keys().forEach(browserContext)\n"
},
{
"path": "webpack.config.js",
"chars": 475,
"preview": "var webpack = require('webpack')\n\nvar config = {\n entry: './src/index',\n module: {\n rules: [\n { test: /\\.js$/,"
}
]
About this extraction
This page contains the full source code of the reactjs/react-router-redux GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (56.8 KB), approximately 15.3k tokens, and a symbol index with 19 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.