356 lines
9.8 KiB
Markdown
356 lines
9.8 KiB
Markdown
|
# Frequently Asked Questions
|
||
|
----------------------------
|
||
|
- [How to navigate with Redux action](#how-to-navigate-with-redux-action)
|
||
|
- [How to get the current browser location (URL)](#how-to-get-the-current-browser-location-url)
|
||
|
- [How to set Router props e.g. basename, initialEntries, etc.](#how-to-set-router-props-eg-basename-initialentries-etc)
|
||
|
- [How to hot reload functional components](#how-to-hot-reload-functional-components)
|
||
|
- [How to hot reload reducers](#how-to-hot-reload-reducers)
|
||
|
- [How to support Immutable.js](#how-to-support-immutablejs)
|
||
|
- [How to migrate from v4 to v5/v6](#how-to-migrate-from-v4-to-v5v6)
|
||
|
- [How to use connected-react-router with react native](#how-to-use-connected-react-router-with-react-native)
|
||
|
- [How to use your own context with react-redux](#how-to-use-your-own-context-with-react-redux)
|
||
|
|
||
|
### How to navigate with Redux action
|
||
|
#### with store.dispatch
|
||
|
```js
|
||
|
import { push } from 'connected-react-router'
|
||
|
|
||
|
store.dispatch(push('/path/to/somewhere'))
|
||
|
```
|
||
|
|
||
|
#### with react-redux
|
||
|
```js
|
||
|
import { push } from 'connected-react-router'
|
||
|
|
||
|
// in component render:
|
||
|
<div onClick={() => {
|
||
|
|
||
|
/** do something before redirection */
|
||
|
props.push('/home');
|
||
|
|
||
|
}}>login</div>
|
||
|
|
||
|
// connect the action:
|
||
|
export default connect(null, { push })(Component);
|
||
|
```
|
||
|
|
||
|
#### in redux thunk
|
||
|
```js
|
||
|
import { push } from 'connected-react-router'
|
||
|
|
||
|
export const login = (username, password) => (dispatch) => {
|
||
|
|
||
|
/* do something before redirection */
|
||
|
|
||
|
dispatch(push('/home'))
|
||
|
}
|
||
|
|
||
|
```
|
||
|
#### in redux saga
|
||
|
```js
|
||
|
import { push } from 'connected-react-router'
|
||
|
import { put, call } from 'redux-saga/effects'
|
||
|
|
||
|
export function* login(username, password) {
|
||
|
|
||
|
/* do something before redirection */
|
||
|
|
||
|
yield put(push('/home'))
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### How to get the current browser location (URL)
|
||
|
The current browser location can be accessed directly from the router state with `react-redux`'s `connect`.
|
||
|
The location object is comprised of pathname, search (query string), and hash.
|
||
|
```js
|
||
|
import { connect } from 'react-redux'
|
||
|
|
||
|
const Child = ({ pathname, search, hash }) => (
|
||
|
<div>
|
||
|
Child receives
|
||
|
<div>
|
||
|
pathname: {pathname}
|
||
|
</div>
|
||
|
<div>
|
||
|
search: {search}
|
||
|
</div>
|
||
|
<div>
|
||
|
hash: {hash}
|
||
|
</div>
|
||
|
</div>
|
||
|
)
|
||
|
|
||
|
const mapStateToProps = state => ({
|
||
|
pathname: state.router.location.pathname,
|
||
|
search: state.router.location.search,
|
||
|
hash: state.router.location.hash,
|
||
|
})
|
||
|
|
||
|
export default connect(mapStateToProps)(Child)
|
||
|
```
|
||
|
|
||
|
### How to set Router props (e.g. basename, initialEntries, etc.)
|
||
|
You can pass props to the `create*History` functions of your choice (`createBrowserHistory`, `createHashHistory`, `createMemoryHistory`)
|
||
|
|
||
|
```js
|
||
|
import { createBrowserHistory } from 'history'
|
||
|
|
||
|
const history = createBrowserHistory({
|
||
|
basename: '/prefix/',
|
||
|
})
|
||
|
```
|
||
|
|
||
|
```js
|
||
|
import { createHashHistory } from 'history'
|
||
|
|
||
|
const history = createHashHistory({
|
||
|
hashType: 'slash',
|
||
|
getUserConfirmation: (message, callback) => callback(window.confirm(message))
|
||
|
})
|
||
|
```
|
||
|
|
||
|
```js
|
||
|
import { createMemoryHistory } from 'history'
|
||
|
|
||
|
const history = createMemoryHistory({
|
||
|
initialEntries: [ '/one', '/two', { pathname: '/three' } ],
|
||
|
initialIndex: 1
|
||
|
})
|
||
|
```
|
||
|
|
||
|
### How to hot reload functional components
|
||
|
1) Save the main app component in its own file.
|
||
|
|
||
|
`App.js`
|
||
|
``` js
|
||
|
import React from 'react'
|
||
|
import { Route, Switch } from 'react-router' /* react-router v4/v5 */
|
||
|
import { ConnectedRouter } from 'connected-react-router'
|
||
|
|
||
|
const App = ({ history }) => ( /* receive history object via props */
|
||
|
<ConnectedRouter history={history}>
|
||
|
<div>
|
||
|
<Switch>
|
||
|
<Route exact path="/" render={() => (<div>Match</div>)} />
|
||
|
<Route render={() => (<div>Miss</div>)} />
|
||
|
</Switch>
|
||
|
</div>
|
||
|
</ConnectedRouter>
|
||
|
)
|
||
|
|
||
|
export default App
|
||
|
```
|
||
|
|
||
|
2) Wrap the `App` component with `AppContainer` from `react-hot-loader` v3 as a top-level container.
|
||
|
|
||
|
`index.js`
|
||
|
```js
|
||
|
import React from 'react'
|
||
|
import ReactDOM from 'react-dom'
|
||
|
import { Provider } from 'react-redux'
|
||
|
import { AppContainer } from 'react-hot-loader' /* react-hot-loader v3 */
|
||
|
import App from './App'
|
||
|
...
|
||
|
const render = () => { // this function will be reused
|
||
|
ReactDOM.render(
|
||
|
<AppContainer> { /* AppContainer for hot reloading v3 */ }
|
||
|
<Provider store={store}>
|
||
|
<App history={history} /> { /* pass history object as props */ }
|
||
|
</Provider>
|
||
|
</AppContainer>,
|
||
|
document.getElementById('react-root')
|
||
|
)
|
||
|
}
|
||
|
|
||
|
render()
|
||
|
```
|
||
|
|
||
|
3) Detect change and re-render with hot reload.
|
||
|
|
||
|
`index.js`
|
||
|
``` js
|
||
|
...
|
||
|
if (module.hot) {
|
||
|
module.hot.accept('./App', () => {
|
||
|
/* For Webpack 2.x
|
||
|
Need to disable babel ES2015 modules transformation in .babelrc
|
||
|
presets: [
|
||
|
["es2015", { "modules": false }]
|
||
|
]
|
||
|
*/
|
||
|
render()
|
||
|
|
||
|
/* For Webpack 1.x
|
||
|
const NextApp = require('./App').default
|
||
|
renderWithHotReload(NextApp)
|
||
|
*/
|
||
|
})
|
||
|
}
|
||
|
```
|
||
|
Now, when you change any component that `App` depends on, it will trigger hot reloading without losing redux state. Thanks [react-hot-loader v3](https://github.com/gaearon/react-hot-loader/tree/next)!
|
||
|
|
||
|
### How to hot reload reducers
|
||
|
Detect change and replace with a new root reducer with router state
|
||
|
|
||
|
`index.js`
|
||
|
``` js
|
||
|
...
|
||
|
if (module.hot) {
|
||
|
module.hot.accept('./reducers', () => {
|
||
|
/* For Webpack 2.x
|
||
|
Need to disable babel ES2015 modules transformation in .babelrc
|
||
|
presets: [
|
||
|
["es2015", { "modules": false }]
|
||
|
]
|
||
|
*/
|
||
|
store.replaceReducer(rootReducer(history))
|
||
|
|
||
|
/* For Webpack 1.x
|
||
|
const nextRootReducer = require('./reducers').default
|
||
|
store.replaceReducer(nextRootReducer(history))
|
||
|
*/
|
||
|
})
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### How to support Immutable.js
|
||
|
1) Create your root reducer as a function that takes `history` and returns reducer. Use `combineReducers` from `redux-immutable` to return the root reducer.
|
||
|
|
||
|
2) Import `connectRouter` from `connected-react-router/immutable` and add router reducer to root reducer
|
||
|
```js
|
||
|
import { combineReducers } from 'redux-immutable'
|
||
|
import { connectRouter } from 'connected-react-router/immutable'
|
||
|
...
|
||
|
const rootReducer = (history) => combineReducers({
|
||
|
router: connectRouter(history),
|
||
|
...
|
||
|
})
|
||
|
...
|
||
|
```
|
||
|
|
||
|
2) Import `ConnectedRouter` and `routerMiddleware` from `connected-react-router/immutable` instead of `connected-react-router`.
|
||
|
```js
|
||
|
import { ConnectedRouter, routerMiddleware } from 'connected-react-router/immutable'
|
||
|
```
|
||
|
|
||
|
3) Create your root reducer with router reducer by passing `history` to `rootReducer` function
|
||
|
```js
|
||
|
const store = createStore(
|
||
|
rootReducer(history),
|
||
|
initialState,
|
||
|
...
|
||
|
)
|
||
|
```
|
||
|
|
||
|
4) (Optional) Initialize state with `Immutable.Map()`
|
||
|
```js
|
||
|
import Immutable from 'immutable'
|
||
|
...
|
||
|
const initialState = Immutable.Map()
|
||
|
...
|
||
|
const store = createStore(
|
||
|
rootReducer(history),
|
||
|
initialState,
|
||
|
...
|
||
|
)
|
||
|
```
|
||
|
|
||
|
### How to migrate from v4 to v5/v6
|
||
|
It's easy to migrate from v4 to v5/v6.
|
||
|
1. In your root reducer file, instead of exporting a root reducer, you need to export a function accepting a `history` object and returning a root reducer with `router` key. The value of the `router` key is `connectedRouter(history)`.
|
||
|
|
||
|
```diff
|
||
|
// reducers.js
|
||
|
|
||
|
import { combineReducers } from 'redux'
|
||
|
+ import { connectRouter } from 'connected-react-router'
|
||
|
|
||
|
- export default combineReducers({
|
||
|
+ export default (history) => combineReducers({
|
||
|
+ router: connectRouter(history),
|
||
|
...
|
||
|
})
|
||
|
```
|
||
|
|
||
|
2. In `createStore` function, change to use the new function creating a root reducer.
|
||
|
```diff
|
||
|
// configureStore.js
|
||
|
...
|
||
|
import { createBrowserHistory } from 'history'
|
||
|
import { applyMiddleware, compose, createStore } from 'redux'
|
||
|
- import { connectRouter, routerMiddleware } from 'connected-react-router'
|
||
|
+ import { routerMiddleware } from 'connected-react-router'
|
||
|
- import rootReducer from './reducers'
|
||
|
+ import createRootReducer from './reducers'
|
||
|
|
||
|
const history = createBrowserHistory()
|
||
|
|
||
|
const store = createStore(
|
||
|
- connectRouter(history)(rootReducer),
|
||
|
+ createRootReducer(history),
|
||
|
initialState,
|
||
|
compose(
|
||
|
applyMiddleware(
|
||
|
routerMiddleware(history),
|
||
|
),
|
||
|
),
|
||
|
)
|
||
|
```
|
||
|
|
||
|
3. For reducers hot reloading, similarly, change to use the new function creating a root reducer.
|
||
|
```diff
|
||
|
// For Webpack 2.x
|
||
|
- store.replaceReducer(connectRouter(history)(rootReducer))
|
||
|
+ store.replaceReducer(createRootReducer(history))
|
||
|
|
||
|
// For Webpack 1.x
|
||
|
- const nextRootReducer = require('./reducers').default
|
||
|
- store.replaceReducer(connectRouter(history)(nextRootReducer))
|
||
|
+ const nextCreateRootReducer = require('./reducers').default
|
||
|
+ store.replaceReducer(nextCreateRootReducer(history))
|
||
|
```
|
||
|
|
||
|
### How to use connected-react-router with react native
|
||
|
#### History does not exist, how can I configure my redux store?
|
||
|
As you know react native does not support natively the HTML5 history API, it's supposed to be available only for web browsers. This issue can be solved by using [`createMemoryHistory`](https://github.com/ReactTraining/history/blob/master/docs/GettingStarted.md#intro).
|
||
|
|
||
|
Here is an example with react-redux v6.0.0.
|
||
|
|
||
|
```js
|
||
|
const history = createMemoryHistory()
|
||
|
|
||
|
ReactDOM.render(
|
||
|
<Provider store={store}>
|
||
|
<ConnectedRouter history={history}>
|
||
|
<Route path="/" component={myComponent} exact={true} />
|
||
|
</ConnectedRouter>
|
||
|
</Provider>
|
||
|
)
|
||
|
```
|
||
|
|
||
|
[Example available here](./examples/react-native/src/configureStore.js)
|
||
|
|
||
|
#### Get location from a screen
|
||
|
You can access at your location interface with `history.location`.
|
||
|
|
||
|
[Example available here](./examples/react-native/src/screens/Account.js)
|
||
|
|
||
|
#### Go to a screen with parameter
|
||
|
You can use `history` and navigate between screens.
|
||
|
|
||
|
[Example available here](./examples/react-native/src/screens/Home.js)
|
||
|
|
||
|
### How to Use Your Own Context with react-redux
|
||
|
With react-redux v6.0.0, you can pass your own context to `<Provider>` component. So, you need to pass the same context as props to `<ConnectedRouter>` component.
|
||
|
```js
|
||
|
const customContext = React.createContext(null) // your own context
|
||
|
|
||
|
ReactDOM.render(
|
||
|
<Provider store={store} context={customContext}>
|
||
|
<ConnectedRouter history={history} context={customContext}>
|
||
|
...
|
||
|
</ConnectedRouter>
|
||
|
</Provider>
|
||
|
)
|
||
|
```
|