Skip to content

Commit

Permalink
Redux Integration (#4668)
Browse files Browse the repository at this point in the history
* Reduxtion! 💥

* Lerna needs to be in independent mode now

react-router-redux is already 4.0, so it will have to be 5.0.0 for now.
Hopefully, we can sync it up soon and Lerna doesn't become too much of a
pain to use now.

* Whoops, that shouldn't have been commented out...

* Add reducer tests

* Add back the history convenience actions

* Add a read me for reading

* Don't need dev-expression since we're not using dev expressions

* Add the nav dispatch middleware to the example
  • Loading branch information
timdorr authored Mar 10, 2017
1 parent 8aa21a2 commit 85fec3c
Show file tree
Hide file tree
Showing 19 changed files with 4,318 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"lerna": "2.0.0-beta.32",
"version": "4.0.0-beta.8",
"version": "independent",
"packages": [
"packages/*"
]
Expand Down
14 changes: 14 additions & 0 deletions packages/react-router-redux/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"presets": [
"./tools/es2015-preset",
"stage-1",
"react"
],
"env": {
"production": {
"plugins": [
"transform-react-remove-prop-types"
]
}
}
}
22 changes: 22 additions & 0 deletions packages/react-router-redux/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"parser": "babel-eslint",
"env": {
"node": true,
"es6": true,
"jest": true
},
"plugins": [
"import",
"react"
],
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:react/recommended"
],
"rules": {
"prefer-arrow-callback": 2,
"react/display-name": 0,
"semi": [ 2, "never" ]
}
}
5 changes: 5 additions & 0 deletions packages/react-router-redux/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
es
node_modules
umd
/*.js
!rollup.config.js
64 changes: 64 additions & 0 deletions packages/react-router-redux/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# react-router-redux

[![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)

> **Keep your state in sync with your router** :sparkles:

## Installation

```
npm install --save react-router-redux
```

## Usage

Here's a basic idea of how it works:

```js
import React from 'react'
import ReactDOM from 'react-dom'

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'

import createHistory from 'history/createBrowserHistory'
import { Route } from 'react-router'

import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux'

import reducers from './reducers' // Or wherever you keep your reducers

// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory()

// Build the middleware for intercepting and dispatching navigation actions
const middleware = routerMiddleware(history)

// Add the reducer to your store on the `router` key
// Also apply our middleware for navigating
const store = createStore(
combineReducers({
...reducers,
router: routerReducer
}),
applyMiddleware(middleware)
)

// Now you can dispatch navigation actions from anywhere!
// store.dispatch(push('/foo'))

ReactDOM.render(
<Provider store={store}>
{ /* ConnectedRouter will use the store from Provider automatically */ }
<ConnectedRouter history={history}>
<div>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/topics" component={Topics}/>
</div>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
```
36 changes: 36 additions & 0 deletions packages/react-router-redux/modules/ConnectedRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { Component, PropTypes } from 'react'
import { Router, Route } from 'react-router'

import { LOCATION_CHANGE } from './reducer'

class ConnectedRouter extends Component {
static propTypes = {
store: PropTypes.object,
history: PropTypes.object,
children: PropTypes.node
}

static contextTypes = {
store: PropTypes.object
}

render() {
const { store:propsStore, history, children, ...props } = this.props
let store = propsStore || this.context.store

return (
<Router {...props} history={history}>
<Route render={({ location }) => {
store.dispatch({
type: LOCATION_CHANGE,
payload: location
})

return children
}}/>
</Router>
)
}
}

export default ConnectedRouter
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react'
import renderer from 'react-test-renderer'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import createHistory from 'history/createMemoryHistory'

import ConnectedRouter from '../ConnectedRouter'
import { routerReducer } from '../reducer'

describe('A <ConnectedRouter>', () => {
let store, history

beforeEach(() => {
store = createStore(combineReducers({
router: routerReducer
}))

history = createHistory()
})

it('connects to a store via Provider', () => {
expect(store.getState()).toHaveProperty('router.location', null)

renderer.create(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>Test</div>
</ConnectedRouter>
</Provider>
)

expect(store.getState()).toHaveProperty('router.location.pathname')
})

it('connects to a store via props', () => {
expect(store.getState()).toHaveProperty('router.location', null)

renderer.create(
<ConnectedRouter store={store} history={history}>
<div>Test</div>
</ConnectedRouter>
)

expect(store.getState()).toHaveProperty('router.location.pathname')
})

it('updates the store with location changes', () => {
renderer.create(
<ConnectedRouter store={store} history={history}>
<div>Test</div>
</ConnectedRouter>
)

expect(store.getState()).toHaveProperty('router.location.pathname', '/')

history.push('/foo')

expect(store.getState()).toHaveProperty('router.location.pathname', '/foo')
})
})
98 changes: 98 additions & 0 deletions packages/react-router-redux/modules/__tests__/actions-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
CALL_HISTORY_METHOD,
push, replace, go, goBack, goForward
} from '../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: []
}
})
})
})

})
32 changes: 32 additions & 0 deletions packages/react-router-redux/modules/__tests__/middleware-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { push, replace } from '../actions'
import routerMiddleware from '../middleware'

describe('routerMiddleware', () => {
let history, next, dispatch

beforeEach(() => {
history = {
push: jest.fn(),
replace: jest.fn()
}
next = jest.fn()

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).toHaveBeenCalledTimes(0)
})

it('ignores other actions', () => {
dispatch({ type: 'FOO' })
expect(next).toHaveBeenCalled()
})
})
Loading

0 comments on commit 85fec3c

Please sign in to comment.