diff --git a/.babelrc b/.babelrc index 2d4d503..6df2603 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,7 @@ { - "presets": ["es2015", "stage-2"] + "presets": ["es2015", "react", "stage-1"], + "plugins": [ + "transform-es3-member-expression-literals", + "transform-es3-property-literals" + ] } diff --git a/.eslintrc b/.eslintrc index 8def310..b218c1e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,11 +1,3 @@ { - "extends": "rackt", - "rules": { - "react/jsx-uses-react": 1, - "react/jsx-no-undef": 2, - "react/wrap-multilines": 2 - }, - "plugins": [ - "react" - ] + "extends": "react-app" } diff --git a/.gitignore b/.gitignore index 7c4bc80..1da2b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ lib +dist node_modules coverage +*.log diff --git a/.travis.yml b/.travis.yml index a54bb7c..ae2c22a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ cache: - node_modules node_js: - "4" - - "5" + - "6" before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start diff --git a/CHANGELOG.md b/CHANGELOG.md index a9000f0..3a76a83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,61 @@ -## [HEAD](https://github.com/rackt/react-router-redux/compare/3.0.0...master) +## [4.0.8](https://github.com/reactjs/react-router-redux/compare/v4.0.7...v4.0.8) -- Nothing yet! +- Don't run listeners synchronously with history 3 [`b2c2259`](https://github.com/reactjs/react-router-redux/commit/b2c2259c189cafad3e6e68eac7729e74f2bd56a9) -## [3.0.0](https://github.com/rackt/react-router-redux/compare/2.1.0...3.0.0) +## [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 ``, 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/rackt/react-router-redux/commit/3ae8110f) -- Ensure the initialState is set properly. [a00acfd4](https://github.com/rackt/react-router-redux/commit/a00acfd4) -- Support any number of args on action creators [524898b5](https://github.com/rackt/react-router-redux/commit/524898b5) +- 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/rackt/react-router-redux/compare/2.0.4...2.1.0) +## [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/rackt/react-router-redux/pull/218) -- Provide unscoped action creators. [#225](https://github.com/rackt/react-router-redux/pull/225) +- `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/rackt/react-router-redux/compare/2.0.2...2.0.4) +## [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/rackt/react-router-redux/issues/133) -- Make actions conform to [Flux Standard Action](https://github.com/acdlite/flux-standard-action). [#208](https://github.com/rackt/react-router-redux/pull/208) +- 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/rackt/react-router-redux/compare/1.0.2...2.0.2) +## [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. @@ -31,20 +65,20 @@ Versions 2.0.0 and 2.0.1 were test releases for the 2.* series. 2.0.2 is the fir * 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/rackt/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`. +* 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/rackt/react-router-redux#api) +[View the new docs](https://github.com/reactjs/react-router-redux#api) -## [1.0.2](https://github.com/rackt/react-router-redux/compare/1.0.1...1.0.2) +## [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/rackt/react-router-redux/compare/1.0.0...1.0.1) +## [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/rackt/react-router-redux/compare/0.0.10...1.0.0) +## [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. diff --git a/LICENSE b/LICENSE index 28160f9..94b1411 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015 James Long +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 diff --git a/README.md b/README.md index 5f5484a..41145ee 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,70 @@ -# react-router-redux +# Project Deprecated -[![npm version](https://img.shields.io/npm/v/react-router-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-router-redux) [![npm downloads](https://img.shields.io/npm/dm/react-router-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-router-redux) [![build status](https://img.shields.io/travis/rackt/react-router-redux/master.svg?style=flat-square)](https://travis-ci.org/rackt/react-router-redux) +This project is no longer maintained. For your Redux <-> Router syncing needs with React Router 4+, please see one of these libraries instead: -**Let react-router do all the work** :sparkles: +* [connected-react-router](https://github.com/supasate/connected-react-router) -_Formerly known as redux-simple-router_ +--- -[Redux](https://github.com/rackt/redux) is awesome. [React Router](https://github.com/rackt/react-router) is cool. The problem is that react-router manages an important piece of your application state: the URL. If you are using redux, you want your app state to fully represent your UI; if you snapshotted the app state, you should be able to load it up later and see the same thing. +⚠️ **This repo is for react-router-redux 4.x, which is only compatible with react-router 2.x and 3.x** -react-router does a great job of mapping the current URL to a component tree, and continually does so with any URL changes. This is very useful, but we really want to store this state in redux as well. +# react-router-redux -The entire state that we are interested in boils down to one thing: the URL. This is an extremely simple library that just puts the URL in redux state and keeps it in sync with any react-router changes. Additionally, you can change the URL via redux and react-router will change accordingly. +[![npm version](https://img.shields.io/npm/v/react-router-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-router-redux) [![npm downloads](https://img.shields.io/npm/dm/react-router-redux.svg?style=flat-square)](https://www.npmjs.com/package/react-router-redux) [![build status](https://img.shields.io/travis/reactjs/react-router-redux/master.svg?style=flat-square)](https://travis-ci.org/reactjs/react-router-redux) -``` -npm install react-router-redux -``` +> **Keep your router in sync with application state** :sparkles: -If you want to install the next major version, use `react-router-redux@next`. Run `npm dist-tag ls react-router-redux` to see what `next` is aliased to. +_Formerly known as redux-simple-router_ -View the [CHANGELOG](https://github.com/rackt/react-router-redux/blob/master/CHANGELOG.md) for recent changes. +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. -Read the [API docs](#api) farther down this page. +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. -**Note:** We are [currently discussing some major changes](https://github.com/rackt/react-router-redux/issues/257) to the library. [React Router's API in 2.0](https://github.com/rackt/react-router/blob/master/upgrade-guides/v2.0.0.md) is significantly improved and obseletes the need for things like action creators and reading location state from the Redux. This library is still critical to enable things like time traveling and persisting state, so we're not going anywhere. But in many cases, you may not need this library and can simply use the provided React Router APIs. Go check them out and drop some technical debt. :smile: +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. -### Usage +**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).** -The idea of this library is to use react-router's functionality exactly like its documentation tells you to. You can access all of its APIs in routing components. Additionally, you can use redux like you normally would, with a single app state. +## Installation -[redux](https://github.com/rackt/redux) (`store.routing`)  ↔  [**react-router-redux**](https://github.com/rackt/react-router-redux)  ↔  [history](https://github.com/rackt/history) (`history.location`)  ↔  [react-router](https://github.com/rackt/react-router) +``` +npm install --save react-router-redux +``` -We only store current URL and state, whereas redux-router stores the entire location object from react-router. You can read it, and also change it with an action. +## How It Works -### Tutorial +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. -Let's take a look at a simple example. +[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) -**Note:** This example uses `react-router`'s 2.0 API, which is currently released under version 2.0.0-rc5. +## Tutorial + +Let's take a look at a simple example. ```js import React from 'react' import ReactDOM from 'react-dom' -import { createStore, combineReducers, applyMiddleware } from 'redux' +import { createStore, combineReducers } from 'redux' import { Provider } from 'react-redux' import { Router, Route, browserHistory } from 'react-router' -import { syncHistory, routeReducer } from 'react-router-redux' -import reducers from '/reducers' - -const reducer = combineReducers(Object.assign({}, reducers, { - routing: routeReducer -})) +import { syncHistoryWithStore, routerReducer } from 'react-router-redux' -// Sync dispatched route actions to the history -const reduxRouterMiddleware = syncHistory(browserHistory) -const createStoreWithMiddleware = applyMiddleware(reduxRouterMiddleware)(createStore) +import reducers from '/reducers' -const store = createStoreWithMiddleware(reducer) +// Add the reducer to your store on the `routing` key +const store = createStore( + combineReducers({ + ...reducers, + routing: routerReducer + }) +) -// Required for replaying actions from devtools to work -reduxRouterMiddleware.listenForReplays(store) +// Create an enhanced history that syncs navigation events with the store +const history = syncHistoryWithStore(browserHistory, store) ReactDOM.render( - + { /* Tell the Router to use our enhanced history */ } + @@ -73,31 +75,32 @@ ReactDOM.render( ) ``` -Now you can read from `state.routing.location.pathname` to get the URL. It's far more likely that you want to change the URL more often, however. You can use the `push` action creator that we provide: +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 -import { routeActions } from 'react-router-redux' +const history = syncHistoryWithStore(browserHistory, store) -function MyComponent({ dispatch }) { - return +
{children}
) } - -export default connect( - null, - routeActions -)(App) diff --git a/examples/basic/index.html b/examples/basic/index.html index 9bc05ca..5452876 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -6,6 +6,6 @@
- + diff --git a/examples/basic/package.json b/examples/basic/package.json index bbebb67..f2ab7d7 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -1,33 +1,33 @@ { "name": "rrr-basic-example", "version": "0.0.0", - "repository": "rackt/react-router-redux", + "repository": "reactjs/react-router-redux", "license": "MIT", "dependencies": { - "history": "^1.14.0", - "react": "^0.14.2", - "react-dom": "^0.14.2", - "react-redux": "^4.0.0", - "react-router": "^1.0.0", - "redux": "^3.0.4", - "react-router-redux": "^2.1.0" + "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.1.21", - "babel-eslint": "^5.0.0-beta6", - "babel-loader": "^6.2.0", - "babel-preset-es2015": "^6.1.18", - "babel-preset-react": "^6.1.18", + "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.15.0", - "redux-devtools": "^3.0.0", + "eslint-plugin-react": "^3.16.1", + "redux-devtools": "^3.1.0", "redux-devtools-dock-monitor": "^1.0.1", - "redux-devtools-log-monitor": "^1.0.1", - "webpack": "^1.12.6" + "redux-devtools-log-monitor": "^1.0.4", + "webpack": "^1.12.13", + "webpack-dev-server": "^1.14.1" }, "scripts": { - "start": "webpack --watch" + "start": "webpack-dev-server --history-api-fallback --no-info --open" } } diff --git a/examples/server/.babelrc b/examples/server/.babelrc new file mode 100644 index 0000000..a1e122a --- /dev/null +++ b/examples/server/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["es2015", "react", "stage-1"], + "plugins": [ + ["babel-plugin-module-alias", [ + { "src": "../../src", "expose": "react-router-redux" } + ]] + ] +} diff --git a/examples/server/client.js b/examples/server/client.js new file mode 100644 index 0000000..a2940b2 --- /dev/null +++ b/examples/server/client.js @@ -0,0 +1,28 @@ +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( + + + , + document.getElementById('root') +) + +render( + + + , + document.getElementById('devtools') +) diff --git a/examples/server/index.html b/examples/server/index.html new file mode 100644 index 0000000..fa87359 --- /dev/null +++ b/examples/server/index.html @@ -0,0 +1,11 @@ + + + + react-router-redux server rendering example + + + +
+ + + diff --git a/examples/server/package.json b/examples/server/package.json new file mode 100644 index 0000000..5a2a5aa --- /dev/null +++ b/examples/server/package.json @@ -0,0 +1,38 @@ +{ + "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" + } +} diff --git a/examples/server/routes.js b/examples/server/routes.js new file mode 100644 index 0000000..5164adf --- /dev/null +++ b/examples/server/routes.js @@ -0,0 +1,31 @@ +import React from 'react' +import { Route, IndexRoute, Link } from 'react-router' + +const App = ({ children }) => ( +
+
+ Links: + {' '} + Home + {' '} + Foo + {' '} + Bar +
+ {children} +
+) + +const Home = () => (
Home!
) +const Foo = () => (
Foo!
) +const Bar = () => (
Bar!
) + +const routes = ( + + + + + +) + +export default routes diff --git a/examples/server/server.js b/examples/server/server.js new file mode 100644 index 0000000..7c80e2d --- /dev/null +++ b/examples/server/server.js @@ -0,0 +1,62 @@ +/*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 }) => ( + + +
+
+