├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .jestrc.json ├── .travis.yml ├── ACKNOWLEDGE.md ├── CODE_OF_CONDUCT.md ├── FAQ.md ├── LICENSE.md ├── README.md ├── examples ├── basic │ ├── .babelrc │ ├── .eslintrc │ ├── README.md │ ├── index.html │ ├── package.json │ ├── server.js │ ├── src │ │ ├── App.js │ │ ├── actions │ │ │ └── counter.js │ │ ├── components │ │ │ ├── Counter.js │ │ │ ├── Hello.js │ │ │ ├── HelloChild.js │ │ │ ├── Home.js │ │ │ ├── NavBar.js │ │ │ └── NoMatch.js │ │ ├── configureStore.js │ │ ├── index.js │ │ ├── reducers │ │ │ ├── counter.js │ │ │ └── index.js │ │ └── routes │ │ │ └── index.js │ ├── webpack.config.js │ └── yarn.lock ├── immutable │ ├── .babelrc │ ├── .eslintrc │ ├── README.md │ ├── index.html │ ├── package.json │ ├── server.js │ ├── src │ │ ├── App.js │ │ ├── actions │ │ │ └── counter.js │ │ ├── components │ │ │ ├── Counter.js │ │ │ ├── Hello.js │ │ │ ├── HelloChild.js │ │ │ ├── Home.js │ │ │ ├── NavBar.js │ │ │ └── NoMatch.js │ │ ├── configureStore.js │ │ ├── index.js │ │ ├── reducers │ │ │ ├── counter.js │ │ │ └── index.js │ │ └── routes │ │ │ └── index.js │ ├── webpack.config.js │ └── yarn.lock ├── react-native │ ├── .buckconfig │ ├── .eslintrc.js │ ├── .flowconfig │ ├── .gitattributes │ ├── .gitignore │ ├── .prettierrc.js │ ├── .watchmanconfig │ ├── android │ │ ├── app │ │ │ ├── BUCK │ │ │ ├── build.gradle │ │ │ ├── build_defs.bzl │ │ │ ├── proguard-rules.pro │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── connectedreactrouter │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MainApplication.java │ │ │ │ └── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ └── settings.gradle │ ├── app.json │ ├── babel.config.js │ ├── index.js │ ├── ios │ │ ├── ConnectedReactRouter-tvOS │ │ │ └── Info.plist │ │ ├── ConnectedReactRouter-tvOSTests │ │ │ └── Info.plist │ │ ├── ConnectedReactRouter.xcodeproj │ │ │ ├── project.pbxproj │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ ├── ConnectedReactRouter-tvOS.xcscheme │ │ │ │ └── ConnectedReactRouter.xcscheme │ │ ├── ConnectedReactRouter.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── ConnectedReactRouter │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Base.lproj │ │ │ │ └── LaunchScreen.xib │ │ │ ├── Images.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Info.plist │ │ │ └── main.m │ │ ├── ConnectedReactRouterTests │ │ │ ├── ConnectedReactRouterTests.m │ │ │ └── Info.plist │ │ ├── Podfile │ │ └── Podfile.lock │ ├── metro.config.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── App.js │ │ ├── configureStore.js │ │ ├── routes │ │ │ └── index.js │ │ ├── screens │ │ │ ├── Account.js │ │ │ ├── Home.js │ │ │ └── __tests__ │ │ │ │ ├── Account-test.js │ │ │ │ ├── Home-test.js │ │ │ │ └── __snapshots__ │ │ │ │ ├── Account-test.js.snap │ │ │ │ └── Home-test.js.snap │ │ └── utils │ │ │ ├── __tests__ │ │ │ └── url-test.js │ │ │ └── url.js │ └── yarn.lock └── typescript │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ ├── App.tsx │ ├── actions │ │ └── counter.ts │ ├── components │ │ ├── Counter.tsx │ │ ├── Hello.tsx │ │ ├── HelloChild.tsx │ │ ├── Home.tsx │ │ ├── NavBar.tsx │ │ └── NoMatch.tsx │ ├── configureStore.tsx │ ├── index.tsx │ ├── reducers │ │ ├── counter.ts │ │ └── index.ts │ └── routes │ │ └── index.tsx │ ├── tsconfig.json │ ├── webpack.config.js │ └── yarn.lock ├── immutable.d.ts ├── immutable.js ├── index.d.ts ├── package.json ├── seamless-immutable.d.ts ├── seamless-immutable.js ├── src ├── ConnectedRouter.js ├── actions.js ├── immutable.js ├── index.js ├── middleware.js ├── reducer.js ├── seamless-immutable.js ├── selectors.js └── structure │ ├── immutable │ ├── getIn.js │ └── index.js │ ├── plain │ ├── getIn.js │ └── index.js │ └── seamless-immutable │ └── index.js ├── test ├── .eslintrc ├── ConnectedRouter.test.js ├── actions.test.js ├── middleware.test.js ├── reducer.test.js └── selectors.test.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "env": { 7 | "esm": { 8 | "presets": [ 9 | ["@babel/preset-env", { "modules": false }] 10 | ] 11 | }, 12 | "test": { 13 | "plugins": [ 14 | "rewire" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | examples/* 2 | esm/* 3 | lib/* 4 | umd/* 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "plugins": [ 8 | "import", 9 | "react" 10 | ], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:import/errors", 14 | "plugin:react/recommended" 15 | ], 16 | "rules": { 17 | "semi": [ 18 | 2, 19 | "never" 20 | ] 21 | }, 22 | "settings": { 23 | "react": { 24 | "version": "16.0" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '23 1 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | *.swp 4 | node_modules 5 | esm 6 | lib 7 | umd 8 | .idea 9 | dist 10 | -------------------------------------------------------------------------------- /.jestrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "testRegex": "(/test/.*\\.test.js)$", 3 | "verbose": true 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "node" 5 | - "11" 6 | - "10" 7 | - "8" 8 | 9 | cache: 10 | yarn: true 11 | directories: 12 | - "node_modules" 13 | -------------------------------------------------------------------------------- /ACKNOWLEDGE.md: -------------------------------------------------------------------------------- 1 | Acknowledgements 2 | ---------------- 3 | Connected React Router is based on several awesome peoples' ideas 4 | - Several parts of Connected React Router are based on [react-router-redux v4](https://github.com/reactjs/react-router-redux/tree/v4.0.7). 5 | - A [PR](https://github.com/reactjs/react-router-redux/pull/460) by @timdorr to make react-router-redux v5 integrate with React Router v4 contains several ideas to drive Connected React Router. 6 | - The ConnectedRouter component is inspired by [App component by @cherijs](https://github.com/lourd/react-router4-redux-example/blob/master/src/App.js) with history synchronization enhancement. 7 | - The idea of uni-directional flow (history -> store -> router -> components) is from [a @lourd's comment](https://github.com/reactjs/react-router-redux/pull/460#issuecomment-260999726) 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at supasate.c@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | ---------------------------- 3 | - [How to navigate with Redux action](#how-to-navigate-with-redux-action) 4 | - [How to get the current browser location (URL)](#how-to-get-the-current-browser-location-url) 5 | - [How to set Router props e.g. basename, initialEntries, etc.](#how-to-set-router-props-eg-basename-initialentries-etc) 6 | - [How to hot reload functional components](#how-to-hot-reload-functional-components) 7 | - [How to hot reload reducers](#how-to-hot-reload-reducers) 8 | - [How to support Immutable.js](#how-to-support-immutablejs) 9 | - [How to migrate from v4 to v5/v6](#how-to-migrate-from-v4-to-v5v6) 10 | - [How to use connected-react-router with react native](#how-to-use-connected-react-router-with-react-native) 11 | - [How to use your own context with react-redux](#how-to-use-your-own-context-with-react-redux) 12 | 13 | ### How to navigate with Redux action 14 | #### with store.dispatch 15 | ```js 16 | import { push } from 'connected-react-router' 17 | 18 | store.dispatch(push('/path/to/somewhere')) 19 | ``` 20 | 21 | #### with react-redux 22 | ```js 23 | import { push } from 'connected-react-router' 24 | 25 | // in component render: 26 |
{ 27 | 28 | /** do something before redirection */ 29 | props.push('/home'); 30 | 31 | }}>login
32 | 33 | // connect the action: 34 | export default connect(null, { push })(Component); 35 | ``` 36 | 37 | #### in redux thunk 38 | ```js 39 | import { push } from 'connected-react-router' 40 | 41 | export const login = (username, password) => (dispatch) => { 42 | 43 | /* do something before redirection */ 44 | 45 | dispatch(push('/home')) 46 | } 47 | 48 | ``` 49 | #### in redux saga 50 | ```js 51 | import { push } from 'connected-react-router' 52 | import { put, call } from 'redux-saga/effects' 53 | 54 | export function* login(username, password) { 55 | 56 | /* do something before redirection */ 57 | 58 | yield put(push('/home')) 59 | } 60 | ``` 61 | 62 | ### How to get the current browser location (URL) 63 | The current browser location can be accessed directly from the router state with `react-redux`'s `connect`. 64 | The location object is comprised of pathname, search (query string), and hash. 65 | ```js 66 | import { connect } from 'react-redux' 67 | 68 | const Child = ({ pathname, search, hash }) => ( 69 |
70 | Child receives 71 |
72 | pathname: {pathname} 73 |
74 |
75 | search: {search} 76 |
77 |
78 | hash: {hash} 79 |
80 |
81 | ) 82 | 83 | const mapStateToProps = state => ({ 84 | pathname: state.router.location.pathname, 85 | search: state.router.location.search, 86 | hash: state.router.location.hash, 87 | }) 88 | 89 | export default connect(mapStateToProps)(Child) 90 | ``` 91 | 92 | ### How to set Router props (e.g. basename, initialEntries, etc.) 93 | You can pass props to the `create*History` functions of your choice (`createBrowserHistory`, `createHashHistory`, `createMemoryHistory`) 94 | 95 | ```js 96 | import { createBrowserHistory } from 'history' 97 | 98 | const history = createBrowserHistory({ 99 | basename: '/prefix/', 100 | }) 101 | ``` 102 | 103 | ```js 104 | import { createHashHistory } from 'history' 105 | 106 | const history = createHashHistory({ 107 | hashType: 'slash', 108 | getUserConfirmation: (message, callback) => callback(window.confirm(message)) 109 | }) 110 | ``` 111 | 112 | ```js 113 | import { createMemoryHistory } from 'history' 114 | 115 | const history = createMemoryHistory({ 116 | initialEntries: [ '/one', '/two', { pathname: '/three' } ], 117 | initialIndex: 1 118 | }) 119 | ``` 120 | 121 | ### How to hot reload functional components 122 | 1) Save the main app component in its own file. 123 | 124 | `App.js` 125 | ``` js 126 | import React from 'react' 127 | import { Route, Switch } from 'react-router' /* react-router v4/v5 */ 128 | import { ConnectedRouter } from 'connected-react-router' 129 | 130 | const App = ({ history }) => ( /* receive history object via props */ 131 | 132 |
133 | 134 | (
Match
)} /> 135 | (
Miss
)} /> 136 |
137 |
138 |
139 | ) 140 | 141 | export default App 142 | ``` 143 | 144 | 2) Wrap the `App` component with `AppContainer` from `react-hot-loader` v3 as a top-level container. 145 | 146 | `index.js` 147 | ```js 148 | import React from 'react' 149 | import ReactDOM from 'react-dom' 150 | import { Provider } from 'react-redux' 151 | import { AppContainer } from 'react-hot-loader' /* react-hot-loader v3 */ 152 | import App from './App' 153 | ... 154 | const render = () => { // this function will be reused 155 | ReactDOM.render( 156 | { /* AppContainer for hot reloading v3 */ } 157 | 158 | { /* pass history object as props */ } 159 | 160 | , 161 | document.getElementById('react-root') 162 | ) 163 | } 164 | 165 | render() 166 | ``` 167 | 168 | 3) Detect change and re-render with hot reload. 169 | 170 | `index.js` 171 | ``` js 172 | ... 173 | if (module.hot) { 174 | module.hot.accept('./App', () => { 175 | /* For Webpack 2.x 176 | Need to disable babel ES2015 modules transformation in .babelrc 177 | presets: [ 178 | ["es2015", { "modules": false }] 179 | ] 180 | */ 181 | render() 182 | 183 | /* For Webpack 1.x 184 | const NextApp = require('./App').default 185 | renderWithHotReload(NextApp) 186 | */ 187 | }) 188 | } 189 | ``` 190 | 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)! 191 | 192 | ### How to hot reload reducers 193 | Detect change and replace with a new root reducer with router state 194 | 195 | `index.js` 196 | ``` js 197 | ... 198 | if (module.hot) { 199 | module.hot.accept('./reducers', () => { 200 | /* For Webpack 2.x 201 | Need to disable babel ES2015 modules transformation in .babelrc 202 | presets: [ 203 | ["es2015", { "modules": false }] 204 | ] 205 | */ 206 | store.replaceReducer(rootReducer(history)) 207 | 208 | /* For Webpack 1.x 209 | const nextRootReducer = require('./reducers').default 210 | store.replaceReducer(nextRootReducer(history)) 211 | */ 212 | }) 213 | } 214 | ``` 215 | 216 | ### How to support Immutable.js 217 | 1) Create your root reducer as a function that takes `history` and returns reducer. Use `combineReducers` from `redux-immutable` to return the root reducer. 218 | 219 | 2) Import `connectRouter` from `connected-react-router/immutable` and add router reducer to root reducer 220 | ```js 221 | import { combineReducers } from 'redux-immutable' 222 | import { connectRouter } from 'connected-react-router/immutable' 223 | ... 224 | const rootReducer = (history) => combineReducers({ 225 | router: connectRouter(history), 226 | ... 227 | }) 228 | ... 229 | ``` 230 | 231 | 2) Import `ConnectedRouter` and `routerMiddleware` from `connected-react-router/immutable` instead of `connected-react-router`. 232 | ```js 233 | import { ConnectedRouter, routerMiddleware } from 'connected-react-router/immutable' 234 | ``` 235 | 236 | 3) Create your root reducer with router reducer by passing `history` to `rootReducer` function 237 | ```js 238 | const store = createStore( 239 | rootReducer(history), 240 | initialState, 241 | ... 242 | ) 243 | ``` 244 | 245 | 4) (Optional) Initialize state with `Immutable.Map()` 246 | ```js 247 | import Immutable from 'immutable' 248 | ... 249 | const initialState = Immutable.Map() 250 | ... 251 | const store = createStore( 252 | rootReducer(history), 253 | initialState, 254 | ... 255 | ) 256 | ``` 257 | 258 | ### How to migrate from v4 to v5/v6 259 | It's easy to migrate from v4 to v5/v6. 260 | 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)`. 261 | 262 | ```diff 263 | // reducers.js 264 | 265 | import { combineReducers } from 'redux' 266 | + import { connectRouter } from 'connected-react-router' 267 | 268 | - export default combineReducers({ 269 | + export default (history) => combineReducers({ 270 | + router: connectRouter(history), 271 | ... 272 | }) 273 | ``` 274 | 275 | 2. In `createStore` function, change to use the new function creating a root reducer. 276 | ```diff 277 | // configureStore.js 278 | ... 279 | import { createBrowserHistory } from 'history' 280 | import { applyMiddleware, compose, createStore } from 'redux' 281 | - import { connectRouter, routerMiddleware } from 'connected-react-router' 282 | + import { routerMiddleware } from 'connected-react-router' 283 | - import rootReducer from './reducers' 284 | + import createRootReducer from './reducers' 285 | 286 | const history = createBrowserHistory() 287 | 288 | const store = createStore( 289 | - connectRouter(history)(rootReducer), 290 | + createRootReducer(history), 291 | initialState, 292 | compose( 293 | applyMiddleware( 294 | routerMiddleware(history), 295 | ), 296 | ), 297 | ) 298 | ``` 299 | 300 | 3. For reducers hot reloading, similarly, change to use the new function creating a root reducer. 301 | ```diff 302 | // For Webpack 2.x 303 | - store.replaceReducer(connectRouter(history)(rootReducer)) 304 | + store.replaceReducer(createRootReducer(history)) 305 | 306 | // For Webpack 1.x 307 | - const nextRootReducer = require('./reducers').default 308 | - store.replaceReducer(connectRouter(history)(nextRootReducer)) 309 | + const nextCreateRootReducer = require('./reducers').default 310 | + store.replaceReducer(nextCreateRootReducer(history)) 311 | ``` 312 | 313 | ### How to use connected-react-router with react native 314 | #### History does not exist, how can I configure my redux store? 315 | 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). 316 | 317 | Here is an example with react-redux v6.0.0. 318 | 319 | ```js 320 | const history = createMemoryHistory() 321 | 322 | ReactDOM.render( 323 | 324 | 325 | 326 | 327 | 328 | ) 329 | ``` 330 | 331 | [Example available here](./examples/react-native/src/configureStore.js) 332 | 333 | #### Get location from a screen 334 | You can access at your location interface with `history.location`. 335 | 336 | [Example available here](./examples/react-native/src/screens/Account.js) 337 | 338 | #### Go to a screen with parameter 339 | You can use `history` and navigate between screens. 340 | 341 | [Example available here](./examples/react-native/src/screens/Home.js) 342 | 343 | ### How to Use Your Own Context with react-redux 344 | With react-redux v6.0.0, you can pass your own context to `` component. So, you need to pass the same context as props to `` component. 345 | ```js 346 | const customContext = React.createContext(null) // your own context 347 | 348 | ReactDOM.render( 349 | 350 | 351 | ... 352 | 353 | 354 | ) 355 | ``` 356 | 357 | ### How to stop initial location change 358 | In order to make this package more compatible with react-router-redux, a LOCATION_CHANGE action is dispatched for the initial location. This can however be disabled via the `noInitialPop` prop. 359 | ```js 360 | ReactDOM.render( 361 | 362 | 363 | 364 | 365 | 366 | ) 367 | ``` 368 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Supasate Choochaisri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Breaking change in v5.0.0! Please read [How to migrate from v4 to v5/v6](https://github.com/supasate/connected-react-router/blob/master/FAQ.md#how-to-migrate-from-v4-to-v5v6). 2 | 3 | > v6.0.0 requires React v16.4.0 and React Redux v6.0 / v7.0. 4 | 5 | Connected React Router [![Build Status](https://travis-ci.org/supasate/connected-react-router.svg?branch=master)](https://travis-ci.org/supasate/connected-react-router) [![Open Source Helpers](https://www.codetriage.com/supasate/connected-react-router/badges/users.svg)](https://www.codetriage.com/supasate/connected-react-router) 6 | ====================== 7 | A Redux binding for React Router v4 and v5 8 | 9 | Main features 10 | ------------- 11 | :sparkles: Synchronize router state with redux store through uni-directional flow (i.e. history -> store -> router -> components). 12 | 13 | :gift: Supports [React Router v4 and v5](https://github.com/ReactTraining/react-router). 14 | 15 | :sunny: Supports functional component hot reloading while preserving state (with [react-hot-reload](https://github.com/gaearon/react-hot-loader)). 16 | 17 | :tada: Dispatching of history methods (`push`, `replace`, `go`, `goBack`, `goForward`) works for both [redux-thunk](https://github.com/gaearon/redux-thunk) and [redux-saga](https://github.com/yelouafi/redux-saga). 18 | 19 | :snowman: Nested children can access routing state such as the current location directly with `react-redux`'s `connect`. 20 | 21 | :clock9: Supports time traveling in Redux DevTools. 22 | 23 | :gem: Supports [Immutable.js](https://facebook.github.io/immutable-js/) 24 | 25 | :muscle: Supports TypeScript 26 | 27 | 28 | Installation 29 | ----------- 30 | Connected React Router requires **React 16.4 and React Redux 6.0 or later**. 31 | 32 | 33 | npm install --save connected-react-router 34 | 35 | Or 36 | 37 | yarn add connected-react-router 38 | 39 | Usage 40 | ----- 41 | ### Step 1 42 | In your root reducer file, 43 | - Create a function that takes `history` as an argument and returns a root reducer. 44 | - Add `router` reducer into root reducer by passing `history` to `connectRouter`. 45 | - **Note: The key MUST be `router`**. 46 | 47 | ```js 48 | // reducers.js 49 | import { combineReducers } from 'redux' 50 | import { connectRouter } from 'connected-react-router' 51 | 52 | const createRootReducer = (history) => combineReducers({ 53 | router: connectRouter(history), 54 | ... // rest of your reducers 55 | }) 56 | export default createRootReducer 57 | ``` 58 | 59 | ### Step 2 60 | When creating a Redux store, 61 | - Create a `history` object. 62 | - Provide the created `history` to the root reducer creator. 63 | - Use `routerMiddleware(history)` if you want to dispatch history actions (e.g. to change URL with `push('/path/to/somewhere')`). 64 | 65 | 66 | ```js 67 | // configureStore.js 68 | ... 69 | import { createBrowserHistory } from 'history' 70 | import { applyMiddleware, compose, createStore } from 'redux' 71 | import { routerMiddleware } from 'connected-react-router' 72 | import createRootReducer from './reducers' 73 | ... 74 | export const history = createBrowserHistory() 75 | 76 | export default function configureStore(preloadedState) { 77 | const store = createStore( 78 | createRootReducer(history), // root reducer with router state 79 | preloadedState, 80 | compose( 81 | applyMiddleware( 82 | routerMiddleware(history), // for dispatching history actions 83 | // ... other middlewares ... 84 | ), 85 | ), 86 | ) 87 | 88 | return store 89 | } 90 | ``` 91 | 92 | ### Step 3 93 | 94 | - Wrap your react-router v4/v5 routing with `ConnectedRouter` and pass the `history` object as a prop. Remember to delete any usage of `BrowserRouter` or `NativeRouter` as leaving this in will [cause](https://github.com/supasate/connected-react-router/issues/230#issuecomment-461628073) [problems](https://github.com/supasate/connected-react-router/issues/230#issuecomment-476164384) synchronising the state. 95 | - Place `ConnectedRouter` as a child of `react-redux`'s `Provider`. 96 | - **N.B.** If doing server-side rendering, you should still use the `StaticRouter` from `react-router` on the server. 97 | 98 | ```js 99 | // index.js 100 | ... 101 | import { Provider } from 'react-redux' 102 | import { Route, Switch } from 'react-router' // react-router v4/v5 103 | import { ConnectedRouter } from 'connected-react-router' 104 | import configureStore, { history } from './configureStore' 105 | ... 106 | const store = configureStore(/* provide initial state if any */) 107 | 108 | ReactDOM.render( 109 | 110 | { /* place ConnectedRouter under Provider */ } 111 | <> { /* your usual react-router v4/v5 routing */ } 112 | 113 | (
Match
)} /> 114 | (
Miss
)} /> 115 |
116 | 117 |
118 |
, 119 | document.getElementById('react-root') 120 | ) 121 | ``` 122 | Note: the `history` object provided to `router` reducer, `routerMiddleware`, and `ConnectedRouter` component must be the same `history` object. 123 | 124 | Now, it's ready to work! 125 | 126 | 127 | Examples 128 | -------- 129 | See the [examples](https://github.com/supasate/connected-react-router/tree/master/examples) folder 130 | 131 | [FAQ](https://github.com/supasate/connected-react-router/tree/master/FAQ.md) 132 | ----- 133 | - [How to navigate with Redux action](https://github.com/supasate/connected-react-router/tree/master/FAQ.md#how-to-navigate-with-redux-action) 134 | - [How to get the current browser location (URL)](https://github.com/supasate/connected-react-router/tree/master/FAQ.md#how-to-get-the-current-browser-location-url) 135 | - [How to set Router props e.g. basename, initialEntries, etc.](https://github.com/supasate/connected-react-router/tree/master/FAQ.md#how-to-set-router-props-eg-basename-initialentries-etc) 136 | - [How to hot reload functional components](https://github.com/supasate/connected-react-router/tree/master/FAQ.md#how-to-hot-reload-functional-components) 137 | - [How to hot reload reducers](https://github.com/supasate/connected-react-router/tree/master/FAQ.md#how-to-hot-reload-reducers) 138 | - [How to support Immutable.js](https://github.com/supasate/connected-react-router/tree/master/FAQ.md#how-to-support-immutablejs) 139 | - [How to implement server-side rendering](https://medium.com/@cereallarceny/server-side-rendering-in-create-react-app-with-all-the-goodies-without-ejecting-4c889d7db25e) ([sample codebase](https://github.com/cereallarceny/cra-ssr)) 140 | - [How to migrate from v4 to v5](https://github.com/supasate/connected-react-router/tree/master/FAQ.md#how-to-migrate-from-v4-to-v5) 141 | - [How to use connected-react-router with react native](./FAQ.md#how-to-use-connected-react-router-with-react-native) 142 | - [How to use your own context with react-redux](https://github.com/supasate/connected-react-router/tree/master/FAQ.md#how-to-use-your-own-context-with-react-redux) 143 | 144 | Build 145 | ----- 146 | ```bash 147 | npm run build 148 | ``` 149 | Generated files will be in the `lib` folder. 150 | 151 | Development 152 | ----------- 153 | When testing the example apps with `npm link` or `yarn link`, you should explicitly provide the same `Context` to both `Provider` and `ConnectedRouter` to make sure that the `ConnectedRouter` doesn't pick up a different `ReactReduxContext` from a different `node_modules` folder. 154 | 155 | In `index.js`. 156 | ```js 157 | ... 158 | import { Provider, ReactReduxContext } from 'react-redux' 159 | ... 160 | 161 | 162 | 163 | ... 164 | ``` 165 | 166 | In `App.js`, 167 | ```js 168 | ... 169 | const App = ({ history, context }) => { 170 | return ( 171 | 172 | { routes } 173 | 174 | ) 175 | } 176 | ... 177 | ``` 178 | 179 | Contributors 180 | ------------ 181 | See [Contributors](https://github.com/supasate/connected-react-router/graphs/contributors) and [Acknowledge](https://github.com/supasate/connected-react-router/blob/master/ACKNOWLEDGE.md). 182 | 183 | License 184 | ------- 185 | [MIT License](https://github.com/supasate/connected-react-router/blob/master/LICENSE.md) 186 | -------------------------------------------------------------------------------- /examples/basic/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/basic/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "plugins": [ 8 | "import", 9 | "react" 10 | ], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:import/errors", 14 | "plugin:react/recommended" 15 | ], 16 | "rules": { 17 | "semi": [2, "never"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Connected React Router Example 2 | 3 | ## Install 4 | ```bash 5 | yarn 6 | ``` 7 | 8 | ## Run 9 | ```bash 10 | npm run dev 11 | ``` 12 | 13 | You can try changing counter value and editing some components. Components will be updated while preserving counter state. 14 | 15 | In Hello link, you will see that the HelloChild component can access router state (URL path) without passing as props via its parent. 16 | -------------------------------------------------------------------------------- /examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Minimal Hot Reload 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connected-react-router-example-basic", 3 | "version": "1.0.0", 4 | "description": "A basic example for Connected React Router", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "dev": "node server.js", 8 | "build": "webpack --mode production" 9 | }, 10 | "author": "Supasate Choochaisri", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@babel/core": "^7.1.2", 14 | "@babel/preset-env": "^7.1.0", 15 | "@babel/preset-react": "^7.0.0", 16 | "babel-cli": "^6.26.0", 17 | "babel-loader": "^8.0.4", 18 | "express": "^4.16.4", 19 | "react-hot-loader": "^4.3.12", 20 | "webpack": "^4.24.0", 21 | "webpack-cli": "^3.1.2", 22 | "webpack-dev-middleware": "^3.4.0", 23 | "webpack-hot-middleware": "^2.24.3" 24 | }, 25 | "dependencies": { 26 | "connected-react-router": "^6.0.0", 27 | "history": "^4.7.2", 28 | "prop-types": "^15.6.2", 29 | "react": "^16.6.0", 30 | "react-dom": "^16.6.0", 31 | "react-redux": "^6.0.0", 32 | "react-router": "^4.3.1", 33 | "react-router-dom": "^4.3.1", 34 | "redux": "^4.0.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/basic/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const webpackDevMiddleware = require('webpack-dev-middleware') 4 | const webpackHotMiddleware = require('webpack-hot-middleware') 5 | const express = require('express') 6 | const config = require('./webpack.config') 7 | 8 | const app = express() 9 | const compiler = webpack(config) 10 | 11 | app.use(webpackDevMiddleware(compiler, { 12 | publicPath: config.output.publicPath, 13 | historyApiFallback: true, 14 | })) 15 | 16 | app.use(webpackHotMiddleware(compiler)) 17 | 18 | app.get('*', (req, res) => { 19 | res.sendFile(path.join(__dirname, 'index.html')) 20 | }) 21 | 22 | app.listen(8080, (err) => { 23 | if (err) { 24 | return console.error(err) // eslint-disable-line no-console 25 | } 26 | console.log('Listening at http://localhost:8080') // eslint-disable-line no-console 27 | }) 28 | -------------------------------------------------------------------------------- /examples/basic/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { ConnectedRouter } from 'connected-react-router' 4 | import routes from './routes' 5 | 6 | const App = ({ history }) => { 7 | return ( 8 | 9 | { routes } 10 | 11 | ) 12 | } 13 | 14 | App.propTypes = { 15 | history: PropTypes.object, 16 | } 17 | 18 | export default App 19 | -------------------------------------------------------------------------------- /examples/basic/src/actions/counter.js: -------------------------------------------------------------------------------- 1 | export const increment = () => ({ 2 | type: 'INCREMENT', 3 | }) 4 | 5 | export const decrement = () => ({ 6 | type: 'DECREMENT', 7 | }) 8 | -------------------------------------------------------------------------------- /examples/basic/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { increment, decrement } from '../actions/counter' 5 | 6 | const Counter = (props) => ( 7 |
8 | Counter: {props.count} 9 | 10 | 11 |
12 | ) 13 | 14 | Counter.propTypes = { 15 | count: PropTypes.number, 16 | increment: PropTypes.func.isRequired, 17 | decrement: PropTypes.func.isRequired, 18 | } 19 | 20 | const mapStateToProps = state => ({ 21 | count: state.count, 22 | }) 23 | 24 | const mapDispatchToProps = dispatch => ({ 25 | increment: () => dispatch(increment()), 26 | decrement: () => dispatch(decrement()), 27 | }) 28 | 29 | export default connect(mapStateToProps, mapDispatchToProps)(Counter) 30 | -------------------------------------------------------------------------------- /examples/basic/src/components/Hello.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HelloChild from './HelloChild' 3 | 4 | const Hello = () => ( 5 |
6 |
Hello
7 | 8 |
9 | ) 10 | 11 | export default Hello 12 | -------------------------------------------------------------------------------- /examples/basic/src/components/HelloChild.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { Link } from 'react-router-dom' 5 | 6 | const HelloChild = ({ pathname, search, hash }) => ( 7 |
8 | Hello-Child 9 |
    10 |
  • with query string
  • 11 |
  • with hash
  • 12 |
13 |
14 | pathname: {pathname} 15 |
16 |
17 | search: {search} 18 |
19 |
20 | hash: {hash} 21 |
22 |
23 | ) 24 | 25 | HelloChild.propTypes = { 26 | pathname: PropTypes.string, 27 | search: PropTypes.string, 28 | hash: PropTypes.string, 29 | } 30 | 31 | const mapStateToProps = state => ({ 32 | pathname: state.router.location.pathname, 33 | search: state.router.location.search, 34 | hash: state.router.location.hash, 35 | }) 36 | 37 | export default connect(mapStateToProps)(HelloChild) 38 | -------------------------------------------------------------------------------- /examples/basic/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Home = () => ( 4 |
5 | Home 6 |
7 | ) 8 | 9 | export default Home 10 | -------------------------------------------------------------------------------- /examples/basic/src/components/NavBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | const NavBar = () => ( 5 |
6 |
Home Hello Counter
7 |
8 | ) 9 | 10 | export default NavBar 11 | -------------------------------------------------------------------------------- /examples/basic/src/components/NoMatch.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const NoMatch = () => ( 4 |
5 | No Match 6 |
7 | ) 8 | 9 | export default NoMatch 10 | -------------------------------------------------------------------------------- /examples/basic/src/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history' 2 | import { applyMiddleware, compose, createStore } from 'redux' 3 | import { routerMiddleware } from 'connected-react-router' 4 | import createRootReducer from './reducers' 5 | 6 | export const history = createBrowserHistory() 7 | 8 | export default function configureStore(preloadedState) { 9 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose 10 | const store = createStore( 11 | createRootReducer(history), 12 | preloadedState, 13 | composeEnhancer( 14 | applyMiddleware( 15 | routerMiddleware(history), 16 | ), 17 | ), 18 | ) 19 | 20 | // Hot reloading 21 | if (module.hot) { 22 | // Enable Webpack hot module replacement for reducers 23 | module.hot.accept('./reducers', () => { 24 | store.replaceReducer(createRootReducer(history)); 25 | }); 26 | } 27 | 28 | return store 29 | } 30 | -------------------------------------------------------------------------------- /examples/basic/src/index.js: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import { Provider } from 'react-redux' 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import App from './App' 6 | import configureStore, { history } from './configureStore' 7 | 8 | const store = configureStore() 9 | const render = () => { 10 | ReactDOM.render( 11 | 12 | 13 | 14 | 15 | , 16 | document.getElementById('react-root') 17 | ) 18 | } 19 | 20 | render() 21 | 22 | // Hot reloading 23 | if (module.hot) { 24 | // Reload components 25 | module.hot.accept('./App', () => { 26 | render() 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /examples/basic/src/reducers/counter.js: -------------------------------------------------------------------------------- 1 | const counterReducer = (state = 0, action) => { 2 | switch (action.type) { 3 | case 'INCREMENT': 4 | return state + 1 5 | case 'DECREMENT': 6 | return state - 1 7 | default: 8 | return state 9 | } 10 | } 11 | 12 | export default counterReducer 13 | -------------------------------------------------------------------------------- /examples/basic/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { connectRouter } from 'connected-react-router' 3 | import counterReducer from './counter' 4 | 5 | const rootReducer = (history) => combineReducers({ 6 | count: counterReducer, 7 | router: connectRouter(history) 8 | }) 9 | 10 | export default rootReducer 11 | -------------------------------------------------------------------------------- /examples/basic/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, Switch } from 'react-router' 3 | import Home from '../components/Home' 4 | import Hello from '../components/Hello' 5 | import Counter from '../components/Counter' 6 | import NoMatch from '../components/NoMatch' 7 | import NavBar from '../components/NavBar' 8 | 9 | const routes = ( 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | ) 20 | 21 | export default routes 22 | -------------------------------------------------------------------------------- /examples/basic/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | module.exports = { 5 | mode: 'development', 6 | devtool: 'eval-source-map', 7 | entry: [ 8 | 'react-hot-loader/patch', 9 | 'webpack-hot-middleware/client', 10 | path.resolve('src/index.js'), 11 | ], 12 | output: { 13 | filename: 'bundle.js', 14 | path: path.resolve(__dirname, 'dist'), 15 | publicPath: '/dist/', 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.jsx?/, 21 | // Don't use .babelrc in `yarn link`-ed dependency's directory and use in current direction instead 22 | loader: 'babel-loader?babelrc=false&extends=' + path.resolve(__dirname, '.babelrc') 23 | } 24 | ], 25 | }, 26 | plugins: [ 27 | new webpack.HotModuleReplacementPlugin(), 28 | ], 29 | resolveLoader: { 30 | modules: [ 31 | 'node_modules', 32 | ], 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /examples/immutable/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/immutable/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "plugins": [ 8 | "import", 9 | "react" 10 | ], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:import/errors", 14 | "plugin:react/recommended" 15 | ], 16 | "rules": { 17 | "semi": [2, "never"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/immutable/README.md: -------------------------------------------------------------------------------- 1 | # Connected React Router Example 2 | 3 | ## Install 4 | ```bash 5 | yarn 6 | ``` 7 | 8 | ## Run 9 | ```bash 10 | npm run dev 11 | ``` 12 | 13 | You can try changing counter value and editing some components. Components will be updated while preserving counter state. 14 | 15 | In Hello link, you will see that the HelloChild component can access router state (URL path) without passing as props via its parent. 16 | -------------------------------------------------------------------------------- /examples/immutable/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Minimal Hot Reload 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/immutable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connected-react-router-example-immutable", 3 | "version": "1.0.0", 4 | "description": "An example with Immutable.js for Connected React Router", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "dev": "node server.js", 8 | "build": "webpack --mode production" 9 | }, 10 | "author": "Supasate Choochaisri", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@babel/core": "^7.1.2", 14 | "@babel/preset-env": "^7.1.0", 15 | "@babel/preset-react": "^7.0.0", 16 | "babel-loader": "^8.0.4", 17 | "express": "^4.16.4", 18 | "react-hot-loader": "^4.3.12", 19 | "webpack": "^4.24.0", 20 | "webpack-cli": "^3.1.2", 21 | "webpack-dev-middleware": "^3.4.0", 22 | "webpack-hot-middleware": "^2.24.3" 23 | }, 24 | "dependencies": { 25 | "connected-react-router": "^6.0.0", 26 | "history": "^4.7.2", 27 | "immutable": "^4.0.0-rc.12", 28 | "prop-types": "^15.6.2", 29 | "react": "^16.6.0", 30 | "react-dom": "^16.6.0", 31 | "react-redux": "^6.0.0", 32 | "react-router": "^4.3.1", 33 | "react-router-dom": "^4.3.1", 34 | "redux": "^4.0.1", 35 | "redux-immutable": "^4.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/immutable/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const webpackDevMiddleware = require('webpack-dev-middleware') 4 | const webpackHotMiddleware = require('webpack-hot-middleware') 5 | const express = require('express') 6 | const config = require('./webpack.config') 7 | 8 | const app = express() 9 | const compiler = webpack(config) 10 | 11 | app.use(webpackDevMiddleware(compiler, { 12 | publicPath: config.output.publicPath, 13 | historyApiFallback: true, 14 | })) 15 | 16 | app.use(webpackHotMiddleware(compiler)) 17 | 18 | app.get('*', (req, res) => { 19 | res.sendFile(path.join(__dirname, 'index.html')) 20 | }) 21 | 22 | app.listen(8080, (err) => { 23 | if (err) { 24 | return console.error(err) // eslint-disable-line no-console 25 | } 26 | console.log('Listening at http://localhost:8080') // eslint-disable-line no-console 27 | }) 28 | -------------------------------------------------------------------------------- /examples/immutable/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { ConnectedRouter } from 'connected-react-router/immutable' 4 | import routes from './routes' 5 | 6 | const App = ({ history }) => { 7 | return ( 8 | 9 | { routes } 10 | 11 | ) 12 | } 13 | 14 | App.propTypes = { 15 | history: PropTypes.object, 16 | } 17 | 18 | export default App 19 | -------------------------------------------------------------------------------- /examples/immutable/src/actions/counter.js: -------------------------------------------------------------------------------- 1 | export const increment = () => ({ 2 | type: 'INCREMENT', 3 | }) 4 | 5 | export const decrement = () => ({ 6 | type: 'DECREMENT', 7 | }) 8 | -------------------------------------------------------------------------------- /examples/immutable/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { increment, decrement } from '../actions/counter' 5 | 6 | const Counter = (props) => ( 7 |
8 | Counter: {props.count} 9 | 10 | 11 |
12 | ) 13 | 14 | Counter.propTypes = { 15 | count: PropTypes.number, 16 | increment: PropTypes.func.isRequired, 17 | decrement: PropTypes.func.isRequired, 18 | } 19 | 20 | const mapStateToProps = state => ({ 21 | count: state.getIn(['count']), 22 | }) 23 | 24 | const mapDispatchToProps = dispatch => ({ 25 | increment: () => dispatch(increment()), 26 | decrement: () => dispatch(decrement()), 27 | }) 28 | 29 | export default connect(mapStateToProps, mapDispatchToProps)(Counter) 30 | -------------------------------------------------------------------------------- /examples/immutable/src/components/Hello.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HelloChild from './HelloChild' 3 | 4 | const Hello = () => ( 5 |
6 |
Hello
7 | 8 |
9 | ) 10 | 11 | export default Hello 12 | -------------------------------------------------------------------------------- /examples/immutable/src/components/HelloChild.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { Link } from 'react-router-dom' 5 | 6 | const HelloChild = ({ pathname, search, hash }) => ( 7 |
8 | Hello-Child 9 |
    10 |
  • with query string
  • 11 |
  • with hash
  • 12 |
13 |
14 | pathname: {pathname} 15 |
16 |
17 | search: {search} 18 |
19 |
20 | hash: {hash} 21 |
22 |
23 | ) 24 | 25 | HelloChild.propTypes = { 26 | pathname: PropTypes.string, 27 | search: PropTypes.string, 28 | hash: PropTypes.string, 29 | } 30 | 31 | const mapStateToProps = state => ({ 32 | pathname: state.getIn(['router', 'location', 'pathname']), 33 | search: state.getIn(['router', 'location', 'search']), 34 | hash: state.getIn(['router', 'location', 'hash']), 35 | }) 36 | 37 | export default connect(mapStateToProps)(HelloChild) 38 | -------------------------------------------------------------------------------- /examples/immutable/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Home = () => ( 4 |
5 | Home 6 |
7 | ) 8 | 9 | export default Home 10 | -------------------------------------------------------------------------------- /examples/immutable/src/components/NavBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | const NavBar = () => ( 5 |
6 |
Home Hello Counter
7 |
8 | ) 9 | 10 | export default NavBar 11 | -------------------------------------------------------------------------------- /examples/immutable/src/components/NoMatch.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const NoMatch = () => ( 4 |
5 | No Match 6 |
7 | ) 8 | 9 | export default NoMatch 10 | -------------------------------------------------------------------------------- /examples/immutable/src/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history' 2 | import { applyMiddleware, compose, createStore } from 'redux' 3 | import { routerMiddleware } from 'connected-react-router/immutable' 4 | import createRootReducer from './reducers' 5 | 6 | export const history = createBrowserHistory() 7 | 8 | export default function configureStore(preloadedState) { 9 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose 10 | const store = createStore( 11 | createRootReducer(history), 12 | preloadedState, 13 | composeEnhancer( 14 | applyMiddleware( 15 | routerMiddleware(history), 16 | ), 17 | ), 18 | ) 19 | 20 | // Hot reloading 21 | if (module.hot) { 22 | // Enable Webpack hot module replacement for reducers 23 | module.hot.accept('./reducers', () => { 24 | store.replaceReducer(createRootReducer(history)); 25 | }); 26 | } 27 | 28 | return store 29 | } 30 | -------------------------------------------------------------------------------- /examples/immutable/src/index.js: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import { Provider } from 'react-redux' 3 | import Immutable from 'immutable' 4 | import React from 'react' 5 | import ReactDOM from 'react-dom' 6 | import App from './App' 7 | import configureStore, { history } from './configureStore' 8 | 9 | const initialState = Immutable.Map() 10 | const store = configureStore(initialState) 11 | const render = () => { 12 | ReactDOM.render( 13 | 14 | 15 | 16 | 17 | , 18 | document.getElementById('react-root') 19 | ) 20 | } 21 | 22 | render() 23 | 24 | // Hot reloading 25 | if (module.hot) { 26 | // Reload components 27 | module.hot.accept('./App', () => { 28 | render() 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /examples/immutable/src/reducers/counter.js: -------------------------------------------------------------------------------- 1 | const counterReducer = (state = 0, action) => { 2 | switch (action.type) { 3 | case 'INCREMENT': 4 | return state + 1 5 | case 'DECREMENT': 6 | return state - 1 7 | default: 8 | return state 9 | } 10 | } 11 | 12 | export default counterReducer 13 | -------------------------------------------------------------------------------- /examples/immutable/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux-immutable' 2 | import { connectRouter } from 'connected-react-router/immutable' 3 | import counterReducer from './counter' 4 | 5 | const rootReducer = (history) => combineReducers({ 6 | count: counterReducer, 7 | router: connectRouter(history) 8 | }) 9 | 10 | export default rootReducer 11 | -------------------------------------------------------------------------------- /examples/immutable/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, Switch } from 'react-router' 3 | import Home from '../components/Home' 4 | import Hello from '../components/Hello' 5 | import Counter from '../components/Counter' 6 | import NoMatch from '../components/NoMatch' 7 | import NavBar from '../components/NavBar' 8 | 9 | const routes = ( 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | ) 20 | 21 | export default routes 22 | -------------------------------------------------------------------------------- /examples/immutable/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | module.exports = { 5 | mode: 'development', 6 | devtool: 'eval-source-map', 7 | entry: [ 8 | 'react-hot-loader/patch', 9 | 'webpack-hot-middleware/client', 10 | path.resolve('src/index.js'), 11 | ], 12 | output: { 13 | filename: 'bundle.js', 14 | path: path.resolve(__dirname, 'dist'), 15 | publicPath: '/dist/', 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.jsx?/, 21 | // Don't use .babelrc in `yarn link`-ed dependency's directory and use in current direction instead 22 | loader: 'babel-loader?babelrc=false&extends=' + path.resolve(__dirname, '.babelrc') 23 | } 24 | ], 25 | }, 26 | plugins: [ 27 | new webpack.HotModuleReplacementPlugin(), 28 | ], 29 | resolveLoader: { 30 | modules: [ 31 | 'node_modules', 32 | ], 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /examples/react-native/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /examples/react-native/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | }; 5 | -------------------------------------------------------------------------------- /examples/react-native/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | node_modules/react-native/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | node_modules/react-native/Libraries/polyfills/.* 18 | 19 | ; These should not be required directly 20 | ; require from fbjs/lib instead: require('fbjs/lib/warning') 21 | node_modules/warning/.* 22 | 23 | ; Flow doesn't support platforms 24 | .*/Libraries/Utilities/HMRLoadingView.js 25 | 26 | [untyped] 27 | .*/node_modules/@react-native-community/cli/.*/.* 28 | 29 | [include] 30 | 31 | [libs] 32 | node_modules/react-native/Libraries/react-native/react-native-interface.js 33 | node_modules/react-native/flow/ 34 | 35 | [options] 36 | emoji=true 37 | 38 | esproposal.optional_chaining=enable 39 | esproposal.nullish_coalescing=enable 40 | 41 | module.file_ext=.js 42 | module.file_ext=.json 43 | module.file_ext=.ios.js 44 | 45 | module.system=haste 46 | module.system.haste.use_name_reducers=true 47 | # get basename 48 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' 49 | # strip .js or .js.flow suffix 50 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' 51 | # strip .ios suffix 52 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' 53 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' 54 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' 55 | module.system.haste.paths.blacklist=.*/__tests__/.* 56 | module.system.haste.paths.blacklist=.*/__mocks__/.* 57 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* 58 | module.system.haste.paths.whitelist=/node_modules/react-native/RNTester/.* 59 | module.system.haste.paths.whitelist=/node_modules/react-native/IntegrationTests/.* 60 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/react-native/react-native-implementation.js 61 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* 62 | 63 | munge_underscores=true 64 | 65 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 66 | 67 | suppress_type=$FlowIssue 68 | suppress_type=$FlowFixMe 69 | suppress_type=$FlowFixMeProps 70 | suppress_type=$FlowFixMeState 71 | 72 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) 73 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ 74 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 75 | 76 | [lints] 77 | sketchy-null-number=warn 78 | sketchy-null-mixed=warn 79 | sketchy-number=warn 80 | untyped-type-import=warn 81 | nonstrict-import=warn 82 | deprecated-type=warn 83 | unsafe-getters-setters=warn 84 | inexact-spread=warn 85 | unnecessary-invariant=warn 86 | signature-verification-failure=warn 87 | deprecated-utility=error 88 | 89 | [strict] 90 | deprecated-type 91 | nonstrict-import 92 | sketchy-null 93 | unclear-type 94 | unsafe-getters-setters 95 | untyped-import 96 | untyped-type-import 97 | 98 | [version] 99 | ^0.98.0 100 | -------------------------------------------------------------------------------- /examples/react-native/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /examples/react-native/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # CocoaPods 59 | /ios/Pods/ 60 | -------------------------------------------------------------------------------- /examples/react-native/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /examples/react-native/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /examples/react-native/android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.connectedreactrouter", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.connectedreactrouter", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /examples/react-native/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format 22 | * bundleCommand: "ram-bundle", 23 | * 24 | * // whether to bundle JS and assets in debug mode 25 | * bundleInDebug: false, 26 | * 27 | * // whether to bundle JS and assets in release mode 28 | * bundleInRelease: true, 29 | * 30 | * // whether to bundle JS and assets in another build variant (if configured). 31 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 32 | * // The configuration property can be in the following formats 33 | * // 'bundleIn${productFlavor}${buildType}' 34 | * // 'bundleIn${buildType}' 35 | * // bundleInFreeDebug: true, 36 | * // bundleInPaidRelease: true, 37 | * // bundleInBeta: true, 38 | * 39 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 40 | * // for example: to disable dev mode in the staging build type (if configured) 41 | * devDisabledInStaging: true, 42 | * // The configuration property can be in the following formats 43 | * // 'devDisabledIn${productFlavor}${buildType}' 44 | * // 'devDisabledIn${buildType}' 45 | * 46 | * // the root of your project, i.e. where "package.json" lives 47 | * root: "../../", 48 | * 49 | * // where to put the JS bundle asset in debug mode 50 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 51 | * 52 | * // where to put the JS bundle asset in release mode 53 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 54 | * 55 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 56 | * // require('./image.png')), in debug mode 57 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 58 | * 59 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 60 | * // require('./image.png')), in release mode 61 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 62 | * 63 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 64 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 65 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 66 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 67 | * // for example, you might want to remove it from here. 68 | * inputExcludes: ["android/**", "ios/**"], 69 | * 70 | * // override which node gets called and with what additional arguments 71 | * nodeExecutableAndArgs: ["node"], 72 | * 73 | * // supply additional arguments to the packager 74 | * extraPackagerArgs: [] 75 | * ] 76 | */ 77 | 78 | project.ext.react = [ 79 | entryFile: "index.js", 80 | enableHermes: false, // clean and rebuild if changing 81 | ] 82 | 83 | apply from: "../../node_modules/react-native/react.gradle" 84 | 85 | /** 86 | * Set this to true to create two separate APKs instead of one: 87 | * - An APK that only works on ARM devices 88 | * - An APK that only works on x86 devices 89 | * The advantage is the size of the APK is reduced by about 4MB. 90 | * Upload all the APKs to the Play Store and people will download 91 | * the correct one based on the CPU architecture of their device. 92 | */ 93 | def enableSeparateBuildPerCPUArchitecture = false 94 | 95 | /** 96 | * Run Proguard to shrink the Java bytecode in release builds. 97 | */ 98 | def enableProguardInReleaseBuilds = false 99 | 100 | /** 101 | * The preferred build flavor of JavaScriptCore. 102 | * 103 | * For example, to use the international variant, you can use: 104 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 105 | * 106 | * The international variant includes ICU i18n library and necessary data 107 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 108 | * give correct results when using with locales other than en-US. Note that 109 | * this variant is about 6MiB larger per architecture than default. 110 | */ 111 | def jscFlavor = 'org.webkit:android-jsc:+' 112 | 113 | /** 114 | * Whether to enable the Hermes VM. 115 | * 116 | * This should be set on project.ext.react and mirrored here. If it is not set 117 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 118 | * and the benefits of using Hermes will therefore be sharply reduced. 119 | */ 120 | def enableHermes = project.ext.react.get("enableHermes", false); 121 | 122 | android { 123 | compileSdkVersion rootProject.ext.compileSdkVersion 124 | 125 | compileOptions { 126 | sourceCompatibility JavaVersion.VERSION_1_8 127 | targetCompatibility JavaVersion.VERSION_1_8 128 | } 129 | 130 | defaultConfig { 131 | applicationId "com.connectedreactrouter" 132 | minSdkVersion rootProject.ext.minSdkVersion 133 | targetSdkVersion rootProject.ext.targetSdkVersion 134 | versionCode 1 135 | versionName "1.0" 136 | } 137 | splits { 138 | abi { 139 | reset() 140 | enable enableSeparateBuildPerCPUArchitecture 141 | universalApk false // If true, also generate a universal APK 142 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 143 | } 144 | } 145 | signingConfigs { 146 | debug { 147 | storeFile file('debug.keystore') 148 | storePassword 'android' 149 | keyAlias 'androiddebugkey' 150 | keyPassword 'android' 151 | } 152 | } 153 | buildTypes { 154 | debug { 155 | signingConfig signingConfigs.debug 156 | } 157 | release { 158 | // Caution! In production, you need to generate your own keystore file. 159 | // see https://facebook.github.io/react-native/docs/signed-apk-android. 160 | signingConfig signingConfigs.debug 161 | minifyEnabled enableProguardInReleaseBuilds 162 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 163 | } 164 | } 165 | // applicationVariants are e.g. debug, release 166 | applicationVariants.all { variant -> 167 | variant.outputs.each { output -> 168 | // For each separate APK per architecture, set a unique version code as described here: 169 | // https://developer.android.com/studio/build/configure-apk-splits.html 170 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 171 | def abi = output.getFilter(OutputFile.ABI) 172 | if (abi != null) { // null for the universal-debug, universal-release variants 173 | output.versionCodeOverride = 174 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 175 | } 176 | 177 | } 178 | } 179 | 180 | packagingOptions { 181 | pickFirst '**/armeabi-v7a/libc++_shared.so' 182 | pickFirst '**/x86/libc++_shared.so' 183 | pickFirst '**/arm64-v8a/libc++_shared.so' 184 | pickFirst '**/x86_64/libc++_shared.so' 185 | pickFirst '**/x86/libjsc.so' 186 | pickFirst '**/armeabi-v7a/libjsc.so' 187 | } 188 | } 189 | 190 | dependencies { 191 | implementation fileTree(dir: "libs", include: ["*.jar"]) 192 | implementation "com.facebook.react:react-native:+" // From node_modules 193 | 194 | if (enableHermes) { 195 | def hermesPath = "../../node_modules/hermesvm/android/"; 196 | debugImplementation files(hermesPath + "hermes-debug.aar") 197 | releaseImplementation files(hermesPath + "hermes-release.aar") 198 | } else { 199 | implementation jscFlavor 200 | } 201 | } 202 | 203 | // Run this once to be able to run the application with BUCK 204 | // puts all compile dependencies into folder libs for BUCK to use 205 | task copyDownloadableDepsToLibs(type: Copy) { 206 | from configurations.compile 207 | into 'libs' 208 | } 209 | 210 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 211 | -------------------------------------------------------------------------------- /examples/react-native/android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /examples/react-native/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /examples/react-native/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/java/com/connectedreactrouter/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.connectedreactrouter; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "ConnectedReactRouter"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/java/com/connectedreactrouter/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.connectedreactrouter; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.facebook.react.PackageList; 7 | import com.facebook.hermes.reactexecutor.HermesExecutorFactory; 8 | import com.facebook.react.bridge.JavaScriptExecutorFactory; 9 | import com.facebook.react.ReactApplication; 10 | import com.facebook.react.ReactNativeHost; 11 | import com.facebook.react.ReactPackage; 12 | import com.facebook.soloader.SoLoader; 13 | 14 | import java.util.List; 15 | 16 | public class MainApplication extends Application implements ReactApplication { 17 | 18 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 19 | @Override 20 | public boolean getUseDeveloperSupport() { 21 | return BuildConfig.DEBUG; 22 | } 23 | 24 | @Override 25 | protected List getPackages() { 26 | @SuppressWarnings("UnnecessaryLocalVariable") 27 | List packages = new PackageList(this).getPackages(); 28 | // Packages that cannot be autolinked yet can be added manually here, for example: 29 | // packages.add(new MyReactNativePackage()); 30 | return packages; 31 | } 32 | 33 | @Override 34 | protected String getJSMainModuleName() { 35 | return "index"; 36 | } 37 | }; 38 | 39 | @Override 40 | public ReactNativeHost getReactNativeHost() { 41 | return mReactNativeHost; 42 | } 43 | 44 | @Override 45 | public void onCreate() { 46 | super.onCreate(); 47 | SoLoader.init(this, /* native exopackage */ false); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ConnectedReactRouter 3 | 4 | -------------------------------------------------------------------------------- /examples/react-native/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/react-native/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "28.0.3" 6 | minSdkVersion = 16 7 | compileSdkVersion = 28 8 | targetSdkVersion = 28 9 | supportLibVersion = "28.0.0" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath("com.android.tools.build:gradle:3.4.1") 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenLocal() 26 | maven { 27 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 28 | url("$rootDir/../node_modules/react-native/android") 29 | } 30 | maven { 31 | // Android JSC is installed from npm 32 | url("$rootDir/../node_modules/jsc-android/dist") 33 | } 34 | 35 | google() 36 | jcenter() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/react-native/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useAndroidX=true 21 | android.enableJetifier=true 22 | -------------------------------------------------------------------------------- /examples/react-native/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supasate/connected-react-router/5d4c197043deb89238f9b380a6a1ef4d909f14a7/examples/react-native/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/react-native/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /examples/react-native/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin, switch paths to Windows format before running java 129 | if $cygwin ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /examples/react-native/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem http://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /examples/react-native/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ConnectedReactRouter' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | -------------------------------------------------------------------------------- /examples/react-native/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ConnectedReactRouter", 3 | "displayName": "ConnectedReactRouter" 4 | } -------------------------------------------------------------------------------- /examples/react-native/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /examples/react-native/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './src/App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSExceptionDomains 28 | 29 | localhost 30 | 31 | NSExceptionAllowsInsecureHTTPLoads 32 | 33 | 34 | 35 | 36 | NSLocationWhenInUseUsageDescription 37 | 38 | UILaunchStoryboardName 39 | LaunchScreen 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter.xcodeproj/xcshareddata/xcschemes/ConnectedReactRouter-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter.xcodeproj/xcshareddata/xcschemes/ConnectedReactRouter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 61 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 92 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 113 | 119 | 120 | 121 | 122 | 124 | 125 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (nonatomic, strong) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | @implementation AppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 19 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 20 | moduleName:@"ConnectedReactRouter" 21 | initialProperties:nil]; 22 | 23 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 24 | 25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | UIViewController *rootViewController = [UIViewController new]; 27 | rootViewController.view = rootView; 28 | self.window.rootViewController = rootViewController; 29 | [self.window makeKeyAndVisible]; 30 | return YES; 31 | } 32 | 33 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 34 | { 35 | #if DEBUG 36 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 37 | #else 38 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 39 | #endif 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ConnectedReactRouter 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSExceptionDomains 32 | 33 | localhost 34 | 35 | NSExceptionAllowsInsecureHTTPLoads 36 | 37 | 38 | 39 | 40 | NSLocationWhenInUseUsageDescription 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UIViewControllerBasedStatusBarAppearance 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouter/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouterTests/ConnectedReactRouterTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | #define TIMEOUT_SECONDS 600 15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 16 | 17 | @interface ConnectedReactRouterTests : XCTestCase 18 | 19 | @end 20 | 21 | @implementation ConnectedReactRouterTests 22 | 23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 24 | { 25 | if (test(view)) { 26 | return YES; 27 | } 28 | for (UIView *subview in [view subviews]) { 29 | if ([self findSubviewInView:subview matching:test]) { 30 | return YES; 31 | } 32 | } 33 | return NO; 34 | } 35 | 36 | - (void)testRendersWelcomeScreen 37 | { 38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 40 | BOOL foundElement = NO; 41 | 42 | __block NSString *redboxError = nil; 43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 44 | if (level >= RCTLogLevelError) { 45 | redboxError = message; 46 | } 47 | }); 48 | 49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 52 | 53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 55 | return YES; 56 | } 57 | return NO; 58 | }]; 59 | } 60 | 61 | RCTSetLogFunction(RCTDefaultLogFunction); 62 | 63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 65 | } 66 | 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /examples/react-native/ios/ConnectedReactRouterTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/react-native/ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | target 'ConnectedReactRouter' do 5 | # Pods for ConnectedReactRouter 6 | pod 'React', :path => '../node_modules/react-native/' 7 | pod 'React-Core', :path => '../node_modules/react-native/React' 8 | pod 'React-DevSupport', :path => '../node_modules/react-native/React' 9 | pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' 10 | pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' 11 | pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' 12 | pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' 13 | pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' 14 | pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' 15 | pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' 16 | pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' 17 | pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' 18 | pod 'React-RCTWebSocket', :path => '../node_modules/react-native/Libraries/WebSocket' 19 | 20 | pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' 21 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' 22 | pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' 23 | pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' 24 | pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga' 25 | 26 | pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' 27 | pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' 28 | pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' 29 | 30 | target 'ConnectedReactRouterTests' do 31 | inherit! :search_paths 32 | # Pods for testing 33 | end 34 | 35 | use_native_modules! 36 | end 37 | 38 | target 'ConnectedReactRouter-tvOS' do 39 | # Pods for ConnectedReactRouter-tvOS 40 | 41 | target 'ConnectedReactRouter-tvOSTests' do 42 | inherit! :search_paths 43 | # Pods for testing 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /examples/react-native/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - boost-for-react-native (1.63.0) 3 | - DoubleConversion (1.1.6) 4 | - Folly (2018.10.22.00): 5 | - boost-for-react-native 6 | - DoubleConversion 7 | - Folly/Default (= 2018.10.22.00) 8 | - glog 9 | - Folly/Default (2018.10.22.00): 10 | - boost-for-react-native 11 | - DoubleConversion 12 | - glog 13 | - glog (0.3.5) 14 | - React (0.60.5): 15 | - React-Core (= 0.60.5) 16 | - React-DevSupport (= 0.60.5) 17 | - React-RCTActionSheet (= 0.60.5) 18 | - React-RCTAnimation (= 0.60.5) 19 | - React-RCTBlob (= 0.60.5) 20 | - React-RCTImage (= 0.60.5) 21 | - React-RCTLinking (= 0.60.5) 22 | - React-RCTNetwork (= 0.60.5) 23 | - React-RCTSettings (= 0.60.5) 24 | - React-RCTText (= 0.60.5) 25 | - React-RCTVibration (= 0.60.5) 26 | - React-RCTWebSocket (= 0.60.5) 27 | - React-Core (0.60.5): 28 | - Folly (= 2018.10.22.00) 29 | - React-cxxreact (= 0.60.5) 30 | - React-jsiexecutor (= 0.60.5) 31 | - yoga (= 0.60.5.React) 32 | - React-cxxreact (0.60.5): 33 | - boost-for-react-native (= 1.63.0) 34 | - DoubleConversion 35 | - Folly (= 2018.10.22.00) 36 | - glog 37 | - React-jsinspector (= 0.60.5) 38 | - React-DevSupport (0.60.5): 39 | - React-Core (= 0.60.5) 40 | - React-RCTWebSocket (= 0.60.5) 41 | - React-jsi (0.60.5): 42 | - boost-for-react-native (= 1.63.0) 43 | - DoubleConversion 44 | - Folly (= 2018.10.22.00) 45 | - glog 46 | - React-jsi/Default (= 0.60.5) 47 | - React-jsi/Default (0.60.5): 48 | - boost-for-react-native (= 1.63.0) 49 | - DoubleConversion 50 | - Folly (= 2018.10.22.00) 51 | - glog 52 | - React-jsiexecutor (0.60.5): 53 | - DoubleConversion 54 | - Folly (= 2018.10.22.00) 55 | - glog 56 | - React-cxxreact (= 0.60.5) 57 | - React-jsi (= 0.60.5) 58 | - React-jsinspector (0.60.5) 59 | - React-RCTActionSheet (0.60.5): 60 | - React-Core (= 0.60.5) 61 | - React-RCTAnimation (0.60.5): 62 | - React-Core (= 0.60.5) 63 | - React-RCTBlob (0.60.5): 64 | - React-Core (= 0.60.5) 65 | - React-RCTNetwork (= 0.60.5) 66 | - React-RCTWebSocket (= 0.60.5) 67 | - React-RCTImage (0.60.5): 68 | - React-Core (= 0.60.5) 69 | - React-RCTNetwork (= 0.60.5) 70 | - React-RCTLinking (0.60.5): 71 | - React-Core (= 0.60.5) 72 | - React-RCTNetwork (0.60.5): 73 | - React-Core (= 0.60.5) 74 | - React-RCTSettings (0.60.5): 75 | - React-Core (= 0.60.5) 76 | - React-RCTText (0.60.5): 77 | - React-Core (= 0.60.5) 78 | - React-RCTVibration (0.60.5): 79 | - React-Core (= 0.60.5) 80 | - React-RCTWebSocket (0.60.5): 81 | - React-Core (= 0.60.5) 82 | - yoga (0.60.5.React) 83 | 84 | DEPENDENCIES: 85 | - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) 86 | - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) 87 | - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) 88 | - React (from `../node_modules/react-native/`) 89 | - React-Core (from `../node_modules/react-native/React`) 90 | - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) 91 | - React-DevSupport (from `../node_modules/react-native/React`) 92 | - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) 93 | - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) 94 | - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) 95 | - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) 96 | - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) 97 | - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) 98 | - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) 99 | - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) 100 | - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) 101 | - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) 102 | - React-RCTText (from `../node_modules/react-native/Libraries/Text`) 103 | - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) 104 | - React-RCTWebSocket (from `../node_modules/react-native/Libraries/WebSocket`) 105 | - yoga (from `../node_modules/react-native/ReactCommon/yoga`) 106 | 107 | SPEC REPOS: 108 | https://github.com/cocoapods/specs.git: 109 | - boost-for-react-native 110 | 111 | EXTERNAL SOURCES: 112 | DoubleConversion: 113 | :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" 114 | Folly: 115 | :podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec" 116 | glog: 117 | :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" 118 | React: 119 | :path: "../node_modules/react-native/" 120 | React-Core: 121 | :path: "../node_modules/react-native/React" 122 | React-cxxreact: 123 | :path: "../node_modules/react-native/ReactCommon/cxxreact" 124 | React-DevSupport: 125 | :path: "../node_modules/react-native/React" 126 | React-jsi: 127 | :path: "../node_modules/react-native/ReactCommon/jsi" 128 | React-jsiexecutor: 129 | :path: "../node_modules/react-native/ReactCommon/jsiexecutor" 130 | React-jsinspector: 131 | :path: "../node_modules/react-native/ReactCommon/jsinspector" 132 | React-RCTActionSheet: 133 | :path: "../node_modules/react-native/Libraries/ActionSheetIOS" 134 | React-RCTAnimation: 135 | :path: "../node_modules/react-native/Libraries/NativeAnimation" 136 | React-RCTBlob: 137 | :path: "../node_modules/react-native/Libraries/Blob" 138 | React-RCTImage: 139 | :path: "../node_modules/react-native/Libraries/Image" 140 | React-RCTLinking: 141 | :path: "../node_modules/react-native/Libraries/LinkingIOS" 142 | React-RCTNetwork: 143 | :path: "../node_modules/react-native/Libraries/Network" 144 | React-RCTSettings: 145 | :path: "../node_modules/react-native/Libraries/Settings" 146 | React-RCTText: 147 | :path: "../node_modules/react-native/Libraries/Text" 148 | React-RCTVibration: 149 | :path: "../node_modules/react-native/Libraries/Vibration" 150 | React-RCTWebSocket: 151 | :path: "../node_modules/react-native/Libraries/WebSocket" 152 | yoga: 153 | :path: "../node_modules/react-native/ReactCommon/yoga" 154 | 155 | SPEC CHECKSUMS: 156 | boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c 157 | DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 158 | Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 159 | glog: 1f3da668190260b06b429bb211bfbee5cd790c28 160 | React: 53c53c4d99097af47cf60594b8706b4e3321e722 161 | React-Core: ba421f6b4f4cbe2fb17c0b6fc675f87622e78a64 162 | React-cxxreact: 8384287780c4999351ad9b6e7a149d9ed10a2395 163 | React-DevSupport: 197fb409737cff2c4f9986e77c220d7452cb9f9f 164 | React-jsi: 4d8c9efb6312a9725b18d6fc818ffc103f60fec2 165 | React-jsiexecutor: 90ad2f9db09513fc763bc757fdc3c4ff8bde2a30 166 | React-jsinspector: e08662d1bf5b129a3d556eb9ea343a3f40353ae4 167 | React-RCTActionSheet: b0f1ea83f4bf75fb966eae9bfc47b78c8d3efd90 168 | React-RCTAnimation: 359ba1b5690b1e87cc173558a78e82d35919333e 169 | React-RCTBlob: 5e2b55f76e9a1c7ae52b826923502ddc3238df24 170 | React-RCTImage: f5f1c50922164e89bdda67bcd0153952a5cfe719 171 | React-RCTLinking: d0ecbd791e9ddddc41fa1f66b0255de90e8ee1e9 172 | React-RCTNetwork: e26946300b0ab7bb6c4a6348090e93fa21f33a9d 173 | React-RCTSettings: d0d37cb521b7470c998595a44f05847777cc3f42 174 | React-RCTText: b074d89033583d4f2eb5faf7ea2db3a13c7553a2 175 | React-RCTVibration: 2105b2e0e2b66a6408fc69a46c8a7fb5b2fdade0 176 | React-RCTWebSocket: cd932a16b7214898b6b7f788c8bddb3637246ac4 177 | yoga: 312528f5bbbba37b4dcea5ef00e8b4033fdd9411 178 | 179 | PODFILE CHECKSUM: 5713161495618484968023c22fd8215ddef07aa1 180 | 181 | COCOAPODS: 1.7.5 182 | -------------------------------------------------------------------------------- /examples/react-native/metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: false, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /examples/react-native/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ConnectedReactRouter", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "react-native start", 7 | "test": "jest", 8 | "lint": "eslint ." 9 | }, 10 | "dependencies": { 11 | "connected-react-router": "^6.5.2", 12 | "react": "16.8.6", 13 | "react-native": "0.62.3", 14 | "react-redux": "^7.1.1", 15 | "react-router-native": "^5.0.1", 16 | "redux": "^4.0.4" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.6.0", 20 | "@babel/runtime": "^7.6.0", 21 | "@react-native-community/eslint-config": "^0.0.5", 22 | "babel-jest": "^24.9.0", 23 | "eslint": "^6.3.0", 24 | "jest": "^24.9.0", 25 | "metro-react-native-babel-preset": "^0.56.0", 26 | "react-test-renderer": "16.8.6" 27 | }, 28 | "jest": { 29 | "preset": "react-native" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/react-native/src/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | * @flow 7 | */ 8 | 9 | import React from 'react'; 10 | import {SafeAreaView, StyleSheet, View} from 'react-native'; 11 | import {Provider} from 'react-redux'; 12 | import Router from './routes'; 13 | import configureStore from './configureStore'; 14 | 15 | const {history, store} = configureStore(); 16 | 17 | const App = () => { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | const styles = StyleSheet.create({ 30 | container: { 31 | padding: 10, 32 | }, 33 | }); 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /examples/react-native/src/configureStore.js: -------------------------------------------------------------------------------- 1 | import {createMemoryHistory} from 'history'; 2 | import {createStore, combineReducers} from 'redux'; 3 | import {connectRouter} from 'connected-react-router'; 4 | 5 | const initialState = {router: createMemoryHistory()}; 6 | 7 | const configureStore = () => { 8 | const history = initialState.router; 9 | const store = createStore( 10 | combineReducers({ 11 | router: connectRouter(history), 12 | }), 13 | initialState, 14 | ); 15 | 16 | return {history, store}; 17 | }; 18 | 19 | export default configureStore; 20 | -------------------------------------------------------------------------------- /examples/react-native/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ConnectedRouter} from 'connected-react-router'; 3 | import {NativeRouter, Route} from 'react-router-native'; 4 | import Home from '../screens/Home'; 5 | import Account from '../screens/Account'; 6 | 7 | const Router = ({history}) => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | 16 | export default Router; 17 | -------------------------------------------------------------------------------- /examples/react-native/src/screens/Account.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Text, StyleSheet, Button} from 'react-native'; 3 | import {getUrlParams} from '../utils/url'; 4 | 5 | const Account = ({history}) => { 6 | const urlParams = getUrlParams(history.location.search); 7 | const {accountId} = urlParams; 8 | return ( 9 | 10 | Account {accountId} 11 | 12 | 13 |
14 | ) 15 | 16 | interface StateProps { 17 | count: number 18 | } 19 | 20 | interface DispatchProps { 21 | increment: () => void 22 | decrement: () => void 23 | } 24 | 25 | const mapStateToProps = (state: State) => ({ 26 | count: state.count, 27 | }) 28 | 29 | const mapDispatchToProps = (dispatch: Dispatch) => ({ 30 | increment: () => dispatch(increment()), 31 | decrement: () => dispatch(decrement()), 32 | }) 33 | 34 | export default connect>(mapStateToProps, mapDispatchToProps)(Counter) 35 | -------------------------------------------------------------------------------- /examples/typescript/src/components/Hello.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HelloChild from './HelloChild' 3 | 4 | const Hello = () => ( 5 |
6 |
Hello
7 | 8 |
9 | ) 10 | 11 | export default Hello 12 | -------------------------------------------------------------------------------- /examples/typescript/src/components/HelloChild.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { Link } from 'react-router-dom' 4 | import { State } from '../reducers' 5 | 6 | interface HelloChildProps { 7 | pathname: string 8 | search: string 9 | hash: string 10 | } 11 | 12 | const HelloChild = ({ pathname, search, hash }: HelloChildProps) => ( 13 |
14 | Hello-Child 15 |
    16 |
  • with query string
  • 17 |
  • with hash
  • 18 |
19 |
20 | pathname: {pathname} 21 |
22 |
23 | search: {search} 24 |
25 |
26 | hash: {hash} 27 |
28 |
29 | ) 30 | 31 | const mapStateToProps = (state: State) => ({ 32 | pathname: state.router.location.pathname, 33 | search: state.router.location.search, 34 | hash: state.router.location.hash, 35 | }) 36 | 37 | export default connect(mapStateToProps)(HelloChild) 38 | -------------------------------------------------------------------------------- /examples/typescript/src/components/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Home = () => ( 4 |
5 | Home 6 |
7 | ) 8 | 9 | export default Home 10 | -------------------------------------------------------------------------------- /examples/typescript/src/components/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | const NavBar = () => ( 5 |
6 |
Home Hello Counter
7 |
8 | ) 9 | 10 | export default NavBar 11 | -------------------------------------------------------------------------------- /examples/typescript/src/components/NoMatch.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const NoMatch = () => ( 4 |
5 | No Match 6 |
7 | ) 8 | 9 | export default NoMatch 10 | -------------------------------------------------------------------------------- /examples/typescript/src/configureStore.tsx: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history' 2 | import { applyMiddleware, compose, createStore } from 'redux' 3 | import { routerMiddleware } from 'connected-react-router' 4 | import createRootReducer from './reducers' 5 | 6 | export const history = createBrowserHistory() 7 | 8 | export default function configureStore(preloadedState?: any) { 9 | const composeEnhancer: typeof compose = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose 10 | const store = createStore( 11 | createRootReducer(history), 12 | preloadedState, 13 | composeEnhancer( 14 | applyMiddleware( 15 | routerMiddleware(history), 16 | ), 17 | ), 18 | ) 19 | 20 | // Hot reloading 21 | if (module.hot) { 22 | // Enable Webpack hot module replacement for reducers 23 | module.hot.accept('./reducers', () => { 24 | store.replaceReducer(createRootReducer(history)); 25 | }); 26 | } 27 | 28 | return store 29 | } 30 | -------------------------------------------------------------------------------- /examples/typescript/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { AppContainer } from 'react-hot-loader' 2 | import { Provider } from 'react-redux' 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import App from './App' 6 | import configureStore, { history } from './configureStore' 7 | 8 | const store = configureStore() 9 | const render = () => { 10 | ReactDOM.render( 11 | 12 | 13 | 14 | 15 | , 16 | document.getElementById('react-root') 17 | ) 18 | } 19 | 20 | render() 21 | 22 | // Hot reloading 23 | if (module.hot) { 24 | // Reload components 25 | module.hot.accept('./App', () => { 26 | render() 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /examples/typescript/src/reducers/counter.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "redux"; 2 | 3 | const counterReducer = (state = 0, action: Action) => { 4 | switch (action.type) { 5 | case 'INCREMENT': 6 | return state + 1 7 | case 'DECREMENT': 8 | return state - 1 9 | default: 10 | return state 11 | } 12 | } 13 | 14 | export default counterReducer 15 | -------------------------------------------------------------------------------- /examples/typescript/src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { History } from 'history' 3 | import { RouterState, connectRouter } from 'connected-react-router' 4 | import counterReducer from './counter' 5 | 6 | const rootReducer = (history: History) => combineReducers({ 7 | count: counterReducer, 8 | router: connectRouter(history) 9 | }) 10 | 11 | export interface State { 12 | count: number 13 | router: RouterState 14 | } 15 | 16 | export default rootReducer 17 | -------------------------------------------------------------------------------- /examples/typescript/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route, Switch } from 'react-router' 3 | import Home from '../components/Home' 4 | import Hello from '../components/Hello' 5 | import Counter from '../components/Counter' 6 | import NoMatch from '../components/NoMatch' 7 | import NavBar from '../components/NavBar' 8 | 9 | const routes = ( 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | ) 20 | 21 | export default routes 22 | -------------------------------------------------------------------------------- /examples/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | 7 | 8 | "jsx": "react", 9 | "lib": [ 10 | "dom", 11 | "es2017" 12 | ], 13 | 14 | "alwaysStrict": true, 15 | "sourceMap": true, 16 | 17 | "forceConsistentCasingInFileNames": true, 18 | "noImplicitAny": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "strictNullChecks": true, 23 | 24 | "allowSyntheticDefaultImports": true 25 | }, 26 | 27 | "exclude": [ 28 | "node_modules", 29 | "dist" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /examples/typescript/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | 4 | module.exports = { 5 | mode: 'development', 6 | devtool: 'eval-source-map', 7 | entry: [ 8 | 'react-hot-loader/patch', 9 | path.resolve('src/index.tsx'), 10 | ], 11 | resolve: { 12 | extensions: ['.js', '.ts', '.tsx'], 13 | }, 14 | devServer: { 15 | historyApiFallback: true, 16 | hot: true, 17 | }, 18 | output: { 19 | filename: 'bundle.js', 20 | path: path.resolve(__dirname, 'dist'), 21 | publicPath: '/dist/', 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.tsx?/, 27 | loader: 'ts-loader' 28 | } 29 | ], 30 | }, 31 | plugins: [ 32 | new webpack.HotModuleReplacementPlugin(), 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /immutable.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module 'connected-react-router/immutable' { 4 | 5 | export * from 'connected-react-router'; 6 | 7 | } -------------------------------------------------------------------------------- /immutable.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/immutable') 2 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'connected-react-router' { 2 | import * as React from 'react'; 3 | import { Middleware, Reducer } from 'redux'; 4 | import { ReactReduxContextValue } from 'react-redux'; 5 | import { match, matchPath } from 'react-router'; 6 | import { 7 | Action, 8 | Hash, 9 | History, 10 | Path, 11 | Location, 12 | LocationState, 13 | LocationDescriptorObject, 14 | Search 15 | } from 'history'; 16 | 17 | type PathParam = Parameters[1]; 18 | 19 | interface ConnectedRouterProps { 20 | history: History; 21 | context?: React.Context; 22 | noInitialPop?: boolean; 23 | noTimeTravelDebugging?: boolean; 24 | omitRouter?: boolean; 25 | children?: React.ReactNode; 26 | } 27 | 28 | export type RouterActionType = Action; 29 | 30 | export interface RouterLocation extends Location { 31 | query: Record 32 | } 33 | 34 | export interface RouterState { 35 | location: RouterLocation 36 | action: RouterActionType 37 | } 38 | 39 | export const LOCATION_CHANGE: '@@router/LOCATION_CHANGE'; 40 | export const CALL_HISTORY_METHOD: '@@router/CALL_HISTORY_METHOD'; 41 | 42 | export interface LocationChangeAction { 43 | type: typeof LOCATION_CHANGE; 44 | payload: LocationChangePayload; 45 | } 46 | 47 | export interface LocationChangePayload extends RouterState { 48 | isFirstRendering: boolean; 49 | } 50 | 51 | export interface CallHistoryMethodAction { 52 | type: typeof CALL_HISTORY_METHOD; 53 | payload: LocationActionPayload; 54 | } 55 | 56 | export interface RouterRootState { 57 | router: RouterState; 58 | } 59 | 60 | export type matchSelectorFn< 61 | S extends RouterRootState, Params extends { [K in keyof Params]?: string } 62 | > = (state: S) => match | null; 63 | 64 | export type RouterAction = LocationChangeAction | CallHistoryMethodAction; 65 | 66 | export function push(path: Path, state?: S): CallHistoryMethodAction<[ Path, S? ]>; 67 | export function push(location: LocationDescriptorObject): CallHistoryMethodAction<[ LocationDescriptorObject ]>; 68 | export function replace(path: Path, state?: S): CallHistoryMethodAction<[ Path, S? ]>; 69 | export function replace(location: LocationDescriptorObject): CallHistoryMethodAction<[ LocationDescriptorObject ]>; 70 | export function go(n: number): CallHistoryMethodAction<[ number ]>; 71 | export function goBack(): CallHistoryMethodAction<[]>; 72 | export function goForward(): CallHistoryMethodAction<[]>; 73 | export function getRouter, LS = LocationState>(state: S): RouterState; 74 | export function getAction(state: S): RouterActionType; 75 | export function getHash(state: S): Hash; 76 | export function getLocation, LS = LocationState>(state: S): RouterLocation; 77 | export function getSearch(state: S): Search; 78 | export function createMatchSelector< 79 | S extends RouterRootState, Params extends { [K in keyof Params]?: string } 80 | >(path: PathParam): matchSelectorFn; 81 | export function onLocationChanged(location: Location, action: RouterActionType, isFirstRendering?: boolean) 82 | : LocationChangeAction; 83 | 84 | export type Push = typeof push; 85 | export type Replace = typeof replace; 86 | export type Go = typeof go; 87 | export type GoBack = typeof goBack; 88 | export type GoForward = typeof goForward; 89 | 90 | export const routerActions: { 91 | push: Push; 92 | replace: Replace; 93 | go: Go; 94 | goBack: GoBack; 95 | goForward: GoForward; 96 | }; 97 | 98 | export interface LocationActionPayload { 99 | method: string; 100 | args?: A; 101 | } 102 | 103 | export class ConnectedRouter extends React.Component< 104 | ConnectedRouterProps, 105 | {} 106 | > {} 107 | 108 | export function connectRouter(history: History) 109 | : Reducer> 110 | 111 | export function routerMiddleware(history: History): Middleware; 112 | } 113 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connected-react-router", 3 | "version": "6.9.3", 4 | "description": "A Redux binding for React Router v4 and v5", 5 | "main": "lib/index.js", 6 | "module": "esm/index.js", 7 | "types": "./index.d.ts", 8 | "sideEffects": false, 9 | "author": "Supasate Choochaisri", 10 | "license": "MIT", 11 | "files": [ 12 | "*.md", 13 | "*.js", 14 | "*.ts", 15 | "esm", 16 | "lib", 17 | "umd" 18 | ], 19 | "scripts": { 20 | "build:esm": "cross-env BABEL_ENV=esm babel src --out-dir esm", 21 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 22 | "build:umd": "cross-env NODE_ENV=development webpack src/index.js --output umd/ConnectedReactRouter.js", 23 | "build:umd:min": "cross-env NODE_ENV=production webpack -p src/index.js --output umd/ConnectedReactRouter.min.js", 24 | "build": "npm run build:esm & npm run build:commonjs & npm run build:umd & npm run build:umd:min", 25 | "lint": "eslint .", 26 | "test": "jest --config ./.jestrc.json", 27 | "test:watch": "npm run test -- --watch", 28 | "posttest": "npm run lint" 29 | }, 30 | "dependencies": { 31 | "prop-types": "^15.7.2", 32 | "lodash.isequalwith": "^4.4.0" 33 | }, 34 | "peerDependencies": { 35 | "history": "^4.7.2", 36 | "react": "^16.4.0 || ^17.0.0", 37 | "react-redux": "^6.0.0 || ^7.1.0", 38 | "react-router": "^4.3.1 || ^5.0.0", 39 | "redux": "^3.6.0 || ^4.0.0" 40 | }, 41 | "optionalDependencies": { 42 | "immutable": "^3.8.1 || ^4.0.0", 43 | "seamless-immutable": "^7.1.3" 44 | }, 45 | "devDependencies": { 46 | "@babel/cli": "^7.1.5", 47 | "@babel/core": "^7.1.5", 48 | "@babel/preset-env": "^7.1.5", 49 | "@babel/preset-react": "^7.0.0", 50 | "@types/history": "^4.7.3", 51 | "@types/react": "*", 52 | "@types/react-redux": "^7.0.0", 53 | "@types/react-router": "^4.4.3", 54 | "babel-core": "7.0.0-bridge.0", 55 | "babel-eslint": "^10.0.1", 56 | "babel-jest": "^23.6.0", 57 | "babel-loader": "^8.0.4", 58 | "babel-plugin-rewire": "^1.2.0", 59 | "babel-template": "^6.2.0", 60 | "babel-types": "^6.2.0", 61 | "cross-env": "^6.0.3", 62 | "enzyme": "3.1.1", 63 | "enzyme-adapter-react-16": "^1.0.4", 64 | "eslint": "^5.9.0", 65 | "eslint-plugin-import": "^2.14.0", 66 | "eslint-plugin-react": "^7.11.1", 67 | "immutable": "^4.0.0", 68 | "jest": "^24.3.1", 69 | "raf": "^3.4.0", 70 | "react": "^16.4.0", 71 | "react-dom": "^16.4.0", 72 | "react-redux": "^6.0.0 || ^7.1.0", 73 | "react-router": "^4.3.1 || ^5.0.0", 74 | "react-test-renderer": "^16.4.0", 75 | "redux": "^4.0.0", 76 | "redux-devtools": "^3.4.0", 77 | "redux-immutable": "^3.0.11", 78 | "redux-mock-store": "^1.2.1", 79 | "redux-seamless-immutable": "^0.4.0", 80 | "rewire": "^2.5.2", 81 | "seamless-immutable": "^7.1.3", 82 | "webpack": "^4.41.2", 83 | "webpack-cli": "^3.3.10" 84 | }, 85 | "repository": { 86 | "type": "git", 87 | "url": "https://github.com/supasate/connected-react-router.git" 88 | }, 89 | "bugs": { 90 | "url": "https://github.com/supasate/connected-react-router/issues" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /seamless-immutable.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module 'connected-react-router/seamless-immutable' { 4 | 5 | export * from 'connected-react-router'; 6 | } 7 | -------------------------------------------------------------------------------- /seamless-immutable.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/seamless-immutable') 2 | -------------------------------------------------------------------------------- /src/ConnectedRouter.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect, ReactReduxContext } from 'react-redux' 4 | import { Router } from 'react-router' 5 | import isEqualWith from 'lodash.isequalwith' 6 | import { onLocationChanged } from './actions' 7 | import createSelectors from './selectors' 8 | 9 | const createConnectedRouter = (structure) => { 10 | const { getLocation } = createSelectors(structure) 11 | /* 12 | * ConnectedRouter listens to a history object passed from props. 13 | * When history is changed, it dispatches action to redux store. 14 | * Then, store will pass props to component to render. 15 | * This creates uni-directional flow from history->store->router->components. 16 | */ 17 | 18 | class ConnectedRouter extends PureComponent { 19 | constructor(props) { 20 | super(props) 21 | 22 | const { store, history, onLocationChanged, stateCompareFunction } = props 23 | 24 | this.inTimeTravelling = false 25 | 26 | // Subscribe to store changes to check if we are in time travelling 27 | this.unsubscribe = store.subscribe(() => { 28 | // Allow time travel debugging compatibility to be turned off 29 | // as the detection for this (below) is error prone in apps where the 30 | // store may be unmounted, a navigation occurs, and then the store is re-mounted 31 | // during the app's lifetime. Detection could be much improved if Redux DevTools 32 | // simply set a global variable like `REDUX_DEVTOOLS_IS_TIME_TRAVELLING=true`. 33 | const isTimeTravelDebuggingAllowed = !props.noTimeTravelDebugging 34 | 35 | // Extract store's location 36 | const { 37 | pathname: pathnameInStore, 38 | search: searchInStore, 39 | hash: hashInStore, 40 | state: stateInStore, 41 | } = getLocation(store.getState()) 42 | // Extract history's location 43 | const { 44 | pathname: pathnameInHistory, 45 | search: searchInHistory, 46 | hash: hashInHistory, 47 | state: stateInHistory, 48 | } = history.location 49 | 50 | // If we do time travelling, the location in store is changed but location in history is not changed 51 | if ( 52 | isTimeTravelDebuggingAllowed && 53 | props.history.action === 'PUSH' && 54 | (pathnameInHistory !== pathnameInStore || 55 | searchInHistory !== searchInStore || 56 | hashInHistory !== hashInStore || 57 | !isEqualWith(stateInStore, stateInHistory, stateCompareFunction)) 58 | ) { 59 | this.inTimeTravelling = true 60 | // Update history's location to match store's location 61 | history.push({ 62 | pathname: pathnameInStore, 63 | search: searchInStore, 64 | hash: hashInStore, 65 | state: stateInStore, 66 | }) 67 | } 68 | }) 69 | 70 | const handleLocationChange = (location, action, isFirstRendering = false) => { 71 | // Dispatch onLocationChanged except when we're in time travelling 72 | if (!this.inTimeTravelling) { 73 | onLocationChanged(location, action, isFirstRendering) 74 | } else { 75 | this.inTimeTravelling = false 76 | } 77 | } 78 | 79 | // Listen to history changes 80 | this.unlisten = history.listen(handleLocationChange) 81 | 82 | if (!props.noInitialPop) { 83 | // Dispatch a location change action for the initial location. 84 | // This makes it backward-compatible with react-router-redux. 85 | // But, we add `isFirstRendering` to `true` to prevent double-rendering. 86 | handleLocationChange(history.location, history.action, true) 87 | } 88 | } 89 | 90 | componentWillUnmount() { 91 | this.unlisten() 92 | this.unsubscribe() 93 | } 94 | 95 | render() { 96 | const { omitRouter, history, children } = this.props 97 | 98 | // The `omitRouter` option is available for applications that must 99 | // have a Router instance higher in the component tree but still desire 100 | // to use connected-react-router for its Redux integration. 101 | 102 | if (omitRouter) { 103 | return <>{ children } 104 | } 105 | 106 | return ( 107 | 108 | { children } 109 | 110 | ) 111 | } 112 | } 113 | 114 | ConnectedRouter.propTypes = { 115 | store: PropTypes.shape({ 116 | getState: PropTypes.func.isRequired, 117 | subscribe: PropTypes.func.isRequired, 118 | }).isRequired, 119 | history: PropTypes.shape({ 120 | action: PropTypes.string.isRequired, 121 | listen: PropTypes.func.isRequired, 122 | location: PropTypes.object.isRequired, 123 | push: PropTypes.func.isRequired, 124 | }).isRequired, 125 | basename: PropTypes.string, 126 | children: PropTypes.oneOfType([ PropTypes.func, PropTypes.node ]), 127 | onLocationChanged: PropTypes.func.isRequired, 128 | noInitialPop: PropTypes.bool, 129 | noTimeTravelDebugging: PropTypes.bool, 130 | stateCompareFunction: PropTypes.func, 131 | omitRouter: PropTypes.bool, 132 | } 133 | 134 | const mapDispatchToProps = dispatch => ({ 135 | onLocationChanged: (location, action, isFirstRendering) => dispatch(onLocationChanged(location, action, isFirstRendering)) 136 | }) 137 | 138 | const ConnectedRouterWithContext = props => { 139 | const Context = props.context || ReactReduxContext 140 | 141 | if (Context == null) { 142 | throw 'Please upgrade to react-redux v6' 143 | } 144 | 145 | return ( 146 | 147 | {({ store }) => } 148 | 149 | ) 150 | } 151 | 152 | ConnectedRouterWithContext.propTypes = { 153 | context: PropTypes.object, 154 | } 155 | 156 | return connect(null, mapDispatchToProps)(ConnectedRouterWithContext) 157 | } 158 | 159 | export default createConnectedRouter 160 | -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This action type will be dispatched when your history 3 | * receives a location change. 4 | */ 5 | export const LOCATION_CHANGE = '@@router/LOCATION_CHANGE' 6 | 7 | export const onLocationChanged = (location, action, isFirstRendering = false) => ({ 8 | type: LOCATION_CHANGE, 9 | payload: { 10 | location, 11 | action, 12 | isFirstRendering, 13 | } 14 | }) 15 | 16 | /** 17 | * This action type will be dispatched by the history actions below. 18 | * If you're writing a middleware to watch for navigation events, be sure to 19 | * look for actions of this type. 20 | */ 21 | export const CALL_HISTORY_METHOD = '@@router/CALL_HISTORY_METHOD' 22 | 23 | const updateLocation = (method) => { 24 | return (...args) => ({ 25 | type: CALL_HISTORY_METHOD, 26 | payload: { 27 | method, 28 | args 29 | } 30 | }) 31 | } 32 | 33 | /** 34 | * These actions correspond to the history API. 35 | * The associated routerMiddleware will capture these events before they get to 36 | * your reducer and reissue them as the matching function on your history. 37 | */ 38 | export const push = updateLocation('push') 39 | export const replace = updateLocation('replace') 40 | export const go = updateLocation('go') 41 | export const goBack = updateLocation('goBack') 42 | export const goForward = updateLocation('goForward') 43 | 44 | export const routerActions = { push, replace, go, goBack, goForward } 45 | -------------------------------------------------------------------------------- /src/immutable.js: -------------------------------------------------------------------------------- 1 | import createConnectedRouter from "./ConnectedRouter" 2 | import createConnectRouter from "./reducer" 3 | import createSelectors from "./selectors" 4 | import immutableStructure from './structure/immutable' 5 | 6 | export { LOCATION_CHANGE, CALL_HISTORY_METHOD, onLocationChanged, push, replace, go, goBack, goForward, routerActions } from "./actions" 7 | export { default as routerMiddleware } from "./middleware" 8 | 9 | export const ConnectedRouter = /*#__PURE__*/ createConnectedRouter(immutableStructure) 10 | export const connectRouter = /*#__PURE__*/ createConnectRouter(immutableStructure) 11 | export const { getLocation, getAction, getHash, getRouter, getSearch, createMatchSelector } = /*#__PURE__*/ createSelectors(immutableStructure) 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import createConnectedRouter from "./ConnectedRouter" 2 | import createConnectRouter from "./reducer" 3 | import createSelectors from "./selectors" 4 | import plainStructure from "./structure/plain" 5 | 6 | export { LOCATION_CHANGE, CALL_HISTORY_METHOD, onLocationChanged, push, replace, go, goBack, goForward, routerActions } from "./actions" 7 | export { default as routerMiddleware } from "./middleware" 8 | 9 | export const ConnectedRouter = /*#__PURE__*/ createConnectedRouter(plainStructure) 10 | export const connectRouter = /*#__PURE__*/ createConnectRouter(plainStructure) 11 | export const { getLocation, getAction, getHash, getRouter, getSearch, createMatchSelector } = /*#__PURE__*/ createSelectors(plainStructure) 12 | -------------------------------------------------------------------------------- /src/middleware.js: -------------------------------------------------------------------------------- 1 | import { CALL_HISTORY_METHOD } from './actions' 2 | 3 | /** 4 | * This middleware captures CALL_HISTORY_METHOD actions to redirect to the 5 | * provided history object. This will prevent these actions from reaching your 6 | * reducer or any middleware that comes after this one. 7 | */ 8 | const routerMiddleware = (history) => store => next => action => { // eslint-disable-line no-unused-vars 9 | if (action.type !== CALL_HISTORY_METHOD) { 10 | return next(action) 11 | } 12 | 13 | const { payload: { method, args } } = action 14 | history[method](...args) 15 | } 16 | 17 | 18 | export default routerMiddleware 19 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | import { LOCATION_CHANGE } from './actions' 2 | 3 | /** 4 | * Adds query to location. 5 | * Utilises the search prop of location to construct query. 6 | */ 7 | const injectQuery = (location) => { 8 | if (location && location.query) { 9 | // Don't inject query if it already exists in history 10 | return location 11 | } 12 | 13 | const searchQuery = location && location.search 14 | 15 | if (typeof searchQuery !== 'string' || searchQuery.length === 0) { 16 | return { 17 | ...location, 18 | query: {} 19 | } 20 | } 21 | 22 | // Ignore the `?` part of the search string e.g. ?username=codejockie 23 | const search = searchQuery.substring(1) 24 | // Split the query string on `&` e.g. ?username=codejockie&name=Kennedy 25 | const queries = search.split('&') 26 | // Contruct query 27 | const query = queries.reduce((acc, currentQuery) => { 28 | // Split on `=`, to get key and value 29 | const [queryKey, queryValue] = currentQuery.split('=') 30 | return { 31 | ...acc, 32 | [queryKey]: queryValue 33 | } 34 | }, {}) 35 | 36 | return { 37 | ...location, 38 | query 39 | } 40 | } 41 | 42 | const createConnectRouter = (structure) => { 43 | const { 44 | fromJS, 45 | merge, 46 | } = structure 47 | 48 | const createRouterReducer = (history) => { 49 | const initialRouterState = fromJS({ 50 | location: injectQuery(history.location), 51 | action: history.action, 52 | }) 53 | 54 | /* 55 | * This reducer will update the state with the most recent location history 56 | * has transitioned to. 57 | */ 58 | return (state = initialRouterState, { type, payload } = {}) => { 59 | if (type === LOCATION_CHANGE) { 60 | const { location, action, isFirstRendering } = payload 61 | // Don't update the state ref for the first rendering 62 | // to prevent the double-rendering issue on initilization 63 | return isFirstRendering 64 | ? state 65 | : merge(state, { location: fromJS(injectQuery(location)), action }) 66 | } 67 | 68 | return state 69 | } 70 | } 71 | 72 | return createRouterReducer 73 | } 74 | 75 | export default createConnectRouter 76 | -------------------------------------------------------------------------------- /src/seamless-immutable.js: -------------------------------------------------------------------------------- 1 | import createConnectedRouter from "./ConnectedRouter" 2 | import createConnectRouter from "./reducer" 3 | import createSelectors from "./selectors" 4 | import immutableStructure from './structure/seamless-immutable' 5 | 6 | export { LOCATION_CHANGE, CALL_HISTORY_METHOD, onLocationChanged, push, replace, go, goBack, goForward, routerActions } from "./actions" 7 | export { default as routerMiddleware } from "./middleware" 8 | 9 | export const ConnectedRouter = /*#__PURE__*/ createConnectedRouter(immutableStructure) 10 | export const connectRouter = /*#__PURE__*/ createConnectRouter(immutableStructure) 11 | export const { getLocation, getAction, getHash, getRouter, getSearch, createMatchSelector } = /*#__PURE__*/ createSelectors(immutableStructure) 12 | -------------------------------------------------------------------------------- /src/selectors.js: -------------------------------------------------------------------------------- 1 | import { matchPath } from "react-router" 2 | 3 | const createSelectors = (structure) => { 4 | const { getIn, toJS } = structure 5 | 6 | const isRouter = (value) => value != null && 7 | typeof value === 'object' && 8 | getIn(value, ['location']) && 9 | getIn(value, ['action']) 10 | 11 | const getRouter = state => { 12 | const router = toJS(getIn(state, ['router'])) 13 | if (!isRouter(router)) { throw 'Could not find router reducer in state tree, it must be mounted under "router"' } 14 | return router 15 | } 16 | const getLocation = state => toJS(getIn(getRouter(state), ['location'])) 17 | const getAction = state => toJS(getIn(getRouter(state), ['action'])) 18 | const getSearch = state => toJS(getIn(getRouter(state), ['location', 'search'])) 19 | const getHash = state => toJS(getIn(getRouter(state), ['location', 'hash'])) 20 | 21 | // It only makes sense to recalculate the `matchPath` whenever the pathname 22 | // of the location changes. That's why `createMatchSelector` memoizes 23 | // the latest result based on the location's pathname. 24 | const createMatchSelector = path => { 25 | let lastPathname = null 26 | let lastMatch = null 27 | 28 | return state => { 29 | const { pathname } = getLocation(state) || {} 30 | if (pathname === lastPathname) { 31 | return lastMatch 32 | } 33 | lastPathname = pathname 34 | const match = matchPath(pathname, path) 35 | if ( 36 | !match 37 | || !lastMatch 38 | || match.url !== lastMatch.url 39 | // When URL matched for nested routes, URL is the same but isExact is not. 40 | || match.isExact !== lastMatch.isExact 41 | ) { 42 | lastMatch = match 43 | } 44 | 45 | return lastMatch 46 | } 47 | } 48 | 49 | return { 50 | getLocation, 51 | getAction, 52 | getRouter, 53 | getSearch, 54 | getHash, 55 | createMatchSelector, 56 | } 57 | } 58 | 59 | export default createSelectors 60 | -------------------------------------------------------------------------------- /src/structure/immutable/getIn.js: -------------------------------------------------------------------------------- 1 | /* Code from github.com/erikras/redux-form by Erik Rasmussen */ 2 | import { Iterable } from 'immutable' 3 | import plainGetIn from '../plain/getIn' 4 | 5 | const getIn = (state, path) => 6 | Iterable.isIterable(state) 7 | ? state.getIn(path) 8 | : plainGetIn(state, path) 9 | 10 | export default getIn 11 | -------------------------------------------------------------------------------- /src/structure/immutable/index.js: -------------------------------------------------------------------------------- 1 | import { Iterable, fromJS } from 'immutable' 2 | import getIn from './getIn' 3 | 4 | const structure = { 5 | fromJS: jsValue => fromJS(jsValue, (key, value) => 6 | Iterable.isIndexed(value) ? value.toList() : value.toMap()), 7 | getIn, 8 | merge: (state, payload) => state.merge(payload), 9 | toJS: value => Iterable.isIterable(value) ? value.toJS() : value, 10 | } 11 | 12 | export default structure 13 | -------------------------------------------------------------------------------- /src/structure/plain/getIn.js: -------------------------------------------------------------------------------- 1 | /* Code from github.com/erikras/redux-form by Erik Rasmussen */ 2 | 3 | const getIn = (state, path) => { 4 | if (!state) { 5 | return state 6 | } 7 | 8 | const length = path.length 9 | if (!length) { 10 | return undefined 11 | } 12 | 13 | let result = state 14 | for (let i = 0; i < length && !!result; ++i) { 15 | result = result[path[i]] 16 | } 17 | 18 | return result 19 | } 20 | 21 | export default getIn 22 | -------------------------------------------------------------------------------- /src/structure/plain/index.js: -------------------------------------------------------------------------------- 1 | import getIn from './getIn' 2 | 3 | const structure = { 4 | fromJS: value => value, 5 | getIn, 6 | merge: (state, payload) => ({ ...state, ...payload }), 7 | toJS: value => value, 8 | } 9 | 10 | export default structure 11 | -------------------------------------------------------------------------------- /src/structure/seamless-immutable/index.js: -------------------------------------------------------------------------------- 1 | import SeamlessImmutable from 'seamless-immutable' 2 | import getIn from '../plain/getIn' 3 | 4 | const { static: Immutable } = SeamlessImmutable 5 | 6 | const structure = { 7 | fromJS: value => Immutable.from(value), 8 | getIn, 9 | merge: (state, payload) => Immutable.merge(state, payload), 10 | toJS: value => Immutable.asMutable(value) 11 | } 12 | 13 | export default structure 14 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } -------------------------------------------------------------------------------- /test/actions.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOCATION_CHANGE, 3 | CALL_HISTORY_METHOD, 4 | onLocationChanged, 5 | push, 6 | replace, 7 | go, 8 | goBack, 9 | goForward, 10 | } from '../src/actions' 11 | 12 | describe('Actions', () => { 13 | it('returns correct action when calling onLocationChanged()', () => { 14 | const actualAction = onLocationChanged({ pathname: '/', search: '', hash: '' }, 'POP') 15 | const expectedAction = { 16 | type: LOCATION_CHANGE, 17 | payload: { 18 | location: { 19 | pathname: '/', 20 | search: '', 21 | hash: '', 22 | }, 23 | action: 'POP', 24 | isFirstRendering: false, 25 | }, 26 | } 27 | expect(actualAction).toEqual(expectedAction) 28 | }) 29 | 30 | it('returns correct action when calling onLocationChanged() for the first rendering', () => { 31 | const actualAction = onLocationChanged({ pathname: '/', search: '', hash: '' }, 'POP', true) 32 | const expectedAction = { 33 | type: LOCATION_CHANGE, 34 | payload: { 35 | location: { 36 | pathname: '/', 37 | search: '', 38 | hash: '', 39 | }, 40 | action: 'POP', 41 | isFirstRendering: true, 42 | }, 43 | } 44 | expect(actualAction).toEqual(expectedAction) 45 | }) 46 | 47 | it('returns correct action when calling push()', () => { 48 | const actualAction = push('/path/to/somewhere') 49 | const expectedAction = { 50 | type: CALL_HISTORY_METHOD, 51 | payload: { 52 | method: 'push', 53 | args: ['/path/to/somewhere'], 54 | }, 55 | } 56 | expect(actualAction).toEqual(expectedAction) 57 | }) 58 | 59 | it('returns correct action when calling replace()', () => { 60 | const actualAction = replace('/path/to/somewhere') 61 | const expectedAction = { 62 | type: CALL_HISTORY_METHOD, 63 | payload: { 64 | method: 'replace', 65 | args: ['/path/to/somewhere'], 66 | }, 67 | } 68 | expect(actualAction).toEqual(expectedAction) 69 | }) 70 | 71 | it('returns correct action when calling go()', () => { 72 | const actualAction = go(2) 73 | const expectedAction = { 74 | type: CALL_HISTORY_METHOD, 75 | payload: { 76 | method: 'go', 77 | args: [2], 78 | }, 79 | } 80 | expect(actualAction).toEqual(expectedAction) 81 | }) 82 | 83 | it('returns correct action when calling goBack()', () => { 84 | const actualAction = goBack() 85 | const expectedAction = { 86 | type: CALL_HISTORY_METHOD, 87 | payload: { 88 | method: 'goBack', 89 | args: [], 90 | }, 91 | } 92 | expect(actualAction).toEqual(expectedAction) 93 | }) 94 | 95 | it('returns correct action when calling goForward()', () => { 96 | const actualAction = goForward() 97 | const expectedAction = { 98 | type: CALL_HISTORY_METHOD, 99 | payload: { 100 | method: 'goForward', 101 | args: [], 102 | }, 103 | } 104 | expect(actualAction).toEqual(expectedAction) 105 | }) 106 | }) 107 | -------------------------------------------------------------------------------- /test/middleware.test.js: -------------------------------------------------------------------------------- 1 | import configureStore from 'redux-mock-store' 2 | import routerMiddleware from '../src/middleware' 3 | import { CALL_HISTORY_METHOD } from '../src/actions' 4 | 5 | describe('Middleware', () => { 6 | it('calls history method based on action payload values', () => { 7 | const history = { 8 | location: {}, 9 | action: 'POP', 10 | push: jest.fn(), 11 | replace: jest.fn(), 12 | go: jest.fn(), 13 | goBack: jest.fn(), 14 | goForward: jest.fn(), 15 | } 16 | const middlewares = [routerMiddleware(history)] 17 | const mockStore = configureStore(middlewares) 18 | const store = mockStore({}) 19 | 20 | // push 21 | store.dispatch({ 22 | type: CALL_HISTORY_METHOD, 23 | payload: { 24 | method: 'push', 25 | args: ['/path/to/somewhere'], 26 | } 27 | }) 28 | expect(history.push).toBeCalledWith('/path/to/somewhere') 29 | 30 | // replace 31 | store.dispatch({ 32 | type: CALL_HISTORY_METHOD, 33 | payload: { 34 | method: 'replace', 35 | args: ['/path/to/somewhere'], 36 | } 37 | }) 38 | expect(history.replace).toBeCalledWith('/path/to/somewhere') 39 | 40 | // go 41 | store.dispatch({ 42 | type: CALL_HISTORY_METHOD, 43 | payload: { 44 | method: 'go', 45 | args: [5], 46 | } 47 | }) 48 | expect(history.go).toBeCalledWith(5) 49 | 50 | // goBack 51 | store.dispatch({ 52 | type: CALL_HISTORY_METHOD, 53 | payload: { 54 | method: 'goBack', 55 | args: [], 56 | } 57 | }) 58 | expect(history.goBack).toBeCalled() 59 | 60 | // goForward 61 | store.dispatch({ 62 | type: CALL_HISTORY_METHOD, 63 | payload: { 64 | method: 'goForward', 65 | args: [], 66 | } 67 | }) 68 | expect(history.goForward).toBeCalled() 69 | }) 70 | 71 | it('passes to next middleware if action type is not CALL_HISTORY_METHOD', () => { 72 | const spy = jest.fn() 73 | const nextMiddleware = store => next => action => { // eslint-disable-line no-unused-vars 74 | spy(action) 75 | } 76 | const history = {} 77 | const middlewares = [routerMiddleware(history), nextMiddleware] 78 | const mockStore = configureStore(middlewares) 79 | const store = mockStore() 80 | const action = { 81 | type: 'NOT_HANDLE_ACTION', 82 | payload: { 83 | text: 'Hello', 84 | }, 85 | } 86 | 87 | store.dispatch(action) 88 | expect(spy).toBeCalledWith(action) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /test/reducer.test.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { combineReducers as combineReducersImmutable } from 'redux-immutable' 3 | import { combineReducers as combineReducersSeamlessImmutable } from 'redux-seamless-immutable' 4 | import Immutable from 'immutable' 5 | import { LOCATION_CHANGE, connectRouter } from '../src' 6 | import { connectRouter as connectRouterImmutable } from '../src/immutable' 7 | import { connectRouter as connectRouterSeamlessImmutable } from '../src/seamless-immutable' 8 | 9 | describe('connectRouter', () => { 10 | let mockHistory 11 | 12 | beforeEach(() => { 13 | mockHistory = { 14 | location: { 15 | pathname: '/', 16 | search: '', 17 | hash: '', 18 | }, 19 | action: 'POP', 20 | } 21 | }) 22 | 23 | describe('with plain structure', () => { 24 | it('creates new root reducer with router reducer inside', () => { 25 | const mockReducer = (state = {}, action) => { 26 | switch (action.type) { 27 | default: 28 | return state 29 | } 30 | } 31 | const rootReducer = combineReducers({ 32 | mock: mockReducer, 33 | router: connectRouter(mockHistory) 34 | }) 35 | 36 | const currentState = { 37 | mock: {}, 38 | router: { 39 | location: { 40 | pathname: '/', 41 | search: '', 42 | hash: '', 43 | }, 44 | action: 'POP', 45 | }, 46 | } 47 | const action = { 48 | type: LOCATION_CHANGE, 49 | payload: { 50 | location: { 51 | pathname: '/path/to/somewhere', 52 | search: '?query=test', 53 | hash: '', 54 | }, 55 | action: 'PUSH', 56 | } 57 | } 58 | const nextState = rootReducer(currentState, action) 59 | const expectedState = { 60 | mock: {}, 61 | router: { 62 | location: { 63 | pathname: '/path/to/somewhere', 64 | search: '?query=test', 65 | hash: '', 66 | query: { query: 'test' } 67 | }, 68 | action: 'PUSH', 69 | }, 70 | } 71 | expect(nextState).toEqual(expectedState) 72 | }) 73 | 74 | it('does not change state ref when action does not trigger any reducers', () => { 75 | const rootReducer = combineReducers({ 76 | router: connectRouter(mockHistory) 77 | }) 78 | 79 | const currentState = { 80 | router: { 81 | location: { 82 | pathname: '/', 83 | search: '', 84 | hash: '', 85 | }, 86 | action: 'POP', 87 | }, 88 | } 89 | const action = { 90 | type: "DUMMY_ACTION", 91 | payload: "dummy payload" 92 | } 93 | const nextState = rootReducer(currentState, action) 94 | expect(nextState).toBe(currentState) 95 | }) 96 | 97 | it('does not change state ref when receiving LOCATION_CHANGE for the first rendering', () => { 98 | const rootReducer = combineReducers({ 99 | router: connectRouter(mockHistory) 100 | }) 101 | const currentState = { 102 | router: { 103 | location: { 104 | pathname: '/', 105 | search: '', 106 | hash: '', 107 | }, 108 | action: 'POP', 109 | }, 110 | } 111 | const action = { 112 | type: LOCATION_CHANGE, 113 | payload: { 114 | location: { 115 | pathname: '/', 116 | search: '', 117 | hash: '', 118 | }, 119 | action: 'POP', 120 | isFirstRendering: true, 121 | } 122 | } 123 | const nextState = rootReducer(currentState, action) 124 | expect(nextState).toBe(currentState) 125 | }) 126 | 127 | it('does not replace query if it already exists in location', () => { 128 | const mockReducer = (state = {}, action) => { 129 | switch (action.type) { 130 | default: 131 | return state 132 | } 133 | } 134 | const rootReducer = combineReducers({ 135 | mock: mockReducer, 136 | router: connectRouter(mockHistory) 137 | }) 138 | 139 | const currentState = { 140 | mock: {}, 141 | router: { 142 | location: { 143 | pathname: '/', 144 | search: '', 145 | hash: '' 146 | }, 147 | action: 'POP' 148 | } 149 | } 150 | const action = { 151 | type: LOCATION_CHANGE, 152 | payload: { 153 | location: { 154 | pathname: '/path/to/somewhere', 155 | search: '?query=%7Bvalue%3A%20%27foobar%27%7D', 156 | hash: '', 157 | query: { query: { value: 'foobar' } } 158 | }, 159 | action: 'PUSH' 160 | } 161 | } 162 | const nextState = rootReducer(currentState, action) 163 | const expectedState = { 164 | mock: {}, 165 | router: action.payload 166 | } 167 | expect(nextState).toEqual(expectedState) 168 | }) 169 | }) 170 | 171 | describe('with immutable structure', () => { 172 | it('creates new root reducer with router reducer inside', () => { 173 | const mockReducer = (state = Immutable.Map(), action) => { 174 | switch (action.type) { 175 | default: 176 | return state 177 | } 178 | } 179 | const rootReducer = combineReducersImmutable({ 180 | mock: mockReducer, 181 | router: connectRouterImmutable(mockHistory) 182 | }) 183 | 184 | const currentState = Immutable.fromJS({ 185 | mock: {}, 186 | router: { 187 | location: { 188 | pathname: '/', 189 | search: '', 190 | hash: '', 191 | }, 192 | action: 'POP', 193 | }, 194 | }) 195 | const action = { 196 | type: LOCATION_CHANGE, 197 | payload: { 198 | location: { 199 | pathname: '/path/to/somewhere', 200 | search: '?query=test', 201 | hash: '', 202 | }, 203 | action: 'PUSH', 204 | } 205 | } 206 | const nextState = rootReducer(currentState, action) 207 | const expectedState = Immutable.fromJS({ 208 | mock: {}, 209 | router: { 210 | location: { 211 | pathname: '/path/to/somewhere', 212 | search: '?query=test', 213 | hash: '', 214 | query: { query: 'test' } 215 | }, 216 | action: 'PUSH', 217 | }, 218 | }) 219 | expect(nextState).toEqual(expectedState) 220 | }) 221 | 222 | it('does not change state ref when receiving LOCATION_CHANGE for the first rendering', () => { 223 | const rootReducer = combineReducers({ 224 | router: connectRouter(mockHistory) 225 | }) 226 | const currentState = { 227 | router: { 228 | location: { 229 | pathname: '/', 230 | search: '', 231 | hash: '', 232 | }, 233 | action: 'POP', 234 | }, 235 | } 236 | const action = { 237 | type: LOCATION_CHANGE, 238 | payload: { 239 | location: { 240 | pathname: '/', 241 | search: '', 242 | hash: '', 243 | }, 244 | action: 'POP', 245 | isFirstRendering: true, 246 | } 247 | } 248 | const nextState = rootReducer(currentState, action) 249 | expect(nextState).toBe(currentState) 250 | }) 251 | }) 252 | 253 | describe('with seamless immutable structure', () => { 254 | it('creates new root reducer with router reducer inside', () => { 255 | const mockReducer = (state = {}, action) => { 256 | switch (action.type) { 257 | default: 258 | return state 259 | } 260 | } 261 | const rootReducer = combineReducersSeamlessImmutable({ 262 | mock: mockReducer, 263 | router: connectRouterSeamlessImmutable(mockHistory) 264 | }) 265 | 266 | const currentState = { 267 | mock: {}, 268 | router: { 269 | location: { 270 | pathname: '/', 271 | search: '', 272 | hash: '', 273 | }, 274 | action: 'POP', 275 | }, 276 | } 277 | const action = { 278 | type: LOCATION_CHANGE, 279 | payload: { 280 | location: { 281 | pathname: '/path/to/somewhere', 282 | search: '?query=test', 283 | hash: '', 284 | }, 285 | action: 'PUSH', 286 | } 287 | } 288 | const nextState = rootReducer(currentState, action) 289 | const expectedState = { 290 | mock: {}, 291 | router: { 292 | location: { 293 | pathname: '/path/to/somewhere', 294 | search: '?query=test', 295 | hash: '', 296 | query: { query: 'test' } 297 | }, 298 | action: 'PUSH', 299 | }, 300 | } 301 | expect(nextState).toEqual(expectedState) 302 | }) 303 | 304 | it('does not change state ref when receiving LOCATION_CHANGE for the first rendering', () => { 305 | const rootReducer = combineReducers({ 306 | router: connectRouter(mockHistory) 307 | }) 308 | const currentState = { 309 | router: { 310 | location: { 311 | pathname: '/', 312 | search: '', 313 | hash: '', 314 | }, 315 | action: 'POP', 316 | }, 317 | } 318 | const action = { 319 | type: LOCATION_CHANGE, 320 | payload: { 321 | location: { 322 | pathname: '/', 323 | search: '', 324 | hash: '', 325 | }, 326 | action: 'POP', 327 | isFirstRendering: true, 328 | } 329 | } 330 | const nextState = rootReducer(currentState, action) 331 | expect(nextState).toBe(currentState) 332 | }) 333 | }) 334 | }) 335 | -------------------------------------------------------------------------------- /test/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers } from "redux" 2 | import { createBrowserHistory } from 'history' 3 | import { connectRouter, getLocation, createMatchSelector, getAction, getSearch, getHash } from '../src' 4 | import { onLocationChanged } from '../src/actions' 5 | 6 | const push = pathname => onLocationChanged( 7 | { 8 | pathname, 9 | search: '', 10 | hash: '', 11 | }, 12 | 'PUSH' 13 | ) 14 | 15 | describe("selectors", () => { 16 | let store 17 | 18 | beforeEach(() => { 19 | const history = createBrowserHistory() 20 | const reducer = combineReducers({ 21 | router: connectRouter(history) 22 | }) 23 | store = createStore(reducer) 24 | }) 25 | 26 | describe("when router not found under 'router' key", () => { 27 | beforeEach(() => { 28 | const reducer = combineReducers({ 29 | notTheRouter: connectRouter(history) 30 | }) 31 | store = createStore(reducer) 32 | }) 33 | 34 | it("throws helpful error", () => { 35 | store.dispatch(push('/')) 36 | const state = store.getState() 37 | expect(() => getLocation(state)).toThrowError(/^Could not find router reducer in state tree, it must be mounted under "router"$/) 38 | }) 39 | }) 40 | 41 | describe("when something else found under 'router' key", () => { 42 | beforeEach(() => { 43 | const reducer = combineReducers({ 44 | router: () => ({ some: 'thing' }) 45 | }) 46 | store = createStore(reducer) 47 | }) 48 | 49 | it("throws helpful error", () => { 50 | store.dispatch(push('/')) 51 | const state = store.getState() 52 | expect(() => getLocation(state)).toThrowError(/^Could not find router reducer in state tree, it must be mounted under "router"$/) 53 | }) 54 | }) 55 | 56 | describe("getLocation", () => { 57 | it("gets the location from the state", () => { 58 | const location = { pathname: "/", hash: '', query: {}, search: '', } 59 | store.dispatch(push('/')) 60 | const state = store.getState() 61 | expect(getLocation(state)).toEqual(location) 62 | }) 63 | }) 64 | 65 | describe("getAction", () => { 66 | it("gets the action from the state", () => { 67 | const action = "PUSH" 68 | store.dispatch(push('/')) 69 | const state = store.getState() 70 | expect(getAction(state)).toBe(action) 71 | }) 72 | }) 73 | 74 | describe("getSearch", () => { 75 | it("gets the current search from state", () => { 76 | const push = ({search}) => onLocationChanged( 77 | { 78 | pathname: '/', 79 | search, 80 | hash: '', 81 | }, 82 | 'PUSH' 83 | ) 84 | const search = "?query=hello" 85 | store.dispatch(push({search})) 86 | const state = store.getState() 87 | expect(getSearch(state)).toBe(search) 88 | }) 89 | }) 90 | 91 | describe("getHash", () => { 92 | it("gets the current search from state", () => { 93 | const push = ({hash}) => onLocationChanged( 94 | { 95 | pathname: '/', 96 | search: '', 97 | hash, 98 | }, 99 | 'PUSH' 100 | ) 101 | const hash = "#test" 102 | store.dispatch(push({hash})) 103 | const state = store.getState() 104 | expect(getHash(state)).toBe(hash) 105 | }) 106 | }) 107 | 108 | describe("createMatchSelector", () => { 109 | it("matches correctly if the router is initialized", () => { 110 | const matchSelector = createMatchSelector("/") 111 | store.dispatch(push('/test')) 112 | const state = store.getState() 113 | expect(matchSelector(state)).toEqual({ 114 | isExact: false, 115 | params: {}, 116 | path: "/", 117 | url: "/" 118 | }) 119 | }) 120 | 121 | it("does not throw error if router has not yet initialized", () => { 122 | const matchSelector = createMatchSelector("/") 123 | const state = store.getState() 124 | expect(() => matchSelector(state)).not.toThrow() 125 | }) 126 | 127 | it("does not update if the match is the same", () => { 128 | const matchSelector = createMatchSelector("/") 129 | const match1 = matchSelector(store.getState()) 130 | store.dispatch(push('/test1')) 131 | const match2 = matchSelector(store.getState()) 132 | store.dispatch(push('/test2')) 133 | const match3 = matchSelector(store.getState()) 134 | expect(match1).not.toBe(match2) 135 | expect(match1).not.toBe(match3) 136 | expect(match2).toEqual(match3) 137 | }) 138 | 139 | it("updates if the match is different", () => { 140 | const matchSelector = createMatchSelector("/sushi/:type") 141 | store.dispatch(push('/sushi/california')) 142 | const californiaMatch = matchSelector(store.getState()) 143 | store.dispatch(push('/sushi/dynamite')) 144 | const dynamiteMatch = matchSelector(store.getState()) 145 | expect(californiaMatch).not.toBe(dynamiteMatch) 146 | expect(californiaMatch).toEqual({ 147 | isExact: true, 148 | params: {type: 'california'}, 149 | path: '/sushi/:type', 150 | url: '/sushi/california', 151 | }) 152 | expect(dynamiteMatch).toEqual({ 153 | isExact: true, 154 | params: {type: 'dynamite'}, 155 | path: '/sushi/:type', 156 | url: '/sushi/dynamite', 157 | }) 158 | }) 159 | 160 | it("updates if the exact match is different", () => { 161 | const matchSelector = createMatchSelector({ 162 | path: "/sushi", 163 | exact: true 164 | }) 165 | store.dispatch(push('/sushi')) 166 | const okMatch = matchSelector(store.getState()) 167 | store.dispatch(push('/sushi/dynamite')) 168 | const koMatch = matchSelector(store.getState()) 169 | expect(okMatch).toEqual({ 170 | isExact: true, 171 | params: {}, 172 | path: '/sushi', 173 | url: '/sushi', 174 | }) 175 | expect(koMatch).toBe(null) 176 | }) 177 | }) 178 | }) 179 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | entry: './src/index', 3 | mode: 'production', 4 | module: { 5 | rules: [ 6 | { test: /\.js$/, use: [ 'babel-loader' ], exclude: /node_modules/ } 7 | ] 8 | }, 9 | output: { 10 | library: 'ConnectedReactRouter', 11 | libraryTarget: 'umd' 12 | } 13 | } 14 | 15 | if (process.env.NODE_ENV === 'production') { 16 | config.optimization = { 17 | minimize: true 18 | } 19 | } 20 | 21 | module.exports = config 22 | --------------------------------------------------------------------------------