├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── SUMMARY.md ├── book.css ├── book.json ├── docs ├── Examples.md └── api │ ├── README.md │ ├── RouterToUrlQuery.md │ ├── Serialize.md │ ├── UrlQueryParamTypes.md │ ├── UrlUpdateTypes.md │ ├── addUrlProps.md │ ├── configureUrlQuery.md │ ├── multiPushInUrlQuery.md │ ├── multiPushInUrlQueryWithAction.md │ ├── multiReplaceInUrlQuery.md │ ├── multiReplaceInUrlQueryFromAction.md │ ├── pushInUrlQuery.md │ ├── pushInUrlQueryFromAction.md │ ├── pushUrlQuery.md │ ├── pushUrlQueryFromAction.md │ ├── replaceInUrlQuery.md │ ├── replaceInUrlQueryFromAction.md │ ├── replaceUrlQuery.md │ ├── replaceUrlQueryFromAction.md │ ├── subquery.md │ ├── subqueryOmit.md │ ├── urlAction.md │ ├── urlMultiPushInAction.md │ ├── urlMultiReplaceInAction.md │ ├── urlPushAction.md │ ├── urlPushInAction.md │ ├── urlQueryDecoder.md │ ├── urlQueryEncoder.md │ ├── urlQueryMiddleware.md │ ├── urlQueryReducer.md │ ├── urlReplaceAction.md │ └── urlReplaceInAction.md ├── examples ├── README.md ├── basic-mapUrlToProps │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ └── src │ │ ├── App.js │ │ ├── App.test.js │ │ ├── MainPage.js │ │ ├── history.js │ │ └── index.js ├── basic │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ └── src │ │ ├── App.js │ │ ├── App.test.js │ │ ├── MainPage.js │ │ ├── history.js │ │ └── index.js ├── buildAll.js ├── react-router-v2-and-redux │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ └── src │ │ ├── App.js │ │ ├── App.test.js │ │ ├── MainPage.js │ │ ├── index.js │ │ └── state │ │ ├── actions.js │ │ └── rootReducer.js ├── react-router-v4-and-redux │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ └── src │ │ ├── App.js │ │ ├── App.test.js │ │ ├── MainPage.js │ │ ├── index.js │ │ └── state │ │ ├── actions.js │ │ └── rootReducer.js ├── redux-with-actions │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ └── src │ │ ├── App.js │ │ ├── App.test.js │ │ ├── MainPage.js │ │ ├── history.js │ │ ├── index.js │ │ └── state │ │ ├── actions.js │ │ ├── rootReducer.js │ │ └── urlQueryReducer.js ├── redux │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ └── src │ │ ├── App.js │ │ ├── App.test.js │ │ ├── MainPage.js │ │ ├── history.js │ │ ├── index.js │ │ └── state │ │ ├── actions.js │ │ └── rootReducer.js └── testAll.js ├── package-lock.json ├── package.json ├── src ├── UrlQueryParamTypes.js ├── UrlUpdateTypes.js ├── __tests__ │ ├── configureUrlQuery-test.js │ ├── index-test.js │ ├── serialize-test.js │ ├── updateUrlQuery-test.js │ ├── urlQueryConfig-test.js │ ├── urlQueryDecoder-test.js │ └── urlQueryEncoder-test.js ├── configureUrlQuery.js ├── index.js ├── react │ ├── RouterToUrlQuery.js │ ├── __tests__ │ │ ├── RouterToUrlQuery-test.js │ │ └── addUrlProps-test.js │ └── addUrlProps.js ├── redux │ ├── __tests__ │ │ ├── updateUrlQueryFromAction-test.js │ │ ├── urlAction-test.js │ │ ├── urlQueryMiddleware-test.js │ │ └── urlQueryReducer-test.js │ ├── updateUrlQueryFromAction.js │ ├── urlAction.js │ ├── urlQueryMiddleware.js │ └── urlQueryReducer.js ├── serialize.js ├── updateUrlQuery.js ├── urlQueryConfig.js ├── urlQueryDecoder.js ├── urlQueryEncoder.js └── utils │ ├── __tests__ │ ├── subquery-test.js │ └── subqueryOmit-test.js │ ├── subquery.js │ └── subqueryOmit.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-0"], 3 | "env": { 4 | "commonjs": { 5 | "plugins": [ 6 | ["transform-es2015-modules-commonjs", { "loose": true }] 7 | ] 8 | }, 9 | } 10 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain 2 | # consistent coding styles between different editors and IDEs. 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/** 2 | **/examples/** 3 | **/node_modules/** 4 | **/server.js 5 | **/webpack.config*.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Do you want to request a *feature* or report a *bug*?** 2 | 3 | 4 | **What is the current behavior?** 5 | 6 | 7 | **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar.** 8 | 9 | 10 | **What is the expected behavior?** 11 | 12 | 13 | **Which versions of react-url-query, and which browser and OS are affected by this issue? Did this work in previous versions of react-url-query?** 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | dist 5 | lib 6 | es 7 | coverage 8 | _book 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "bracketSpacing": true, 6 | "parser": "babylon", 7 | "semi": true 8 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | script: 5 | - npm run check:src 6 | - npm run build 7 | branches: 8 | only: 9 | - master 10 | cache: 11 | directories: 12 | - $HOME/.npm 13 | - examples/basic/node_modules 14 | - examples/basic-mapUrlToProps/node_modules 15 | - examples/react-router-v2-and-redux/node_modules 16 | - examples/react-router-v4-and-redux/node_modules 17 | - examples/redux/node_modules 18 | - examples/redux-with-actions/node_modules 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | # 1.4.0 7 | 8 | - Helpers to easily encode and decode queries based on `urlPropsQueryConfig` 9 | 10 | # 1.3.0 11 | 12 | - Support global configuration of `entrySeparator` and `keyValSeparator` serialisation properties 13 | 14 | # 1.2.0 15 | 16 | - Adds in numericArray and numericObject types 17 | - Fixes problem with React Router v4 where location could get out of sync (#22) 18 | - Updates addRouterParams feature to work with React Router v4's props.match.params 19 | 20 | # 1.1.4 21 | 22 | - Fixes missed warnings about using `prop-types` npm package (#15) 23 | 24 | # 1.1.3 25 | 26 | - Fixes bug where location prop wasn't recognized if search attribute was the empty string (#12) 27 | 28 | # 1.1.2 29 | 30 | - Fixes warning about using `prop-types` npm package (#15) 31 | - Updates to use the latest React Router v4 API (#17) 32 | 33 | # 1.1.1 34 | 35 | - Fixes bug where multiReplaceInUrlQuery and multiPushInUrlQuery were not exported. (#8) 36 | 37 | # 1.1.0 38 | 39 | - Adds in support for changing multiple query parameters at once through the `onChangeUrlQueryParams()` callback. This function is passed as a prop when `addChangeHandlers` is true. It takes an object that maps prop names to unencoded values and updates all of them at once. See the [Basic Example](https://github.com/pbeshai/react-url-query/blob/master/examples/basic/src/MainPage.js). 40 | - You can also make use of new helper functions `multiReplaceInUrlQuery()` and `multiPushInUrlQuery()`. 41 | -------------------------------------------------------------------------------- /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, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | 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 dan.abramov@me.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 [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Peter Beshai 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 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | * [Read Me](/README.md) 4 | * [Examples](/docs/Examples.md) 5 | * [Change Log](/CHANGELOG.md) 6 | 7 | 8 | * [API Reference](/docs/api/README.md) 9 | * [addUrlProps](/docs/api/addUrlProps.md) 10 | * [configureUrlQuery](/docs/api/configureUrlQuery.md) 11 | 12 | * Type Enums 13 | * [UrlQueryParamTypes](/docs/api/UrlQueryParamTypes.md) 14 | * [UrlUpdateTypes](/docs/api/UrlUpdateTypes.md) 15 | 16 | * Serialization 17 | * [Serialize](/docs/api/Serialize.md) 18 | * [decode](/docs/api/Serialize.md#decode), _alias of Serialize.decode for convenience_ 19 | * [encode](/docs/api/Serialize.md#encode), _alias of Serialize.encode for convenience_ 20 | * [urlQueryDecoder(config)](/docs/api/urlQueryDecoder.md) 21 | * [urlQueryEncoder(config)](/docs/api/urlQueryEncoder.md) 22 | 23 | * Utils 24 | * [subquery](/docs/api/subquery.md) 25 | * [subqueryOmit](/docs/api/subqueryOmit.md) 26 | 27 | * URL Update 28 | * [replaceInUrlQuery](/docs/api/replaceInUrlQuery.md) 29 | * [pushInUrlQuery](/docs/api/pushInUrlQuery.md) 30 | * [replaceUrlQuery](/docs/api/replaceUrlQuery.md) 31 | * [pushUrlQuery](/docs/api/pushUrlQuery.md) 32 | * [multiReplaceInUrlQuery](/docs/api/multiReplaceInUrlQuery.md) 33 | * [multiPushInUrlQuery](/docs/api/multiPushInUrlQuery.md) 34 | 35 | * React Router v4 36 | * [RouterToUrlQuery](/docs/api/RouterToUrlQuery.md) 37 | 38 | * Redux Action Integration 39 | * [replaceInUrlQueryFromAction](/docs/api/replaceInUrlQueryFromAction.md) 40 | * [replaceUrlQueryFromAction](/docs/api/replaceUrlQueryFromAction.md) 41 | * [pushInUrlQueryFromAction](/docs/api/pushInUrlQueryFromAction.md) 42 | * [pushUrlQueryFromAction](/docs/api/pushUrlQueryFromAction.md) 43 | * [urlAction](/docs/api/urlAction.md) 44 | * [urlReplaceInAction](/docs/api/urlReplaceInAction.md) 45 | * [urlPushInAction](/docs/api/urlPushInAction.md) 46 | * [urlReplaceAction](/docs/api/urlReplaceAction.md) 47 | * [urlPushAction](/docs/api/urlPushAction.md) 48 | * [urlQueryMiddleware](/docs/api/urlQueryMiddleware.md) 49 | * [urlQueryReducer](/docs/api/urlQueryReducer.md) 50 | -------------------------------------------------------------------------------- /book.css: -------------------------------------------------------------------------------- 1 | .book-summary ul.summary li a:hover { 2 | color: #008cff; 3 | text-decoration: none; 4 | } 5 | 6 | .book-summary ul.summary li span { 7 | opacity: 0.5; 8 | } 9 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitbook": ">=3.2.1", 3 | "title": "React URL Query Docs", 4 | "plugins": ["edit-link", "prism", "-highlight", "github", "anchorjs"], 5 | "pluginsConfig": { 6 | "edit-link": { 7 | "base": "https://github.com/pbeshai/react-url-query/tree/master", 8 | "label": "Edit This Page" 9 | }, 10 | "github": { 11 | "url": "https://github.com/pbeshai/react-url-query/" 12 | }, 13 | "theme-default": { 14 | "showLevel": false, 15 | "styles": { 16 | "website": "book.css" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/Examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | The examples are included in the [repository on GitHub](https://github.com/pbeshai/react-url-query/tree/master/examples). 4 | 5 | 6 | ### [basic](https://github.com/pbeshai/react-url-query/tree/master/examples/basic) 7 | 8 | Shows the bare minimum amount of work to get React URL Query working in your application when you are not using React Router or Redux. The basic steps are: 9 | 10 | 1. Use a [history](https://github.com/mjackson/history) of some sort to control pushing or replacing items in the browser's history stack. Be sure to listen for changes to the history and force an update when they occur (see [App.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic/src/App.js)) 11 | 1. Configure React URL Query to use the history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic/src/index.js)). 12 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic/src/MainPage.js)). 13 | 14 | 15 | ### [basic-mapUrlToProps](https://github.com/pbeshai/react-url-query/tree/master/examples/basic-mapUrlToProps) 16 | 17 | Shows a basic configuration to get React URL Query working in your application when you are not using React Router or Redux. Uses alternative approach of `mapUrlToProps` instead of `urlPropsQueryConfig`. The steps are: 18 | 19 | 1. Use a [history](https://github.com/mjackson/history) of some sort to control pushing or replacing items in the browser's history stack. Be sure to listen for changes to the history and force an update when they occur (see [App.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic-mapUrlToProps/src/App.js)) 20 | 1. Configure React URL Query to use the history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic-mapUrlToProps/src/index.js)). 21 | 1. Use a `mapUrlToProps`, `mapUrlChangeHandlersToProps` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic-mapUrlToProps/src/MainPage.js)). 22 | 23 | 24 | 25 | ### [redux](https://github.com/pbeshai/react-url-query/tree/master/examples/redux) 26 | 27 | Demonstrates how to integrate React URL Query with Redux (and no React Router). The only difference between this and the basic example is how you wrap your component with `addUrlProps`. The steps are: 28 | 29 | 1. Use a [history](https://github.com/mjackson/history) of some sort to control pushing or replacing items in the browser's history stack. Be sure to listen for changes to the history and force an update when they occur (see [App.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux/src/App.js)) 30 | 1. Configure React URL Query to use the history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux/src/index.js)). 31 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query. In this case, we wrap the connected component: `addUrlProps(...)(connect(...)(MyComponent))` (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux/src/MainPage.js)) 32 | 33 | 34 | ### [redux-with-actions](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions) 35 | 36 | The most common way of integrating Redux with React URL Query is shown in the [redux example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux). This project demonstrates an alternative approach, using `dispatch` and actions to change the URL. React Router is not used. The steps are: 37 | 38 | 1. Use a [history](https://github.com/mjackson/history) of some sort to control pushing or replacing items in the browser's history stack. Be sure to listen for changes to the history and force an update when they occur (see [App.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/App.js)) 39 | 1. Configure React URL Query to use the history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/index.js)). Disable auto-generation of change handlers by passing `addChangeHandlers: false`. 40 | 1. Attach the [`urlQueryMiddleware`](api/urlQueryMiddleware.md) to Redux, optionally instantiating with a custom [URL query reducer](api/urlQueryReducer). (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/index.js)) 41 | 1. Create actions for URL updates with [`urlAction`](api/urlAction.md), [`urlReplaceInAction`](api/urlReplaceInAction.md), [`urlPushInAction`](api/urlPushInAction.md), etc. (see [actions.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/state/actions.js)) 42 | 1. Create change handlers in `mapDispatchToProps` for the actions you've made. (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/MainPage.js)) 43 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query. In this case, we wrap the connected component: `addUrlProps(...)(connect(...)(MyComponent))` (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/MainPage.js)) 44 | 45 | 46 | ### [react-router-v2-and-redux](https://github.com/pbeshai/react-url-query/tree/master/examples/react-router-v2-and-redux) 47 | 48 | Demonstrates how to use React URL Query with React Router v2 and Redux. This example also demonstrates using a custom type for a URL query parameter. The steps are: 49 | 50 | 1. Configure React URL Query to use the React Router's history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/react-router-v2-and-redux/src/index.js)). 51 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query. In this case, we wrap the connected component: `addUrlProps(...)(connect(...)(MyComponent))` (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/react-router-v2-and-redux/src/MainPage.js)). 52 | 53 | 54 | 55 | 56 | ### [react-router-v4-and-redux](https://github.com/pbeshai/react-url-query/tree/master/examples/react-router-v4-and-redux) 57 | 58 | Demonstrates how to use React URL Query with React Router v4 and Redux. This example also demonstrates using a custom type for a URL query parameter. When using React Router v4, we do *not* use [`configureUrlQuery`](api/configureUrlQuery.md) as we do in all the others. Instead we use [``](api/RouterToUrlQuery.md). The steps are: 59 | 60 | 1. Use `` to configure React URL Query to use the React Router's history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/react-router-v4-and-redux/src/index.js)). 61 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query. In this case, we wrap the connected component: `addUrlProps(...)(connect(...)(MyComponent))` (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/react-router-v4-and-redux/src/MainPage.js)). 62 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ### Top-Level Exports 4 | 5 | React URL Query provides a number of top-level exports. 6 | 7 | * [addUrlProps([options])(WrappedComponent)](addUrlProps.md) 8 | * [configureUrlQuery(config)](configureUrlQuery.md) 9 | 10 | #### Type Enums 11 | * [UrlQueryParamTypes](UrlQueryParamTypes.md) 12 | * [UrlUpdateTypes](UrlUpdateTypes.md) 13 | 14 | #### Serialization 15 | * [Serialize](Serialize.md) 16 | * [decode(type, encodedValue, [defaultValue])](Serialize.md#decode), _alias of Serialize.decode for convenience_ 17 | * [encode(type, valueToEncode)](Serialize.md#encode), _alias of Serialize.encode for convenience_ 18 | * [urlQueryDecoder(config)](urlQueryDecoder.md) 19 | * [urlQueryEncoder(config)](urlQueryEncoder.md) 20 | 21 | #### Utils 22 | * [subquery(query, ...params)](subquery.md) 23 | * [subqueryOmit(query, ...omitParams)](subqueryOmit.md) 24 | 25 | #### URL Update 26 | * [replaceInUrlQuery(queryParam, encodedValue, [location])](replaceInUrlQuery.md) 27 | * [pushInUrlQuery(queryParam, encodedValue, [location])](pushInUrlQuery.md) 28 | * [replaceUrlQuery(newQuery, [location])](replaceUrlQuery.md) 29 | * [pushUrlQuery(newQuery, [location])](pushUrlQuery.md) 30 | * [multiReplaceInUrlQuery(queryReplacements, [location])](multiReplaceInUrlQuery.md) 31 | * [multiPushInUrlQuery(queryReplacements, [location])](multiPushInUrlQuery.md) 32 | 33 | #### React-Router v4 34 | * [RouterToUrlQuery](RouterToUrlQuery.md) 35 | 36 | #### Redux Action Integration 37 | 38 | Note that these helpers are provided in the event you would like to use Redux's `dispatch` to update the URL. They are *not necessary* for using React URL Query with Redux. Compare the [example without using dispatch](https://github.com/pbeshai/react-url-query/tree/master/examples/redux) with the [example using dispatch](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). 39 | 40 | * [replaceInUrlQueryFromAction(action, [location])](replaceInUrlQueryFromAction.md) 41 | * [replaceUrlQueryFromAction(action, [location])](replaceUrlQueryFromAction.md) 42 | * [pushInUrlQueryFromAction(action, [location])](pushInUrlQueryFromAction.md) 43 | * [pushUrlQueryFromAction(action, [location])](pushUrlQueryFromAction.md) 44 | * [urlAction(actionType, payload, [meta])](urlAction.md) 45 | * [urlReplaceInAction(actionType, queryParam, valueType)](urlReplaceInAction.md) 46 | * [urlPushInAction(actionType, queryParam, valueType)](urlPushInAction.md) 47 | * [urlReplaceAction(actionType, [encodeQuery])](urlReplaceAction.md) 48 | * [urlPushAction(actionType, [encodeQuery])](urlPushAction.md) 49 | * [urlQueryMiddleware([options])](urlQueryMiddleware.md) 50 | * [urlQueryReducer(action, [location])](urlQueryReducer.md) 51 | 52 | 53 | ### Importing 54 | 55 | You can import any of the **Top-Level Exports** as follows: 56 | 57 | #### ES6 58 | 59 | ```js 60 | import { addUrlProps } from 'react-url-query' 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/api/RouterToUrlQuery.md: -------------------------------------------------------------------------------- 1 | ### `` 2 | 3 | This component is intended to be used with React Router v4. It passes `router` from `this.context` to the url query configuration via `configureUrlQuery` to be used as the `history` by adapting the interface. It maps: 4 | 5 | * `push`: `router.push` or `router.transitionTo` 6 | * `replace`: `router.replace` or `router.replaceWith` 7 | 8 | Since it reads from the context, it needs to be placed as a child of ``. 9 | 10 | Note that when using this, you will not be configuring `history` in `configureUrlQuery`, but you still can configure other options. 11 | 12 | #### Props 13 | 14 | It takes no props besides `children`. 15 | 16 | #### Example 17 | 18 | ```js 19 | import { RouterToUrlQuery } from 'react-url-query'; 20 | import Router from 'react-router/BrowserRouter'; 21 | 22 | ReactDOM.render( 23 | 24 | 25 | 26 | 27 | , 28 | document.getElementById('root') 29 | ); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/api/UrlQueryParamTypes.md: -------------------------------------------------------------------------------- 1 | ### UrlQueryParamTypes 2 | 3 | An enum listing all the query parameter types that have built-in [serializers](Serialize.md) for encoding in and decoding from the URL. 4 | 5 | * `number` - Used for numbers (integers or floats) 6 | * `string` - Used for strings 7 | * `object` - Used for objects 8 | * `array` - Used for arrays 9 | * `json` - Used for generic JSON values 10 | * `date` - Used for Date values 11 | * `boolean` - Used for boolean values 12 | * `numericObject` - Used for objects where all values are numbers 13 | * `numericArray` - Used for arrays where all values are numbers 14 | 15 | #### Examples 16 | 17 | ```js 18 | import { UrlQueryParamTypes } from 'react-url-query'; 19 | 20 | const urlPropsQueryConfig = { 21 | foo: { type: UrlQueryParamTypes.number }, 22 | }; 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/api/UrlUpdateTypes.md: -------------------------------------------------------------------------------- 1 | ### UrlUpdateTypes 2 | 3 | An enum listing all the available update types that can be used to indicate how the URL updates. 4 | 5 | * `replaceIn` - Update a single value in the set of query parameters, retaining the existing values for all others. Uses replace when modifying the history, so it does not add anything to the history stack. 6 | 7 | * `pushIn` - Update a single value in the set of query parameters, retaining the existing values for all others. Uses push when modifying the history, meaning it pushes an entry on to the history stack. This enables using the back button to go to the previous state. 8 | 9 | * `replace` - Update all values in the query parameters. Uses replace when modifying the history, so it does not add anything to the history stack. 10 | 11 | * `push` - Update all values in the query parameters. Uses push when modifying the history, meaning it pushes an entry on to the history stack. This enables using the back button to go to the previous state. 12 | 13 | 14 | #### Examples 15 | 16 | ```js 17 | import { UrlUpdateTypes } from 'react-url-query'; 18 | 19 | const urlPropsQueryConfig = { 20 | foo: { type: ..., updateType: UrlUpdateTypes.pushIn }, 21 | }; 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/api/configureUrlQuery.md: -------------------------------------------------------------------------------- 1 | ### `configureUrlQuery(config)` 2 | 3 | This function configures the singleton instance of React URL Query. The available options to configure are: 4 | 5 | * `history` (*Object*): Must be provided unless using React Router v4. This object should provide two functions: `push(location)` and `replace(location)` that update the URL based on the `location` provided. 6 | 7 | * `addUrlChangeHandlers` (*Boolean*): If true, adds generated URL change handlers when using [`addUrlProps`](addUrlProps.md) with a `urlPropsQueryConfig`. Defaults to `true`. 8 | 9 | * `addRouterParams` (*Boolean*): If true, lifts values from `props.params` provided by React Router to direct `props` when using [`addUrlProps`](addUrlProps.md). Defaults to `true`. 10 | 11 | * `changeHandlerName` (*Function*): Specifies how change handler names are generated when `addUrlChangeHandlers` is set to `true`. By default, maps `propName` to `onChangePropName`. 12 | 13 | * `readLocationFromStore` (*Function*): Reads in `location` from the Redux store if available and passes it to the reducer in [`urlQueryMiddleware`](urlQueryMiddleware.md). This property is only used when the middleware is added to Redux, as in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). Defaults to reading from `state.routing.locationBeforeTransitions`, the standard location for [react-router-redux](https://github.com/reactjs/react-router-redux). 14 | 15 | * `entrySeparator` (*String*): Specifies the separator that should be between entries when encoding / decoding. By default `_`. 16 | 17 | * `keyValSeparator` (*String*): Specifies the separator that should be between key and values when encoding / decoding. By default `-`. 18 | 19 | #### Arguments 20 | 21 | 1. `options` (*Object*): The options to update in the configuration. 22 | 23 | #### Returns 24 | 25 | (*void*): It does not return anything. 26 | 27 | #### Remarks 28 | 29 | * If you are using React Router v4, instead of configuring the `history` option here, use [`RouterToUrlQuery`](RouterToUrlQuery.md). All other options can still be configured normally. 30 | 31 | #### Examples 32 | 33 | ```js 34 | import { browserHistory } from 'react-router'; // v2 35 | 36 | configureUrlQuery({ 37 | history: browserHistory, 38 | addRouterParams: false, 39 | }); 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/api/multiPushInUrlQuery.md: -------------------------------------------------------------------------------- 1 | ### `multiPushInUrlQuery(queryReplacements, [location])` 2 | 3 | Updates the URL to have the specified query parameter's values set to those in the `queryReplacements` object, while keeping all the other query parameters the same. The `queryReplacements` object has the form: 4 | 5 | ``` 6 | { 7 | queryParamName: encodedValue, 8 | ... 9 | } 10 | ``` 11 | 12 | Uses push to change the URL, which means the new state will be pushed on to the history stack, so the back button will be able to return you to the previous state. 13 | 14 | #### Arguments 15 | 16 | 1. `queryReplacements` (*Object*): The object representing the query parameters and their encoded values to update. 17 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 18 | 19 | #### Returns 20 | 21 | (*Any*): The result of `history.push()`, will depend on the history being used. 22 | 23 | #### Examples 24 | 25 | ```js 26 | // Given URL /page?foo=bar&baz=123&jim=bo 27 | multiPushInUrlQuery({ foo: 'test', baz: '99' }); 28 | // URL is now /page?foo=test&baz=99&jim=bo 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/api/multiPushInUrlQueryWithAction.md: -------------------------------------------------------------------------------- 1 | ### `multiPushInUrlQueryFromAction(action, [location])` 2 | 3 | A helper function for when you want to use redux `dispatch` to handle your URL updating actions, an optional way of combining Redux with React URL Query. It expects the action to have the form: 4 | 5 | ``` 6 | { 7 | payload: { 8 | encodedQuery: { 9 | queryParamName: encodedValue, 10 | ... 11 | } 12 | } 13 | ... 14 | } 15 | ``` 16 | 17 | This function is typically only useful when using your own urlReducer, as shown in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). 18 | 19 | Uses push to change the URL, which means the new state will be pushed on to the history stack, so the back button will be able to return you to the previous state. 20 | 21 | #### Arguments 22 | 23 | 1. `action` (*Object*): An action, typically from [`urlMultiPushInAction`](urlMultiPushInAction.md). Shape described above. 24 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 25 | 26 | #### Returns 27 | 28 | (*void*): It does not return anything. 29 | 30 | 31 | #### Examples 32 | 33 | ```js 34 | // create an action creator 35 | const changeFoo = urlMultiPushInAction( 36 | 'CHANGE_FOO', 37 | (newQuery) => ({ 38 | foo: encode(UrlQueryParamTypes.number, newQuery.foo), 39 | }) 40 | ); 41 | 42 | // call an action creator to get an action 43 | const action = changeFoo({foo: 94}); 44 | 45 | // typically in a reducer, call this helper to update the URL 46 | // e.g. URL was /page?foo=123&bar=baz 47 | multiPushInUrlQueryFromAction(action) 48 | // e.g. URL now is /page?foo=94&bar=baz 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/api/multiReplaceInUrlQuery.md: -------------------------------------------------------------------------------- 1 | ### `multiReplaceInUrlQuery(queryReplacements, [location])` 2 | 3 | Updates the URL to have the specified query parameter's values set to those in the `queryReplacements` object, while keeping all the other query parameters the same. The `queryReplacements` object has the form: 4 | 5 | ``` 6 | { 7 | queryParamName: encodedValue, 8 | ... 9 | } 10 | ``` 11 | 12 | Uses replace to change the URL, which means nothing gets pushed on to the history stack, so the back button will not be able to return you to the previous state. 13 | 14 | #### Arguments 15 | 16 | 1. `queryReplacements` (*Object*): The object representing the query parameters and their encoded values to update. 17 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 18 | 19 | #### Returns 20 | 21 | (*Any*): The result of `history.replace()`, will depend on the history being used. 22 | 23 | #### Examples 24 | 25 | ```js 26 | // Given URL /page?foo=bar&baz=123&jim=bo 27 | multiReplaceInUrlQuery({ foo: 'test', baz: '99' }); 28 | // URL is now /page?foo=test&baz=99&jim=bo 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/api/multiReplaceInUrlQueryFromAction.md: -------------------------------------------------------------------------------- 1 | ### `multiReplaceInUrlQueryFromAction(action, [location])` 2 | 3 | A helper function for when you want to use redux `dispatch` to handle your URL updating actions, an optional way of combining Redux with React URL Query. It expects the action to have the form: 4 | 5 | ``` 6 | { 7 | payload: { 8 | encodedQuery: { 9 | queryParamName: encodedValue, 10 | ... 11 | } 12 | } 13 | ... 14 | } 15 | ``` 16 | 17 | This function is typically only useful when using your own urlReducer, as shown in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). 18 | 19 | Uses replace to change the URL, which means nothing gets pushed on to the history stack, so the back button will not be able to return you to the previous state. 20 | 21 | #### Arguments 22 | 23 | 1. `action` (*Object*): An action, typically from [`urlReplaceInAction`](urlReplaceInAction.md). Shape described above. 24 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 25 | 26 | #### Returns 27 | 28 | (*void*): It does not return anything. 29 | 30 | 31 | #### Examples 32 | 33 | ```js 34 | // create an action creator 35 | const changeFoo = urlMultiReplaceInAction( 36 | 'CHANGE_FOO', 37 | (newQuery) => ({ 38 | foo: encode(UrlQueryParamTypes.number, newQuery.foo), 39 | }) 40 | ); 41 | 42 | // call an action creator to get an action 43 | const action = changeFoo({foo: 94}); 44 | 45 | // typically in a reducer, call this helper to update the URL 46 | // e.g. URL was /page?foo=123&bar=baz 47 | multiReplaceInUrlQueryFromAction(action) 48 | // e.g. URL now is /page?foo=94&bar=baz 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/api/pushInUrlQuery.md: -------------------------------------------------------------------------------- 1 | ### `pushInUrlQuery(queryParam, encodedValue, [location])` 2 | 3 | Updates the URL to have the specified query parameter's (`queryParam`) value set to `encodedValue`, while keeping all the other query parameters the same. Uses push to change the URL, which means the new state will be pushed on to the history stack, so the back button will be able to return you to the previous state. 4 | 5 | #### Arguments 6 | 7 | 1. `queryParam` (*String*): The name of the query parameter to update. 8 | 1. `encodedValue` (*String*): The value to set the query parameter to have in the URL. 9 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 10 | 11 | #### Returns 12 | 13 | (*Any*): The result of `history.push()`, will depend on the history being used. 14 | 15 | #### Examples 16 | 17 | ```js 18 | // Given URL /page?foo=bar&baz=123&jim=bo 19 | pushInUrlQuery('foo', 'test'); 20 | // URL is now /page?foo=test&baz=123&jim=bo 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/api/pushInUrlQueryFromAction.md: -------------------------------------------------------------------------------- 1 | ### `pushInUrlQueryFromAction(action, [location])` 2 | 3 | A helper function for when you want to use redux `dispatch` to handle your URL updating actions, an optional way of combining Redux with React URL Query. It expects the action to have the form: 4 | 5 | ``` 6 | { 7 | payload: { 8 | queryParam: String, 9 | encodedValue: String, 10 | } 11 | ... 12 | } 13 | ``` 14 | 15 | This function is typically only useful when using your own urlReducer, as shown in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). 16 | 17 | Uses push to change the URL, which means the new state will be pushed on to the history stack, so the back button will be able to return you to the previous state. 18 | 19 | #### Arguments 20 | 21 | 1. `action` (*Object*): An action, typically from [`urlPushInAction`](urlPushInAction.md). Shape described above. 22 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 23 | 24 | #### Returns 25 | 26 | (*void*): It does not return anything. 27 | 28 | 29 | #### Examples 30 | 31 | ```js 32 | // create an action creator 33 | const changeFoo = urlPushInAction('CHANGE_FOO', 'foo', UrlQueryParamTypes.number); 34 | 35 | // call an action creator to get an action 36 | const action = changeFoo(94); 37 | 38 | // typically in a reducer, call this helper to update the URL 39 | // e.g. URL was /page?foo=123&bar=baz 40 | pushInUrlQueryFromAction(action) 41 | // e.g. URL now is /page?foo=94&bar=baz 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/api/pushUrlQuery.md: -------------------------------------------------------------------------------- 1 | ### `pushUrlQuery(newQuery, [location])` 2 | 3 | Updates the URL to have `newQuery` as the new set of query parameters, completely replacing what was there previously. Uses push to change the URL, which means the new state will be pushed on to the history stack, so the back button will be able to return you to the previous state. 4 | 5 | #### Arguments 6 | 7 | 1. `newQuery` (*Object*): An object mapping query parameter names to values (*String*). 8 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 9 | 10 | #### Returns 11 | 12 | (*Any*): The result of `history.push()`, will depend on the history being used. 13 | 14 | #### Examples 15 | 16 | ```js 17 | // Given URL /page?foo=bar&baz=123&jim=bo 18 | pushUrlQuery({ foo: 'test', jim: 'vlan' }); 19 | // URL is now /page?foo=test&jim=vlan 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/api/pushUrlQueryFromAction.md: -------------------------------------------------------------------------------- 1 | ### `pushUrlQueryFromAction(action, [location])` 2 | 3 | A helper function for when you want to use redux `dispatch` to handle your URL updating actions, an optional way of combining Redux with React URL Query. It expects the action to have the form: 4 | 5 | ``` 6 | { 7 | payload: { 8 | encodedQuery: { 9 | queryParam1: encodedValue1, 10 | queryParam2: encodedValue2, 11 | ... 12 | } 13 | } 14 | ... 15 | } 16 | ``` 17 | 18 | This function is typically only useful when using your own urlReducer, as shown in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). 19 | 20 | Uses push to change the URL, which means the new state will be pushed on to the history stack, so the back button will be able to return you to the previous state. 21 | 22 | 23 | #### Arguments 24 | 25 | 1. `action` (*Object*): An action, typically from [`urlPushAction`](urlPushAction.md). Shape described above. 26 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 27 | 28 | #### Returns 29 | 30 | (*void*): It does not return anything. 31 | 32 | 33 | #### Examples 34 | 35 | ```js 36 | // create an action creator 37 | const changeFoo = urlPushAction('CHANGE_FOO'); 38 | 39 | // call an action creator to get an action 40 | const action = changeFoo({ foo: 'test' }); 41 | 42 | // typically in a reducer, call this helper to update the URL 43 | // e.g. URL was /page?foo=ing&bar=baz 44 | pushUrlQueryFromAction(action) 45 | // e.g. URL now is /page?foo=test 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/api/replaceInUrlQuery.md: -------------------------------------------------------------------------------- 1 | ### `replaceInUrlQuery(queryParam, encodedValue, [location])` 2 | 3 | Updates the URL to have the specified query parameter's (`queryParam`) value set to `encodedValue`, while keeping all the other query parameters the same. Uses replace to change the URL, which means nothing gets pushed on to the history stack, so the back button will not be able to return you to the previous state. 4 | 5 | #### Arguments 6 | 7 | 1. `queryParam` (*String*): The name of the query parameter to update. 8 | 1. `encodedValue` (*String*): The value to set the query parameter to have in the URL. 9 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 10 | 11 | #### Returns 12 | 13 | (*Any*): The result of `history.replace()`, will depend on the history being used. 14 | 15 | #### Examples 16 | 17 | ```js 18 | // Given URL /page?foo=bar&baz=123&jim=bo 19 | replaceInUrlQuery('foo', 'test'); 20 | // URL is now /page?foo=test&baz=123&jim=bo 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/api/replaceInUrlQueryFromAction.md: -------------------------------------------------------------------------------- 1 | ### `replaceInUrlQueryFromAction(action, [location])` 2 | 3 | A helper function for when you want to use redux `dispatch` to handle your URL updating actions, an optional way of combining Redux with React URL Query. It expects the action to have the form: 4 | 5 | ``` 6 | { 7 | payload: { 8 | queryParam: String, 9 | encodedValue: String, 10 | } 11 | ... 12 | } 13 | ``` 14 | 15 | This function is typically only useful when using your own urlReducer, as shown in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). 16 | 17 | Uses replace to change the URL, which means nothing gets pushed on to the history stack, so the back button will not be able to return you to the previous state. 18 | 19 | #### Arguments 20 | 21 | 1. `action` (*Object*): An action, typically from [`urlReplaceInAction`](urlReplaceInAction.md). Shape described above. 22 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 23 | 24 | #### Returns 25 | 26 | (*void*): It does not return anything. 27 | 28 | 29 | #### Examples 30 | 31 | ```js 32 | // create an action creator 33 | const changeFoo = urlReplaceInAction('CHANGE_FOO', 'foo', UrlQueryParamTypes.number); 34 | 35 | // call an action creator to get an action 36 | const action = changeFoo(94); 37 | 38 | // typically in a reducer, call this helper to update the URL 39 | // e.g. URL was /page?foo=123&bar=baz 40 | replaceInUrlQueryFromAction(action) 41 | // e.g. URL now is /page?foo=94&bar=baz 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/api/replaceUrlQuery.md: -------------------------------------------------------------------------------- 1 | ### `replaceUrlQuery(newQuery, [location])` 2 | 3 | Updates the URL to have `newQuery` as the new set of query parameters, completely replacing what was there previously. Uses replace to change the URL, which means nothing gets pushed on to the history stack, so the back button will not be able to return you to the previous state. 4 | 5 | #### Arguments 6 | 7 | 1. `newQuery` (*Object*): An object mapping query parameter names to values (*String*). 8 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 9 | 10 | #### Returns 11 | 12 | (*Any*): The result of `history.replace()`, will depend on the history being used. 13 | 14 | #### Examples 15 | 16 | ```js 17 | // Given URL /page?foo=bar&baz=123&jim=bo 18 | replaceUrlQuery({ foo: 'test', jim: 'vlan' }); 19 | // URL is now /page?foo=test&jim=vlan 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/api/replaceUrlQueryFromAction.md: -------------------------------------------------------------------------------- 1 | ### `replaceUrlQueryFromAction(action, [location])` 2 | 3 | A helper function for when you want to use redux `dispatch` to handle your URL updating actions, an optional way of combining Redux with React URL Query. It expects the action to have the form: 4 | 5 | ``` 6 | { 7 | payload: { 8 | encodedQuery: { 9 | queryParam1: encodedValue1, 10 | queryParam2: encodedValue2, 11 | ... 12 | } 13 | } 14 | ... 15 | } 16 | ``` 17 | 18 | This function is typically only useful when using your own urlReducer, as shown in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). 19 | 20 | Uses replace to change the URL, which means nothing gets pushed on to the history stack, so the back button will not be able to return you to the previous state. 21 | 22 | #### Arguments 23 | 24 | 1. `action` (*Object*): An action, typically from [`urlReplaceAction`](urlReplaceAction.md). Shape described above. 25 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 26 | 27 | #### Returns 28 | 29 | (*void*): It does not return anything. 30 | 31 | 32 | #### Examples 33 | 34 | ```js 35 | // create an action creator 36 | const changeFoo = urlReplaceAction('CHANGE_FOO'); 37 | 38 | // call an action creator to get an action 39 | const action = changeFoo({ foo: 'test' }); 40 | 41 | // typically in a reducer, call this helper to update the URL 42 | // e.g. URL was /page?foo=ing&bar=baz 43 | replaceUrlQueryFromAction(action) 44 | // e.g. URL now is /page?foo=test 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/api/subquery.md: -------------------------------------------------------------------------------- 1 | ### `subquery(query, ...params)` 2 | 3 | Given a query object, returns an object with only a subset of the parameters in it. Similar to lodash's `pick`. A similar function that takes a list of parameters to exclude is [`subqueryOmit`](subqueryOmit.md). 4 | 5 | #### Arguments 6 | 7 | 1. `query` (*Object*): The query parameters object, mapping from query param to encoded value. 8 | 1. `...params` (*String*): The list of query parameters to keep in the returned result. These values should match a subset of the keys in `query`. 9 | 10 | #### Returns 11 | 12 | (*type*): Returns `query` with only the keys defined in `params`. 13 | 14 | #### Examples 15 | 16 | ```js 17 | subquery({ a: 'one', b: 'two', c: 'three' }, 'a', 'c'); 18 | // === { a: 'one', c: 'three' } 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/api/subqueryOmit.md: -------------------------------------------------------------------------------- 1 | ### `subqueryOmit(query, ...omitParams)` 2 | 3 | Given a query object, returns an object with only a subset of the parameters in it. Similar to lodash's `omit`. A similar function that takes a list of parameters to include is [`subquery`](subquery.md). 4 | 5 | #### Arguments 6 | 7 | 1. `query` (*Object*): The query parameters object, mapping from query param to encoded value. 8 | 1. `...omitParams` (*String*): The list of query parameters to exclude from the returned result. These values should match a subset of the keys in `query`. 9 | 10 | #### Returns 11 | 12 | (*type*): Returns `query` with only the keys that are not specified in `omitParams`. 13 | 14 | #### Examples 15 | 16 | ```js 17 | subqueryOmit({ a: 'one', b: 'two', c: 'three' }, 'a', 'c'); 18 | // === { b: 'two' } 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/api/urlAction.md: -------------------------------------------------------------------------------- 1 | ### `urlAction(actionType, [payload], [meta])` 2 | 3 | A helper function to create action creators that can create actions interpretable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md). The standard format of an action produced by the action creators this function creates is: 4 | 5 | ```js 6 | { 7 | type: actionType, 8 | meta: { 9 | ...meta, 10 | urlQuery: true 11 | }, 12 | payload: payload 13 | } 14 | ``` 15 | 16 | 17 | #### Arguments 18 | 19 | 1. `actionType` (*String*): The standard redux action type, maps to `type` in the action. 20 | 1. [`payload`] (*Function*): Takes the arguments provided from the action creator and produces what ends up in `payload` in the action. Can return any type. It defaults to the identity function. 21 | 1. [`meta`] (*Function*): Takes the arguments provided from the action creator and produces what ends up in `meta` in the action. It must return an object, otherwise it will show up under `meta.value`. 22 | 23 | #### Returns 24 | 25 | (*Function*): An action creator that will produce an action that is recognizable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md). 26 | 27 | #### Examples 28 | 29 | ```js 30 | const changeFoo = urlAction('CHANGE_FOO', foo => ({ encodedValue: String(foo) })); 31 | dispatch(changeFoo(94)); 32 | /* 33 | dispatches action of form: 34 | { 35 | type: 'CHANGE_FOO', 36 | meta: { 37 | urlQuery: true 38 | }, 39 | payload: { 40 | encodedValue: '94' 41 | } 42 | } 43 | */ 44 | ``` 45 | 46 | ```js 47 | const changeBar = urlAction( 48 | 'CHANGE_BAR', 49 | bar => bar, 50 | bar => ({ updateType: UrlUpdateTypes.pushIn }) 51 | ); 52 | dispatch(changeBar('some-bar-value')); 53 | /* 54 | dispatches action of form: 55 | { 56 | type: 'CHANGE_BAR', 57 | meta: { 58 | urlQuery: true, 59 | updateType: UrlUpdateTypes.pushIn 60 | }, 61 | payload: 'some-bar-value' 62 | } 63 | */ 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/api/urlMultiPushInAction.md: -------------------------------------------------------------------------------- 1 | ### `urlMultiPushInAction(actionType, [encodeQuery])` 2 | 3 | A helper function to create action creators that can create actions interpretable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md) to push a change to several query parameters into the URL, adding an entry to the history stack. The standard format of an action produced by the action creators this function creates is: 4 | 5 | ```js 6 | { 7 | type: actionType, 8 | meta: { 9 | updateType: UrlUpdateTypes.multiPushIn, 10 | urlQuery: true 11 | }, 12 | payload: { 13 | encodedQuery: Object, 14 | decodedQuery: Object, 15 | } 16 | } 17 | ``` 18 | 19 | 20 | #### Arguments 21 | 22 | 1. `actionType` (*String*): The standard redux action type, maps to `type` in the action. 23 | 1. [`encodeQuery`] (*Function*): A function that takes in a query object and maps it to an *Object* where the keys represent query parameters and the values represent encoded *String* values. 24 | 25 | #### Returns 26 | 27 | (*Function*): An action creator that will produce an action that is recognizable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md). 28 | 29 | #### Remarks 30 | 31 | * The default [urlQueryReducer](urlQueryReducer.md) provided by React URL Query interprets the `updateType` property in the meta of the action to update the URL accordingly. If you are not using it, you'll need to do so manually. 32 | 33 | #### Examples 34 | 35 | ```js 36 | const changeFooBar = urlMultiPushInAction( 37 | 'CHANGE_FOO_BAR', 38 | query => ({ foo: String(query.foo), bar: query.bar }) 39 | ); 40 | dispatch(changeFooBar({ foo: 137, bar: 'baz' })); 41 | /* 42 | dispatches action of form: 43 | { 44 | type: 'CHANGE_FOO_BAR', 45 | meta: { 46 | urlQuery: true, 47 | updateType: UrlUpdateTypes.multiPushIn 48 | }, 49 | payload: { 50 | encodedQuery: { foo: '137', bar: 'baz' }, 51 | decodedQuery: { foo: 137, bar: 'baz' } 52 | } 53 | } 54 | */ 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/api/urlMultiReplaceInAction.md: -------------------------------------------------------------------------------- 1 | ### `urlMultiReplaceInAction(actionType, [encodeQuery])` 2 | 3 | A helper function to create action creators that can create actions interpretable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md) to set new query parameters into the URL, replacing what was there previously without adding an entry to the history stack. The standard format of an action produced by the action creators this function creates is: 4 | 5 | ```js 6 | { 7 | type: actionType, 8 | meta: { 9 | updateType: UrlUpdateTypes.multiReplaceIn, 10 | urlQuery: true 11 | }, 12 | payload: { 13 | encodedQuery: Object, 14 | decodedQuery: Object, 15 | } 16 | } 17 | ``` 18 | 19 | 20 | #### Arguments 21 | 22 | 1. `actionType` (*String*): The standard redux action type, maps to `type` in the action. 23 | 1. [`encodeQuery`] (*Function*): A function that takes in a query object and maps it to an *Object* where the keys represent query parameters and the values represent encoded *String* values. 24 | 25 | #### Returns 26 | 27 | (*Function*): An action creator that will produce an action that is recognizable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md). 28 | 29 | #### Remarks 30 | 31 | * The default [urlQueryReducer](urlQueryReducer.md) provided by React URL Query interprets the `updateType` property in the meta of the action to update the URL accordingly. If you are not using it, you'll need to do so manually. 32 | 33 | #### Examples 34 | 35 | ```js 36 | const changeFooBar = urlMultiReplaceInAction( 37 | 'CHANGE_FOO_BAR', 38 | query => ({ foo: String(query.foo), bar: query.bar }) 39 | ); 40 | dispatch(changeFooBar({ foo: 137, bar: 'baz' })); 41 | /* 42 | dispatches action of form: 43 | { 44 | type: 'CHANGE_FOO_BAR', 45 | meta: { 46 | urlQuery: true, 47 | updateType: UrlUpdateTypes.multiReplaceIn 48 | }, 49 | payload: { 50 | encodedQuery: { foo: '137', bar: 'baz' }, 51 | decodedQuery: { foo: 137, bar: 'baz' } 52 | } 53 | } 54 | */ 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/api/urlPushAction.md: -------------------------------------------------------------------------------- 1 | ### `urlPushAction(actionType, [encodeQuery])` 2 | 3 | A helper function to create action creators that can create actions interpretable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md) to push a new query into the URL, replacing what was there previously and adding an entry to the history stack. The standard format of an action produced by the action creators this function creates is: 4 | 5 | ```js 6 | { 7 | type: actionType, 8 | meta: { 9 | updateType: UrlUpdateTypes.push, 10 | urlQuery: true 11 | }, 12 | payload: { 13 | encodedQuery: Object, 14 | decodedQuery: Object, 15 | } 16 | } 17 | ``` 18 | 19 | 20 | #### Arguments 21 | 22 | 1. `actionType` (*String*): The standard redux action type, maps to `type` in the action. 23 | 1. [`encodeQuery`] (*Function*): A function that takes in a query object and maps it to an *Object* where the keys represent query parameters and the values represent encoded *String* values. 24 | 25 | #### Returns 26 | 27 | (*Function*): An action creator that will produce an action that is recognizable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md). 28 | 29 | #### Remarks 30 | 31 | * The default [urlQueryReducer](urlQueryReducer.md) provided by React URL Query interprets the `updateType` property in the meta of the action to update the URL accordingly. If you are not using it, you'll need to do so manually. 32 | 33 | #### Examples 34 | 35 | ```js 36 | const changeFooBar = urlPushAction( 37 | 'CHANGE_FOO_BAR', 38 | query => ({ foo: String(query.foo), bar: query.bar }) 39 | ); 40 | dispatch(changeFooBar({ foo: 137, bar: 'baz' })); 41 | /* 42 | dispatches action of form: 43 | { 44 | type: 'CHANGE_FOO_BAR', 45 | meta: { 46 | urlQuery: true, 47 | updateType: UrlUpdateTypes.push 48 | }, 49 | payload: { 50 | encodedQuery: { foo: '137', bar: 'baz' }, 51 | decodedQuery: { foo: 137, bar: 'baz' } 52 | } 53 | } 54 | */ 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/api/urlPushInAction.md: -------------------------------------------------------------------------------- 1 | ### `urlPushInAction(actionType, queryParam, valueType)` 2 | 3 | A helper function to create action creators that can create actions interpretable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md) to push a change to one query parameter into the URL, adding an entry to the history stack. The standard format of an action produced by the action creators this function creates is: 4 | 5 | ```js 6 | { 7 | type: actionType, 8 | meta: { 9 | updateType: UrlUpdateTypes.pushIn, 10 | urlQuery: true 11 | }, 12 | payload: { 13 | queryParam: String, 14 | encodedValue: String, 15 | decodedValue: Any, 16 | type: valueType, 17 | } 18 | } 19 | ``` 20 | 21 | 22 | #### Arguments 23 | 24 | 1. `actionType` (*String*): The standard redux action type, maps to `type` in the action. 25 | 1. `queryParam` (*String*): The name of the query parameter to update in the URL. 26 | 1. `valueType` (*String|Function|Object*): The type of the data. This is used to encode the data via [`encode`](Serialize.md#encode). 27 | 28 | #### Returns 29 | 30 | (*Function*): An action creator that will produce an action that is recognizable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md). 31 | 32 | #### Remarks 33 | 34 | * The default [urlQueryReducer](urlQueryReducer.md) provided by React URL Query interprets the `updateType` property in the meta of the action to update the URL accordingly. If you are not using it, you'll need to do so manually. 35 | 36 | #### Examples 37 | 38 | ```js 39 | const changeFoo = urlPushInAction('CHANGE_FOO', 'foo', UrlQueryParamTypes.number); 40 | dispatch(changeFoo(137)); 41 | /* 42 | dispatches action of form: 43 | { 44 | type: 'CHANGE_FOO', 45 | meta: { 46 | urlQuery: true, 47 | updateType: UrlUpdateTypes.pushIn 48 | }, 49 | payload: { 50 | queryParam: 'foo', 51 | encodedValue: '137', 52 | decodeValue: 137, 53 | type: UrlQueryParamTypes.number 54 | } 55 | } 56 | */ 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/api/urlQueryDecoder.md: -------------------------------------------------------------------------------- 1 | ### `urlQueryDecoder(config)` 2 | 3 | A helper function to create a query decoder from [`urlPropsQueryConfig`](addUrlProps.md#urlPropsQueryConfig). 4 | 5 | 6 | #### Arguments 7 | 8 | 1. `config` (*Object*): The `urlPropsQueryConfig` object, [see urlPropsQueryConfig for details](addUrlProps.md#urlPropsQueryConfig). 9 | 10 | #### Returns 11 | 12 | (*Function*): A function which, given an object with encoded parameters, returns the decoded equivalent. It compares against cached values to see 13 | if decoding is necessary or if it can reuse old values. 14 | 15 | #### Examples 16 | 17 | ```js 18 | const urlPropsQueryConfig = { 19 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 20 | bar: { type: UrlQueryParamTypes.string }, 21 | }; 22 | 23 | const decode = urlQueryDecoder(urlPropsQueryConfig); 24 | 25 | decode({ fooInUrl: '137', bar: 'str' }); 26 | // === { foo: 137, bar: 'str' } 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/api/urlQueryEncoder.md: -------------------------------------------------------------------------------- 1 | ### `urlQueryEncoder(config)` 2 | 3 | A helper function to create a query encoder from [`urlPropsQueryConfig`](addUrlProps.md#urlPropsQueryConfig). 4 | 5 | 6 | #### Arguments 7 | 8 | 1. `config` (*Object*): The `urlPropsQueryConfig` object, [see urlPropsQueryConfig for details](addUrlProps.md#urlPropsQueryConfig). 9 | 10 | #### Returns 11 | 12 | (*Function*): A function which, given an object with decoded parameters, returns the encoded equivalent. 13 | 14 | #### Examples 15 | 16 | ```js 17 | const urlPropsQueryConfig = { 18 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 19 | bar: { type: UrlQueryParamTypes.string }, 20 | }; 21 | 22 | const encode = urlQueryEncoder(urlPropsQueryConfig); 23 | 24 | encode({ foo: 137, bar: 'str' }); 25 | // === { fooInUrl: '137', bar: 'str' } 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/api/urlQueryMiddleware.md: -------------------------------------------------------------------------------- 1 | ### `urlQueryMiddleware([options])` 2 | 3 | If you want to have URL update actions dispatched via Redux as shown in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions), you need to use this Redux middleware. It intercepts actions where `meta.urlQuery` is true and passes them to the configured `reducer` (by default [urlQueryReducer](urlQueryReducer)) to be interpreted. 4 | 5 | If you are using [react-router-redux](https://github.com/reactjs/react-router-redux), you will want to read `location` from the store. The default setting for `readLocationFromStore` does this. If you want to read `location` from another place in the store, specify a different `readLocationStore` in the options. 6 | 7 | Default settings result in URL actions not making it to the final Redux store, so listeners to the store won't be notified of a change. This is because usually there is something else listening for changes to the URL and causing the components to update, so we'd end up with an unnecessary second render. If you want the store to be notified, you need to set the `shortcircuit` option to `false`. 8 | 9 | 10 | #### Arguments 11 | 12 | 1. [`options`] (*Object*): The options for configuring the middleware. They include: 13 | * `reducer` (*Function*): A function that takes `action, location` and updates the URL. If not specified in `options`, it is read from the [configuration](configureUrlQuery.md), and finally defaults to [urlQueryReducer](urlQueryReducer) if no value is provided. 14 | * `readLocationFromStore` (*Function*): A function that takes `state`, the Redux store state, and returns the current URL location to pass to the reducer. If not specified, defaults to reading from `react-router-redux`'s default location. 15 | * `shortcircuit` (*Boolean*): A flag on whether or not we should pass on to the next Redux middleware, and thus eventually receive notifications from the Redux store. 16 | 17 | #### Returns 18 | 19 | (*Function*): A redux middleware function. 20 | 21 | #### Remarks 22 | 23 | * You must instantiate `urlQueryMiddleware` with options. You cannot run `applyMiddleware(urlQueryMiddleware)`. If you do not want to supply options, you can run: `applyMiddleware(urlQueryMiddleware())`. 24 | 25 | #### Examples 26 | 27 | ```js 28 | import { createStore, applyMiddleware } from 'redux'; 29 | import { urlQueryMiddleware } from 'react-url-query'; 30 | import './myUrlQueryReducer'; 31 | import './rootReducer'; 32 | 33 | // get the new create store with middleware 34 | const createStoreWithMiddleware = applyMiddleware( 35 | urlQueryMiddleware({ 36 | reducer: myUrlQueryReducer 37 | }) 38 | )(createStore); 39 | 40 | // create the store 41 | const store = createStoreWithMiddleware(rootReducer); 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/api/urlQueryReducer.md: -------------------------------------------------------------------------------- 1 | ### `urlQueryReducer(action, [location])` 2 | 3 | This is the default url query reducer that ships with React URL Query. It is only used when you integrating with Redux via [`urlQueryMiddleware`](urlQueryMiddleware.md) as shown in [this example](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions). Note that you do not need to use the middleware and the reducer when integrating with Redux, they're only necessary if you want to dispatch actions to change the URL. 4 | 5 | This reducer reads in the `updateType` from `action.meta` and modifies the URL accordingly by delegating to the corresponding `fromAction` function (e.g., [`replaceInUrlQueryFromAction`](replaceInUrlQueryFromAction.md)). See [`UrlUpdateTypes`](UrlUpdateTypes.md) for all supported update types. 6 | 7 | NOTE: This is *NOT* a Redux reducer. It does not map from (state, action) -> state. 8 | Instead it "reduces" actions into URL query parameter state. *NOT* redux state. 9 | 10 | #### Arguments 11 | 12 | 1. `action` (*Object*): An action, typically from [`urlAction`](urlAction.md) or similar. 13 | 1. [`location`] (*Object*): The location from which the current URL state should be read. If not provided, `location` is read from the configured `history` or the `window`. 14 | 15 | #### Returns 16 | 17 | (*void*): It does not return anything. 18 | 19 | #### Examples 20 | 21 | This is typically just used by `urlQueryMiddleware` since it is the default `reducer` option, but if you provide your own reducer, you can defer to this reducer after handling a subset of the URL update actions. 22 | 23 | ```js 24 | import { 25 | replaceInUrlQueryFromAction, 26 | pushUrlQuery, 27 | pushInUrlQueryFromAction, 28 | urlQueryReducer as defaultUrlQueryReducer 29 | } from 'react-url-query'; 30 | 31 | function myUrlQueryReducer(action) { 32 | switch (action.type) { 33 | case CHANGE_FOO: { 34 | const foo = action.payload.decodedValue; 35 | if (foo < 300) { 36 | // pushing new URL state since foo < 300 37 | pushInUrlQueryFromAction(action); 38 | } else if (foo > 700) { 39 | // pushing entire query into URL state since foo > 700: { foo, party } 40 | const newQuery = { 41 | [action.payload.queryParam]: [action.payload.encodedValue], 42 | party: Math.ceil(Math.random() * 100) 43 | }; 44 | pushUrlQuery(newQuery); 45 | } else { 46 | // just replace this value otherwise (normal behavior) 47 | replaceInUrlQueryFromAction(action); 48 | } 49 | break; 50 | } 51 | default: 52 | // all other actions can be handled by the default reducer 53 | defaultUrlQueryReducer(action); 54 | break; 55 | } 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/api/urlReplaceAction.md: -------------------------------------------------------------------------------- 1 | ### `urlReplaceAction(actionType, [encodeQuery])` 2 | 3 | A helper function to create action creators that can create actions interpretable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md) to set a new query into the URL, replacing what was there previously without adding an entry to the history stack. The standard format of an action produced by the action creators this function creates is: 4 | 5 | ```js 6 | { 7 | type: actionType, 8 | meta: { 9 | updateType: UrlUpdateTypes.replace, 10 | urlQuery: true 11 | }, 12 | payload: { 13 | encodedQuery: Object, 14 | decodedQuery: Object, 15 | } 16 | } 17 | ``` 18 | 19 | 20 | #### Arguments 21 | 22 | 1. `actionType` (*String*): The standard redux action type, maps to `type` in the action. 23 | 1. [`encodeQuery`] (*Function*): A function that takes in a query object and maps it to an *Object* where the keys represent query parameters and the values represent encoded *String* values. 24 | 25 | #### Returns 26 | 27 | (*Function*): An action creator that will produce an action that is recognizable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md). 28 | 29 | #### Remarks 30 | 31 | * The default [urlQueryReducer](urlQueryReducer.md) provided by React URL Query interprets the `updateType` property in the meta of the action to update the URL accordingly. If you are not using it, you'll need to do so manually. 32 | 33 | #### Examples 34 | 35 | ```js 36 | const changeFooBar = urlReplaceAction( 37 | 'CHANGE_FOO_BAR', 38 | query => ({ foo: String(query.foo), bar: query.bar }) 39 | ); 40 | dispatch(changeFooBar({ foo: 137, bar: 'baz' })); 41 | /* 42 | dispatches action of form: 43 | { 44 | type: 'CHANGE_FOO_BAR', 45 | meta: { 46 | urlQuery: true, 47 | updateType: UrlUpdateTypes.replace 48 | }, 49 | payload: { 50 | encodedQuery: { foo: '137', bar: 'baz' }, 51 | decodedQuery: { foo: 137, bar: 'baz' } 52 | } 53 | } 54 | */ 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/api/urlReplaceInAction.md: -------------------------------------------------------------------------------- 1 | ### `urlReplaceInAction(actionType, queryParam, valueType)` 2 | 3 | A helper function to create action creators that can create actions interpretable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md) to change one query parameter in the URL without adding an entry to the history stack. The standard format of an action produced by the action creators this function creates is: 4 | 5 | ```js 6 | { 7 | type: actionType, 8 | meta: { 9 | updateType: UrlUpdateTypes.replaceIn, 10 | urlQuery: true 11 | }, 12 | payload: { 13 | queryParam: String, 14 | encodedValue: String, 15 | decodedValue: Any, 16 | type: valueType, 17 | } 18 | } 19 | ``` 20 | 21 | 22 | #### Arguments 23 | 24 | 1. `actionType` (*String*): The standard redux action type, maps to `type` in the action. 25 | 1. `queryParam` (*String*): The name of the query parameter to update in the URL. 26 | 1. `valueType` (*String|Function|Object*): The type of the data. This is used to encode the data via [`encode`](Serialize.md#encode). 27 | 28 | #### Returns 29 | 30 | (*Function*): An action creator that will produce an action that is recognizable by [urlQueryMiddleware](urlQueryMiddleware.md) and [urlQueryReducer](urlQueryReducer.md). 31 | 32 | #### Remarks 33 | 34 | * The default [urlQueryReducer](urlQueryReducer.md) provided by React URL Query interprets the `updateType` property in the meta of the action to update the URL accordingly. If you are not using it, you'll need to do so manually. 35 | 36 | #### Examples 37 | 38 | ```js 39 | const changeFoo = urlReplaceInAction('CHANGE_FOO', 'foo', UrlQueryParamTypes.number); 40 | dispatch(changeFoo(137)); 41 | /* 42 | dispatches action of form: 43 | { 44 | type: 'CHANGE_FOO', 45 | meta: { 46 | urlQuery: true, 47 | updateType: UrlUpdateTypes.replaceIn 48 | }, 49 | payload: { 50 | queryParam: 'foo', 51 | encodedValue: '137', 52 | decodeValue: 137, 53 | type: UrlQueryParamTypes.number 54 | } 55 | } 56 | */ 57 | ``` 58 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | - [basic](https://github.com/pbeshai/react-url-query/tree/master/examples/basic) - Basic usage of React URL Query without any third party tools 4 | - [basic-mapUrlToProps](https://github.com/pbeshai/react-url-query/tree/master/examples/basic) - Basic usage of React URL Query without any third party tools, uses alternative approach of `mapUrlToProps` instead of `urlPropsQueryConfig`. 5 | - [redux](https://github.com/pbeshai/react-url-query/tree/master/examples/redux) - Integration with Redux 6 | - [redux](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions) - Alternative integration with Redux using dispatched actions 7 | - [react-router-v2-and-redux](https://github.com/pbeshai/react-url-query/tree/master/examples/react-router-v2-and-redux) - Integration with React Router v2 and Redux 8 | - [react-router-v4-and-redux](https://github.com/pbeshai/react-url-query/tree/master/examples/react-router-v4-and-redux) - Integration with React Router v4 and Redux 9 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/README.md: -------------------------------------------------------------------------------- 1 | # Basic with mapUrlToProps Example 2 | 3 | Example of using react-url-query. Start it with `npm start`. 4 | 5 | Shows a basic configuration to get React URL Query working in your application when you are not using React Router or Redux. Uses alternative approach of `mapUrlToProps` instead of `urlPropsQueryConfig`. The steps are: 6 | 7 | 1. Use a [history](https://github.com/mjackson/history) of some sort to control pushing or replacing items in the browser's history stack. Be sure to listen for changes to the history and force an update when they occur (see [App.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic-mapUrlToProps/src/App.js)) 8 | 1. Configure React URL Query to use the history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic-mapUrlToProps/src/index.js)). 9 | 1. Use a `mapUrlToProps`, `mapUrlChangeHandlersToProps` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic-mapUrlToProps/src/MainPage.js)). 10 | 11 | 12 | ## Available Scripts 13 | 14 | In the project directory, you can run: 15 | 16 | ### `npm start` 17 | 18 | Runs the app in the development mode.
19 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 20 | 21 | The page will reload if you make edits.
22 | You will also see any lint errors in the console. 23 | 24 | ### `npm run build` 25 | 26 | Builds the app for production to the `build` folder.
27 | It correctly bundles React in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes.
30 | Your app is ready to be deployed! 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | 43 | --- 44 | 45 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 46 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-basic-mapUrlToProps", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.0" 7 | }, 8 | "dependencies": { 9 | "history": "^4.2.0", 10 | "prop-types": "^15.5.9", 11 | "react": "^15.3.2", 12 | "react-dom": "^15.3.2", 13 | "react-url-query": "1.0.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pbeshai/react-url-query/fc1ea8bb6a90b3f8e6bda8093ad15535c6d4e43d/examples/basic-mapUrlToProps/public/favicon.ico -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example: Basic with mapUrlToProps - react-url-query 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import MainPage from './MainPage'; 3 | import history from './history'; 4 | 5 | class App extends Component { 6 | componentDidMount() { 7 | // force an update if the URL changes 8 | history.listen(() => this.forceUpdate()); 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |

react-url-query example: basic-mapUrlToProps

15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/src/MainPage.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { decode, encode, addUrlProps, UrlQueryParamTypes, replaceInUrlQuery } from 'react-url-query'; 4 | 5 | /** 6 | * Map from url query params to props. The values in `url` will still be encoded 7 | * as strings since we did not pass a `urlPropsQueryConfig` to addUrlProps. 8 | */ 9 | function mapUrlToProps(url, props) { 10 | return { 11 | foo: decode(UrlQueryParamTypes.number, url.fooInUrl), 12 | bar: url.bar, 13 | }; 14 | } 15 | 16 | /** 17 | * Manually specify how to deal with changes to URL query param props. 18 | * We do this since we are not using a urlPropsQueryConfig. 19 | */ 20 | function mapUrlChangeHandlersToProps(props) { 21 | return { 22 | onChangeFoo: (value) => replaceInUrlQuery('fooInUrl', encode(UrlQueryParamTypes.number, value)), 23 | onChangeBar: (value) => replaceInUrlQuery('bar', value), 24 | } 25 | } 26 | 27 | class MainPage extends PureComponent { 28 | static propTypes = { 29 | foo: PropTypes.number, 30 | bar: PropTypes.string, 31 | onChangeFoo: PropTypes.func, 32 | onChangeBar: PropTypes.func, 33 | } 34 | 35 | static defaultProps = { 36 | foo: 123, 37 | bar: 'bar', 38 | } 39 | 40 | render() { 41 | const { foo, bar, onChangeFoo, onChangeBar } = this.props; 42 | 43 | return ( 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | 57 | 58 | 59 | 60 | 61 | 66 | 67 | 68 |
foo{foo}(url query param) 52 | 55 |
bar{bar}(url query param) 62 | 65 |
69 |
70 | ); 71 | } 72 | } 73 | 74 | /** 75 | * We use the addUrlProps higher-order component to map URL query parameters 76 | * to props for MainPage. 77 | */ 78 | export default addUrlProps({ mapUrlToProps, mapUrlChangeHandlersToProps })(MainPage); 79 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/src/history.js: -------------------------------------------------------------------------------- 1 | import createHistory from 'history/createBrowserHistory'; 2 | 3 | const history = createHistory(); 4 | 5 | export default history; 6 | -------------------------------------------------------------------------------- /examples/basic-mapUrlToProps/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { configureUrlQuery } from 'react-url-query'; 4 | 5 | import App from './App'; 6 | import history from './history'; 7 | 8 | // link the history used in our app to url-query so it can update the URL with it. 9 | configureUrlQuery({ history }); 10 | 11 | ReactDOM.render( 12 | , 13 | document.getElementById('root') 14 | ); 15 | -------------------------------------------------------------------------------- /examples/basic/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /examples/basic/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # Basic Example 2 | 3 | Example of using react-url-query. Start it with `npm start`. 4 | 5 | Shows the bare minimum amount of work to get React URL Query working in your application when you are not using React Router or Redux. The basic steps are: 6 | 7 | 1. Use a [history](https://github.com/mjackson/history) of some sort to control pushing or replacing items in the browser's history stack. Be sure to listen for changes to the history and force an update when they occur (see [App.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic/src/App.js)) 8 | 1. Configure React URL Query to use the history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic/src/index.js)). 9 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/basic/src/MainPage.js)). 10 | 11 | 12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm start` 18 | 19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 21 | 22 | The page will reload if you make edits.
23 | You will also see any lint errors in the console. 24 | 25 | ### `npm run build` 26 | 27 | Builds the app for production to the `build` folder.
28 | It correctly bundles React in production mode and optimizes the build for the best performance. 29 | 30 | The build is minified and the filenames include the hashes.
31 | Your app is ready to be deployed! 32 | 33 | ### `npm run eject` 34 | 35 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 36 | 37 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 38 | 39 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 40 | 41 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 42 | 43 | 44 | --- 45 | 46 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 47 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-basic", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.1" 7 | }, 8 | "dependencies": { 9 | "history": "^4.2.0", 10 | "prop-types": "^15.5.9", 11 | "react": "^16.1.1", 12 | "react-dom": "^16.1.1", 13 | "react-url-query": "^1" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/basic/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pbeshai/react-url-query/fc1ea8bb6a90b3f8e6bda8093ad15535c6d4e43d/examples/basic/public/favicon.ico -------------------------------------------------------------------------------- /examples/basic/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example: Basic - react-url-query 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/basic/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import MainPage from './MainPage'; 3 | import history from './history'; 4 | 5 | class App extends Component { 6 | componentDidMount() { 7 | // force an update if the URL changes 8 | history.listen(() => this.forceUpdate()); 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |

react-url-query example: basic

15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /examples/basic/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/basic/src/MainPage.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { addUrlProps, UrlQueryParamTypes } from 'react-url-query'; 4 | 5 | /** 6 | * Specify how the URL gets decoded here. This is an object that takes the prop 7 | * name as a key, and a query param specifier as the value. The query param 8 | * specifier can have a `type`, indicating how to decode the value from the 9 | * URL, and a `queryParam` field that indicates which key in the query 10 | * parameters should be read (this defaults to the prop name if not provided). 11 | */ 12 | const urlPropsQueryConfig = { 13 | bar: { type: UrlQueryParamTypes.string }, 14 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 15 | }; 16 | 17 | class MainPage extends PureComponent { 18 | static propTypes = { 19 | bar: PropTypes.string, 20 | foo: PropTypes.number, 21 | // change handlers are automatically generated and passed if a config is provided 22 | // and `addChangeHandlers` isn't false. They use `replaceIn` by default, just 23 | // updating that single query parameter and keeping the other existing ones. 24 | onChangeFoo: PropTypes.func, 25 | onChangeBar: PropTypes.func, 26 | onChangeUrlQueryParams: PropTypes.func, 27 | } 28 | 29 | static defaultProps = { 30 | foo: 123, 31 | bar: 'bar', 32 | } 33 | 34 | render() { 35 | const { 36 | foo, bar, onChangeFoo, onChangeBar, onChangeUrlQueryParams 37 | } = this.props; 38 | 39 | return ( 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 52 | 53 | 54 | 55 | 56 | 57 | 62 | 63 | 64 | 72 | 73 | 74 |
foo{foo}(url query param) 48 | 51 |
bar{bar}(url query param) 58 | 61 |
65 | 71 |
75 |
76 | ); 77 | } 78 | } 79 | 80 | /** 81 | * We use the addUrlProps higher-order component to map URL query parameters 82 | * to props for MainPage. In this case the mapping happens automatically by 83 | * first decoding the URL query parameters based on the urlPropsQueryConfig. 84 | */ 85 | export default addUrlProps({ urlPropsQueryConfig })(MainPage); 86 | -------------------------------------------------------------------------------- /examples/basic/src/history.js: -------------------------------------------------------------------------------- 1 | import createHistory from 'history/createBrowserHistory'; 2 | 3 | const history = createHistory(); 4 | 5 | export default history; 6 | -------------------------------------------------------------------------------- /examples/basic/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { configureUrlQuery } from 'react-url-query'; 4 | 5 | import App from './App'; 6 | import history from './history'; 7 | 8 | // link the history used in our app to url-query so it can update the URL with it. 9 | configureUrlQuery({ history }); 10 | 11 | ReactDOM.render( 12 | , 13 | document.getElementById('root') 14 | ); 15 | -------------------------------------------------------------------------------- /examples/buildAll.js: -------------------------------------------------------------------------------- 1 | // From: https://github.com/reactjs/redux/blob/master/examples/buildAll.js 2 | /** 3 | * Runs an ordered set of commands within each of the build directories. 4 | */ 5 | 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const { spawnSync } = require('child_process'); 9 | 10 | const exampleDirs = fs.readdirSync(__dirname).filter(file => 11 | fs.statSync(path.join(__dirname, file)).isDirectory()); 12 | 13 | // Ordering is important here. `npm install` must come first. 14 | const cmdArgs = [ 15 | { cmd: 'npm', args: ['install'] }, 16 | { cmd: 'webpack', args: ['index.js'] }, 17 | ]; 18 | 19 | for (const dir of exampleDirs) { 20 | for (const cmdArg of cmdArgs) { 21 | // declare opts in this scope to avoid https://github.com/joyent/node/issues/9158 22 | const opts = { 23 | cwd: path.join(__dirname, dir), 24 | stdio: 'inherit', 25 | }; 26 | let result = {}; 27 | if (process.platform === 'win32') { 28 | result = spawnSync(`${cmdArg.cmd}.cmd`, cmdArg.args, opts); 29 | } else { 30 | result = spawnSync(cmdArg.cmd, cmdArg.args, opts); 31 | } 32 | if (result.status !== 0) { 33 | throw new Error('Building examples exited with non-zero'); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/README.md: -------------------------------------------------------------------------------- 1 | # React Router v2 and Redux Example 2 | 3 | Example of using react-url-query. Start it with `npm start`. 4 | 5 | 6 | Demonstrates how to use React URL Query with React Router v2 and Redux. This example also demonstrates using a custom type for a URL query parameter. The steps are: 7 | 8 | 1. Configure React URL Query to use the React Router's history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/react-router-v2-and-redux/src/index.js)). 9 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query. In this case, we wrap the connected component: `addUrlProps(...)(connect(...)(MyComponent))` (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/react-router-v2-and-redux/src/MainPage.js)). 10 | 11 | 12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm start` 18 | 19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 21 | 22 | The page will reload if you make edits.
23 | You will also see any lint errors in the console. 24 | 25 | ### `npm run build` 26 | 27 | Builds the app for production to the `build` folder.
28 | It correctly bundles React in production mode and optimizes the build for the best performance. 29 | 30 | The build is minified and the filenames include the hashes.
31 | Your app is ready to be deployed! 32 | 33 | ### `npm run eject` 34 | 35 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 36 | 37 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 38 | 39 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 40 | 41 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 42 | 43 | 44 | --- 45 | 46 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 47 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-redux", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.0" 7 | }, 8 | "dependencies": { 9 | "prop-types": "^15.5.9", 10 | "react": "^15.3.2", 11 | "react-dom": "^15.3.2", 12 | "react-redux": "^4.4.5", 13 | "react-router": "^2.8.1", 14 | "react-router-redux": "^4.0.6", 15 | "react-url-query": "1.0.0", 16 | "redux": "^3.6.0" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test --env=jsdom", 22 | "eject": "react-scripts eject" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pbeshai/react-url-query/fc1ea8bb6a90b3f8e6bda8093ad15535c6d4e43d/examples/react-router-v2-and-redux/public/favicon.ico -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example: React Router v2 and Redux - react-url-query 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class App extends Component { 4 | render() { 5 | return ( 6 |
7 |

react-url-query example: react-router-v2-and-redux

8 | {this.props.children} 9 |
10 | ); 11 | } 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/src/MainPage.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router'; 5 | 6 | import { addUrlProps, UrlQueryParamTypes, subquery } from 'react-url-query'; 7 | import { changeBaz } from './state/actions'; 8 | 9 | /** 10 | * To specify a custom type, pass a decode and an encode function 11 | */ 12 | const customType = { 13 | decode: (encoded) => (encoded ? encoded.substring(6) : 'mystery'), 14 | encode: (decoded) => (decoded ? `custom${decoded}` : undefined), 15 | }; 16 | 17 | /** 18 | * Specify how the URL gets decoded here. This is an object that takes the prop 19 | * name as a key, and a query param specifier as the value. The query param 20 | * specifier can have a `type`, indicating how to decode the value from the 21 | * URL, and a `queryParam` field that indicates which key in the query 22 | * parameters should be read (this defaults to the prop name if not provided). 23 | * 24 | * The queryParam value for `foo` here matches the one used in the changeFoo 25 | * action. 26 | */ 27 | const urlPropsQueryConfig = { 28 | arr: { type: UrlQueryParamTypes.array }, 29 | bar: { type: UrlQueryParamTypes.string, validate: bar => bar && bar.length < 6 }, 30 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 31 | custom: { type: customType } 32 | } 33 | 34 | /** 35 | * Standard react-redux mapStateToProps -- maps state from the Redux store to 36 | * the `baz` prop in MainPage. Used via the higher-order component `connect`. 37 | */ 38 | function mapStateToProps(state, props) { 39 | return { 40 | baz: state.app.baz, 41 | }; 42 | } 43 | 44 | /** 45 | * Standard react-redux mapDispatchToProps 46 | */ 47 | function mapDispatchToProps(dispatch) { 48 | return { 49 | onChangeBaz: (baz) => dispatch(changeBaz(baz)), 50 | }; 51 | } 52 | 53 | /** 54 | * The MainPage container. Note that none of the code within this component 55 | * indicates which values are stored in the URL and which are stored in the Redux 56 | * store. 57 | */ 58 | class MainPage extends PureComponent { 59 | static propTypes = { 60 | arr: PropTypes.array, 61 | bar: PropTypes.string, 62 | baz: PropTypes.string, 63 | custom: PropTypes.string, 64 | foo: PropTypes.number, 65 | onChangeArr: PropTypes.func, 66 | onChangeBar: PropTypes.func, 67 | onChangeBaz: PropTypes.func, 68 | onChangeCustom: PropTypes.func, 69 | onChangeFoo: PropTypes.func, 70 | word: PropTypes.string, 71 | } 72 | 73 | static defaultProps = { 74 | arr: [], 75 | bar: 'bar', 76 | baz: 'baz', 77 | custom: 'custom', 78 | foo: 123, 79 | } 80 | 81 | /** 82 | * Log whether or not we are getting a new array decoded for `arr` each time 83 | * we receive props. Ideally, this only happens when `arr` changes. 84 | * 85 | * We are using urlPropsQueryConfig, so the decoding is handled by addUrlProps 86 | * as opposed to mapUrlToProps. The decoding within addUrlProps only re-decodes 87 | * a value if it has changed, so we get the desired behavior. 88 | */ 89 | componentWillReceiveProps(nextProps) { 90 | const { arr } = this.props; 91 | if (arr !== nextProps.arr) { 92 | console.log('got new arr:', arr, '->', nextProps.arr); 93 | } else { 94 | console.log('arr did not change:', arr, '===', nextProps.arr); 95 | } 96 | } 97 | 98 | render() { 99 | const { arr, foo, bar, baz, custom, word, location, onChangeArr, 100 | onChangeBar, onChangeBaz, onChangeFoo, onChangeCustom } = this.props; 101 | 102 | return ( 103 |
104 | 105 | 106 | 107 | 108 | 109 | 110 | 122 | 123 | 124 | 125 | 126 | 127 | 132 | 133 | 134 | 135 | 136 | 137 | 142 | 143 | 144 | 145 | 146 | 147 | 152 | 153 | 154 | 155 | 156 | 157 | 162 | 163 | 164 | 165 | 166 | 167 | 172 | 173 | 174 |
word{word}(url param) 111 | 112 | 113 | 114 | {' '} 115 | 116 | 117 | 118 | 119 | 120 | 121 |
arr{JSON.stringify(arr)}(url query param) 128 | 131 |
foo{foo}(url query param) 138 | 141 |
bar{bar}(url query param) 148 | 151 |
custom{custom}(url query param) 158 | 161 |
baz{baz}(redux state) 168 | 171 |
175 |
176 | ); 177 | } 178 | } 179 | 180 | /** 181 | * We use the addUrlProps higher-order component to map URL query parameters 182 | * to props for MainPage. In this case the mapping happens automatically by 183 | * first decoding the URL query parameters based on the urlPropsQueryConfig. 184 | */ 185 | export default addUrlProps({ urlPropsQueryConfig })(connect(mapStateToProps, mapDispatchToProps)(MainPage)); 186 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore } from 'redux'; 5 | import { Router, Route, IndexRoute, browserHistory } from 'react-router' 6 | import { syncHistoryWithStore } from 'react-router-redux' 7 | 8 | import { configureUrlQuery } from 'react-url-query'; 9 | 10 | import rootReducer from './state/rootReducer'; 11 | import App from './App'; 12 | import MainPage from './MainPage'; 13 | 14 | // create the Redux store 15 | const store = createStore(rootReducer); 16 | 17 | // Create an enhanced history that syncs navigation events with the store 18 | const history = syncHistoryWithStore(browserHistory, store); 19 | 20 | // link the history used in our app to url-query so it can update the URL with it. 21 | configureUrlQuery({ history }); 22 | 23 | ReactDOM.render( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | , 32 | document.getElementById('root') 33 | ); 34 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/src/state/actions.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_BAZ = 'CHANGE_BAZ'; 2 | 3 | /** 4 | * Standard redux action creator 5 | */ 6 | export function changeBaz(baz) { 7 | return { 8 | type: CHANGE_BAZ, 9 | payload: baz 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /examples/react-router-v2-and-redux/src/state/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux' 3 | import { CHANGE_BAZ } from './actions'; 4 | 5 | /** 6 | * Simple redux reducer that handles the CHANGE_BAZ action, updating 7 | * the redux store to have the new value of baz. 8 | */ 9 | function app(state = {}, action) { 10 | switch (action.type) { 11 | case CHANGE_BAZ: 12 | return { 13 | ...state, 14 | baz: action.payload, 15 | }; 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default combineReducers({ 22 | app, 23 | routing: routerReducer, // add in state about routing via react-router-redux 24 | }); 25 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/README.md: -------------------------------------------------------------------------------- 1 | # React Router v4 and Redux Example 2 | 3 | Example of using react-url-query. Start it with `npm start`. 4 | 5 | Demonstrates how to use React URL Query with React Router v4 and Redux. This example also demonstrates using a custom type for a URL query parameter. When using React Router v4, we do *not* use [`configureUrlQuery`](api/configureUrlQuery.md) as we do in all the others. Instead we use [``](api/RouterToUrlQuery.md). The steps are: 6 | 7 | 1. Use `` to configure React URL Query to use the React Router's history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/react-router-v4-and-redux/src/index.js)). 8 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query. In this case, we wrap the connected component: `addUrlProps(...)(connect(...)(MyComponent))` (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/react-router-v4-and-redux/src/MainPage.js)). 9 | 10 | ## Available Scripts 11 | 12 | In the project directory, you can run: 13 | 14 | ### `npm start` 15 | 16 | Runs the app in the development mode.
17 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 18 | 19 | The page will reload if you make edits.
20 | You will also see any lint errors in the console. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.
25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.
28 | Your app is ready to be deployed! 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | 41 | --- 42 | 43 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 44 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-redux", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.0" 7 | }, 8 | "dependencies": { 9 | "prop-types": "^15.5.9", 10 | "react": "^15.3.2", 11 | "react-dom": "^15.3.2", 12 | "react-redux": "^4.4.5", 13 | "react-router": "^4.1.1", 14 | "react-router-dom": "^4.1.1", 15 | "react-url-query": "1.0.0", 16 | "redux": "^3.6.0" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test --env=jsdom", 22 | "eject": "react-scripts eject" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pbeshai/react-url-query/fc1ea8bb6a90b3f8e6bda8093ad15535c6d4e43d/examples/react-router-v4-and-redux/public/favicon.ico -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example: React Router v4 and Redux - react-url-query 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | Route, 4 | } from 'react-router-dom'; 5 | 6 | import MainPage from './MainPage'; 7 | 8 | class App extends Component { 9 | render() { 10 | return ( 11 |
12 |

react-url-query example: react-router-v4-and-redux

13 | 14 | 15 |
16 | ); 17 | } 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import Router from 'react-router/BrowserRouter'; 5 | import { RouterToUrlQuery } from 'react-url-query'; 6 | 7 | it('renders without crashing', () => { 8 | const div = document.createElement('div'); 9 | ReactDOM.render( 10 | 11 | 12 | 13 | 14 | , div); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/src/MainPage.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | import { addUrlProps, UrlQueryParamTypes, subquery } from 'react-url-query'; 7 | import { changeBaz } from './state/actions'; 8 | 9 | /** 10 | * To specify a custom type, pass a decode and an encode function 11 | */ 12 | const customType = { 13 | decode: (encoded) => (encoded ? encoded.substring(6) : 'mystery'), 14 | encode: (decoded) => (decoded ? `custom${decoded}` : undefined), 15 | }; 16 | 17 | /** 18 | * Specify how the URL gets decoded here. This is an object that takes the prop 19 | * name as a key, and a query param specifier as the value. The query param 20 | * specifier can have a `type`, indicating how to decode the value from the 21 | * URL, and a `queryParam` field that indicates which key in the query 22 | * parameters should be read (this defaults to the prop name if not provided). 23 | * 24 | * The queryParam value for `foo` here matches the one used in the changeFoo 25 | * action. 26 | */ 27 | const urlPropsQueryConfig = { 28 | arr: { type: UrlQueryParamTypes.array }, 29 | bar: { type: UrlQueryParamTypes.string, validate: bar => bar && bar.length < 6 }, 30 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 31 | custom: { type: customType } 32 | } 33 | 34 | /** 35 | * Standard react-redux mapStateToProps -- maps state from the Redux store to 36 | * the `baz` prop in MainPage. Used via the higher-order component `connect`. 37 | */ 38 | function mapStateToProps(state, props) { 39 | return { 40 | baz: state.baz, 41 | }; 42 | } 43 | 44 | /** 45 | * Standard react-redux mapDispatchToProps 46 | */ 47 | function mapDispatchToProps(dispatch) { 48 | return { 49 | onChangeBaz(baz) { dispatch(changeBaz(baz)); }, 50 | }; 51 | } 52 | 53 | /** 54 | * The MainPage container. Note that none of the code within this component 55 | * indicates which values are stored in the URL and which are stored in the Redux 56 | * store. 57 | */ 58 | class MainPage extends PureComponent { 59 | static propTypes = { 60 | arr: PropTypes.array, 61 | bar: PropTypes.string, 62 | baz: PropTypes.string, 63 | custom: PropTypes.string, 64 | foo: PropTypes.number, 65 | onChangeArr: PropTypes.func, 66 | onChangeBar: PropTypes.func, 67 | onChangeBaz: PropTypes.func, 68 | onChangeCustom: PropTypes.func, 69 | onChangeFoo: PropTypes.func, 70 | word: PropTypes.string, 71 | } 72 | 73 | static defaultProps = { 74 | arr: [], 75 | bar: 'bar', 76 | baz: 'baz', 77 | custom: 'custom', 78 | foo: 123, 79 | } 80 | 81 | static contextTypes = { 82 | // router: RouterPropTypes.router, 83 | router: PropTypes.any, 84 | } 85 | 86 | /** 87 | * Log whether or not we are getting a new array decoded for `arr` each time 88 | * we receive props. Ideally, this only happens when `arr` changes. 89 | * 90 | * We are using urlPropsQueryConfig, so the decoding is handled by addUrlProps 91 | * as opposed to mapUrlToProps. The decoding within addUrlProps only re-decodes 92 | * a value if it has changed, so we get the desired behavior. 93 | */ 94 | componentWillReceiveProps(nextProps) { 95 | const { arr } = this.props; 96 | if (arr !== nextProps.arr) { 97 | console.log('got new arr:', arr, '->', nextProps.arr); 98 | } else { 99 | console.log('arr did not change:', arr, '===', nextProps.arr); 100 | } 101 | } 102 | 103 | render() { 104 | const { arr, foo, bar, baz, custom, word, location, onChangeArr, 105 | onChangeBar, onChangeBaz, onChangeFoo, onChangeCustom } = this.props; 106 | 107 | return ( 108 |
109 | 110 | 111 | 112 | 113 | 114 | 115 | 127 | 128 | 129 | 130 | 131 | 132 | 137 | 138 | 139 | 140 | 141 | 142 | 147 | 148 | 149 | 150 | 151 | 152 | 157 | 158 | 159 | 160 | 161 | 162 | 167 | 168 | 169 | 170 | 171 | 172 | 177 | 178 | 179 |
word{word}(url param) 116 | 117 | 118 | 119 | {' '} 120 | 121 | 122 | 123 | 124 | 125 | 126 |
arr{JSON.stringify(arr)}(url query param) 133 | 136 |
foo{foo}(url query param) 143 | 146 |
bar{bar}(url query param) 153 | 156 |
custom{custom}(url query param) 163 | 166 |
baz{baz}(redux state) 173 | 176 |
180 |
181 | ); 182 | } 183 | } 184 | 185 | /** 186 | * We use the addUrlProps higher-order component to map URL query parameters 187 | * to props for MainPage. In this case the mapping happens automatically by 188 | * first decoding the URL query parameters based on the urlPropsQueryConfig. 189 | */ 190 | export default addUrlProps({ urlPropsQueryConfig })(connect(mapStateToProps, mapDispatchToProps)(MainPage)); 191 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore } from 'redux'; 5 | import { 6 | BrowserRouter as Router, 7 | } from 'react-router-dom' 8 | 9 | 10 | import { RouterToUrlQuery } from 'react-url-query'; 11 | 12 | import rootReducer from './state/rootReducer'; 13 | import App from './App'; 14 | 15 | // create the Redux store 16 | const store = createStore(rootReducer); 17 | 18 | // instead of using configureUrlQuery to set the history, we use 19 | // as a child of . We can still use configureUrlQuery to set other options. 20 | 21 | ReactDOM.render( 22 | 23 | 24 | 25 | 26 | 27 | 28 | , 29 | document.getElementById('root') 30 | ); 31 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/src/state/actions.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_BAZ = 'CHANGE_BAZ'; 2 | /** 3 | * Standard redux action creator 4 | */ 5 | export function changeBaz(baz) { 6 | return { 7 | type: CHANGE_BAZ, 8 | payload: baz 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-v4-and-redux/src/state/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { CHANGE_BAZ } from './actions'; 2 | 3 | /** 4 | * Simple redux reducer that handles the CHANGE_BAZ action, updating 5 | * the redux store to have the new value of baz. 6 | */ 7 | export default function app(state = {}, action) { 8 | switch (action.type) { 9 | case CHANGE_BAZ: 10 | return { 11 | ...state, 12 | baz: action.payload, 13 | }; 14 | default: 15 | return state; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/redux-with-actions/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /examples/redux-with-actions/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /examples/redux-with-actions/README.md: -------------------------------------------------------------------------------- 1 | # Redux with actions Example 2 | 3 | Example of using react-url-query. Start it with `npm start`. 4 | 5 | Demonstrates how to integrate React URL Query with Redux (and no React Router). The only difference between this and the basic example is how you wrap your component with `addUrlProps`. The steps are: 6 | 7 | 1. Use a [history](https://github.com/mjackson/history) of some sort to control pushing or replacing items in the browser's history stack. Be sure to listen for changes to the history and force an update when they occur (see [App.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/App.js)) 8 | 1. Configure React URL Query to use the history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/index.js)). Disable auto-generation of change handlers by passing `addChangeHandlers: false`. 9 | 1. Attach the [`urlQueryMiddleware`](api/urlQueryMiddleware.md) to Redux, optionally instantiating with a custom [URL query reducer](api/urlQueryReducer). (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/index.js)) 10 | 1. Create actions for URL updates with [`urlAction`](api/urlAction.md), [`urlReplaceInAction`](api/urlReplaceInAction.md), [`urlPushInAction`](api/urlPushInAction.md), etc. (see [actions.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/state/actions.js)) 11 | 1. Create change handlers in `mapDispatchToProps` for the actions you've made. (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/MainPage.js)) 12 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux-with-actions/src/MainPage.js)). In this case, we wrap the connected component: `addUrlProps(...)(connect(...)(MyComponent))`. 13 | 14 | 15 | ## Available Scripts 16 | 17 | In the project directory, you can run: 18 | 19 | ### `npm start` 20 | 21 | Runs the app in the development mode.
22 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 23 | 24 | The page will reload if you make edits.
25 | You will also see any lint errors in the console. 26 | 27 | ### `npm run build` 28 | 29 | Builds the app for production to the `build` folder.
30 | It correctly bundles React in production mode and optimizes the build for the best performance. 31 | 32 | The build is minified and the filenames include the hashes.
33 | Your app is ready to be deployed! 34 | 35 | ### `npm run eject` 36 | 37 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 38 | 39 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 40 | 41 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 42 | 43 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 44 | 45 | 46 | --- 47 | 48 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 49 | -------------------------------------------------------------------------------- /examples/redux-with-actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-redux-with-actions", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.0" 7 | }, 8 | "dependencies": { 9 | "prop-types": "^15.5.9", 10 | "history": "^4.2.0", 11 | "react": "^15.3.2", 12 | "react-dom": "^15.3.2", 13 | "react-redux": "^4.4.5", 14 | "react-url-query": "1.0.0", 15 | "redux": "^3.6.0" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/redux-with-actions/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pbeshai/react-url-query/fc1ea8bb6a90b3f8e6bda8093ad15535c6d4e43d/examples/redux-with-actions/public/favicon.ico -------------------------------------------------------------------------------- /examples/redux-with-actions/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example: Redux with actions - react-url-query 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/redux-with-actions/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import MainPage from './MainPage'; 3 | import history from './history'; 4 | 5 | class App extends Component { 6 | componentDidMount() { 7 | // force an update if the URL changes 8 | history.listen(() => this.forceUpdate()); 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |

react-url-query example: redux-with-action

15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /examples/redux-with-actions/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore } from 'redux'; 5 | 6 | import rootReducer from './state/rootReducer'; 7 | import App from './App'; 8 | 9 | // create the Redux store 10 | const store = createStore(rootReducer); 11 | 12 | it('renders without crashing', () => { 13 | const div = document.createElement('div'); 14 | ReactDOM.render( 15 | 16 | 17 | , div); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/redux-with-actions/src/MainPage.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { addUrlProps, UrlQueryParamTypes } from 'react-url-query'; 6 | import { changeArr, changeBaz, changeFoo, changeBar, changeMany } from './state/actions'; 7 | 8 | /** 9 | * Specify how the URL gets decoded here. This is an object that takes the prop 10 | * name as a key, and a query param specifier as the value. The query param 11 | * specifier can have a `type`, indicating how to decode the value from the 12 | * URL, and a `queryParam` field that indicates which key in the query 13 | * parameters should be read (this defaults to the prop name if not provided). 14 | * 15 | * The queryParam value for `foo` here matches the one used in the changeFoo 16 | * action. 17 | */ 18 | const urlPropsQueryConfig = { 19 | arr: { type: UrlQueryParamTypes.array }, 20 | bar: { type: UrlQueryParamTypes.string }, 21 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 22 | } 23 | 24 | /** 25 | * Standard react-redux mapStateToProps -- maps state from the Redux store to 26 | * the `baz` prop in MainPage. Used via the higher-order component `connect`. 27 | */ 28 | function mapStateToProps(state, props) { 29 | return { 30 | baz: state.baz, 31 | }; 32 | } 33 | 34 | /** 35 | * Standard react-redux mapDispatchToProps 36 | */ 37 | function mapDispatchToProps(dispatch) { 38 | return { 39 | onChangeArr: (arr) => dispatch(changeArr(arr)), 40 | onChangeFoo: (foo) => dispatch(changeFoo(foo)), 41 | onChangeBar: (bar) => dispatch(changeBar(bar)), 42 | onChangeBaz: (baz) => dispatch(changeBaz(baz)), 43 | onChangeMany: (foo) => dispatch(changeMany({ foo })), 44 | }; 45 | } 46 | 47 | /** 48 | * The MainPage container. Note that none of the code within this component 49 | * indicates which values are stored in the URL and which are stored in the Redux 50 | * store. 51 | */ 52 | class MainPage extends PureComponent { 53 | static propTypes = { 54 | arr: PropTypes.array, 55 | bar: PropTypes.string, 56 | baz: PropTypes.string, 57 | foo: PropTypes.number, 58 | onChangeArr: PropTypes.func, 59 | onChangeBar: PropTypes.func, 60 | onChangeBaz: PropTypes.func, 61 | onChangeFoo: PropTypes.func, 62 | onChangeMany: PropTypes.func, 63 | } 64 | 65 | static defaultProps = { 66 | arr: [], 67 | bar: 'bar', 68 | baz: 'baz', 69 | foo: 123, 70 | } 71 | 72 | /** 73 | * Log whether or not we are getting a new array decoded for `arr` each time 74 | * we receive props. Ideally, this only happens when `arr` changes. 75 | * 76 | * We are using urlPropsQueryConfig, so the decoding is handled by addUrlProps 77 | * as opposed to mapUrlToProps. The decoding within addUrlProps only re-decodes 78 | * a value if it has changed, so we get the desired behavior. 79 | */ 80 | componentWillReceiveProps(nextProps) { 81 | const { arr } = this.props; 82 | if (arr !== nextProps.arr) { 83 | console.log('got new arr:', arr, '->', nextProps.arr); 84 | } else { 85 | console.log('arr did not change:', arr, '===', nextProps.arr); 86 | } 87 | } 88 | 89 | render() { 90 | const { arr, foo, bar, baz, onChangeArr, onChangeBar, onChangeBaz, 91 | onChangeFoo, onChangeMany } = this.props; 92 | 93 | return ( 94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 106 | 107 | 108 | 109 | 110 | 111 | 116 | 117 | 118 | 119 | 120 | 121 | 126 | 127 | 128 | 129 | 130 | 131 | 136 | 137 | 138 | 139 | 140 | 141 | 146 | 147 | 148 |
arr{JSON.stringify(arr)}(url query param) 102 | 105 |
foo{foo}(url query param) 112 | 115 |
bar{bar}(url query param) 122 | 125 |
baz{baz}(redux state) 132 | 135 |
142 | 145 |
149 |
150 | ); 151 | } 152 | } 153 | 154 | /** 155 | * We use the addUrlProps higher-order component to map URL query parameters 156 | * to props for MainPage. In this case the mapping happens automatically by 157 | * first decoding the URL query parameters based on the urlPropsQueryConfig. 158 | */ 159 | export default addUrlProps({ urlPropsQueryConfig })(connect(mapStateToProps, mapDispatchToProps)(MainPage)); 160 | -------------------------------------------------------------------------------- /examples/redux-with-actions/src/history.js: -------------------------------------------------------------------------------- 1 | import createHistory from 'history/createBrowserHistory'; 2 | 3 | const history = createHistory(); 4 | 5 | export default history; 6 | -------------------------------------------------------------------------------- /examples/redux-with-actions/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore, applyMiddleware } from 'redux'; 5 | 6 | import { configureUrlQuery, urlQueryMiddleware } from 'react-url-query'; 7 | 8 | import rootReducer from './state/rootReducer'; 9 | import urlQueryReducer from './state/urlQueryReducer'; 10 | import App from './App'; 11 | import history from './history'; 12 | 13 | // link the history used in our app to url-query so it can update the URL with it. 14 | // do not auto generate change handlers since we won't be using them, we'll use 15 | // those we create in mapDispatchToProps 16 | configureUrlQuery({ history, addChangeHandlers: false }); 17 | 18 | /** 19 | * Initialize Redux 20 | * apply middleware to the store creator then use it to create the store below. 21 | * 22 | * Specifying a reducer is optional, the default reducer works for most cases. 23 | * 24 | * Note that urlQueryMiddleware shortcircuits the redux middleware chain and does not 25 | * result in the store notifying changes happened. This is done to prevent multiple renders 26 | * every time a URL changes since there is typically something else watching URL changes. 27 | * If you don't want this behavior, pass the option `shortcircuit: false` to urlQueryMiddleware. 28 | */ 29 | const createStoreWithMiddleware = applyMiddleware(urlQueryMiddleware({ reducer: urlQueryReducer }))(createStore); 30 | 31 | // create the store 32 | const store = createStoreWithMiddleware(rootReducer); 33 | 34 | ReactDOM.render( 35 | 36 | 37 | , 38 | document.getElementById('root') 39 | ); 40 | -------------------------------------------------------------------------------- /examples/redux-with-actions/src/state/actions.js: -------------------------------------------------------------------------------- 1 | import { urlReplaceInAction, urlPushAction, UrlQueryParamTypes, encode } from 'react-url-query'; 2 | 3 | export const CHANGE_BAZ = 'CHANGE_BAZ'; 4 | export const CHANGE_FOO = 'CHANGE_FOO'; 5 | export const CHANGE_BAR = 'CHANGE_BAR'; 6 | export const CHANGE_ARR = 'CHANGE_ARR'; 7 | export const CHANGE_MANY = 'CHANGE_MANY'; 8 | 9 | /** 10 | * Standard redux action creator 11 | */ 12 | export function changeBaz(baz) { 13 | return { 14 | type: CHANGE_BAZ, 15 | payload: baz 16 | }; 17 | } 18 | 19 | /** 20 | * Action creators for the urlQueryReducer. The first argument specifies 21 | * the action type, the second the query parameter name (what shows up in 22 | * the URL), and the third argument specifies the value type (used to 23 | * encode the value as a string for the URL). 24 | * 25 | * Application code uses these the same way standard redux action creators 26 | * are used. (e.g., `dispatch(changeFoo(94))`) 27 | */ 28 | export const changeFoo = urlReplaceInAction(CHANGE_FOO, 'fooInUrl', UrlQueryParamTypes.number); 29 | export const changeBar = urlReplaceInAction(CHANGE_BAR, 'bar', UrlQueryParamTypes.string); 30 | export const changeArr = urlReplaceInAction(CHANGE_ARR, 'arr', UrlQueryParamTypes.array); 31 | 32 | /** 33 | * Example of pushing a whole new query. The second argument specifies how to 34 | * encode the query for the URL 35 | */ 36 | export const changeMany = urlPushAction(CHANGE_MANY, 37 | (newQuery) => ({ 38 | fooInUrl: encode(UrlQueryParamTypes.number, newQuery.foo), 39 | bar: 'par', 40 | arr: encode(UrlQueryParamTypes.array, ['T', 'Y']), 41 | })); 42 | -------------------------------------------------------------------------------- /examples/redux-with-actions/src/state/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { CHANGE_BAZ } from './actions'; 2 | 3 | /** 4 | * Simple redux reducer that handles the CHANGE_BAZ action, updating 5 | * the redux store to have the new value of baz. 6 | */ 7 | export default function rootReducer(state = {}, action) { 8 | switch (action.type) { 9 | case CHANGE_BAZ: 10 | return { 11 | ...state, 12 | baz: action.payload, 13 | }; 14 | default: 15 | return state; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/redux-with-actions/src/state/urlQueryReducer.js: -------------------------------------------------------------------------------- 1 | import { CHANGE_FOO } from './actions'; 2 | import { 3 | replaceInUrlQueryFromAction, 4 | pushUrlQuery, 5 | pushInUrlQueryFromAction, 6 | urlQueryReducer as defaultUrlQueryReducer 7 | } from 'react-url-query'; 8 | 9 | /** 10 | * Reducer that handles actions that modify the URL query parameters. 11 | * In this case, the actions replace a single query parameter at a time. 12 | */ 13 | export default function urlQueryReducer(action) { 14 | switch (action.type) { 15 | case CHANGE_FOO: { 16 | const foo = action.payload.decodedValue; 17 | if (foo < 300) { 18 | console.log('pushing new URL state since foo < 300'); 19 | pushInUrlQueryFromAction(action); 20 | } else if (foo > 700) { 21 | console.log('pushing entire query into URL state since foo > 700'); 22 | const newQuery = { 23 | [action.payload.queryParam]: [action.payload.encodedValue], 24 | party: Math.ceil(Math.random() * 100) 25 | }; 26 | pushUrlQuery(newQuery); 27 | } else { 28 | replaceInUrlQueryFromAction(action); 29 | } 30 | break; 31 | } 32 | default: 33 | // This will be used for CHANGE_MANY, CHANGE_BAR, and CHANGE_ARR since they 34 | // are not handled above. 35 | defaultUrlQueryReducer(action); 36 | break; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/redux/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /examples/redux/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /examples/redux/README.md: -------------------------------------------------------------------------------- 1 | # Redux Example 2 | 3 | Example of using react-url-query. Start it with `npm start`. 4 | 5 | Demonstrates how to integrate React URL Query with Redux (and no React Router). The only difference between this and the basic example is how you wrap your component with `addUrlProps`. The steps are: 6 | 7 | 1. Use a [history](https://github.com/mjackson/history) of some sort to control pushing or replacing items in the browser's history stack. Be sure to listen for changes to the history and force an update when they occur (see [App.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux/src/App.js)) 8 | 1. Configure React URL Query to use the history in your application's setup (see [index.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux/src/index.js)). 9 | 1. Use a `urlPropsQueryConfig` and [`addUrlProps`](api/addUrlProps.md) to connect your component to React URL Query (see [MainPage.js](https://github.com/pbeshai/react-url-query/blob/master/examples/redux/src/MainPage.js)). In this case, we wrap the connected component: `addUrlProps(...)(connect(...)(MyComponent))`. 10 | 11 | ## Available Scripts 12 | 13 | In the project directory, you can run: 14 | 15 | ### `npm start` 16 | 17 | Runs the app in the development mode.
18 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 19 | 20 | The page will reload if you make edits.
21 | You will also see any lint errors in the console. 22 | 23 | ### `npm run build` 24 | 25 | Builds the app for production to the `build` folder.
26 | It correctly bundles React in production mode and optimizes the build for the best performance. 27 | 28 | The build is minified and the filenames include the hashes.
29 | Your app is ready to be deployed! 30 | 31 | ### `npm run eject` 32 | 33 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 34 | 35 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 36 | 37 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 38 | 39 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 40 | 41 | 42 | --- 43 | 44 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 45 | -------------------------------------------------------------------------------- /examples/redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-redux", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "^0.6.0" 7 | }, 8 | "dependencies": { 9 | "prop-types": "^15.5.9", 10 | "history": "^4.2.0", 11 | "react": "^15.3.2", 12 | "react-dom": "^15.3.2", 13 | "react-redux": "^4.4.5", 14 | "react-url-query": "1.0.0", 15 | "redux": "^3.6.0" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/redux/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pbeshai/react-url-query/fc1ea8bb6a90b3f8e6bda8093ad15535c6d4e43d/examples/redux/public/favicon.ico -------------------------------------------------------------------------------- /examples/redux/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example: Redux - react-url-query 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/redux/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import MainPage from './MainPage'; 3 | import history from './history'; 4 | 5 | class App extends Component { 6 | componentDidMount() { 7 | // force an update if the URL changes 8 | history.listen(() => this.forceUpdate()); 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |

react-url-query example: redux

15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /examples/redux/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore } from 'redux'; 5 | 6 | import rootReducer from './state/rootReducer'; 7 | import App from './App'; 8 | 9 | // create the Redux store 10 | const store = createStore(rootReducer); 11 | 12 | it('renders without crashing', () => { 13 | const div = document.createElement('div'); 14 | ReactDOM.render( 15 | 16 | 17 | , div); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/redux/src/MainPage.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { addUrlProps, UrlQueryParamTypes } from 'react-url-query'; 6 | import { changeBaz } from './state/actions'; 7 | 8 | /** 9 | * Specify how the URL gets decoded here. This is an object that takes the prop 10 | * name as a key, and a query param specifier as the value. The query param 11 | * specifier can have a `type`, indicating how to decode the value from the 12 | * URL, and a `queryParam` field that indicates which key in the query 13 | * parameters should be read (this defaults to the prop name if not provided). 14 | * 15 | * The queryParam value for `foo` here matches the one used in the changeFoo 16 | * action. 17 | */ 18 | const urlPropsQueryConfig = { 19 | arr: { type: UrlQueryParamTypes.array }, 20 | bar: { type: UrlQueryParamTypes.string }, 21 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 22 | } 23 | 24 | /** 25 | * Standard react-redux mapStateToProps -- maps state from the Redux store to 26 | * the `baz` prop in MainPage. Used via the higher-order component `connect`. 27 | */ 28 | function mapStateToProps(state, props) { 29 | return { 30 | baz: state.baz, 31 | }; 32 | } 33 | 34 | /** 35 | * Standard react-redux mapDispatchToProps 36 | */ 37 | function mapDispatchToProps(dispatch) { 38 | return { 39 | onChangeBaz: (baz) => dispatch(changeBaz(baz)), 40 | }; 41 | } 42 | 43 | /** 44 | * The MainPage container. Note that none of the code within this component 45 | * indicates which values are stored in the URL and which are stored in the Redux 46 | * store. 47 | */ 48 | class MainPage extends PureComponent { 49 | static propTypes = { 50 | arr: PropTypes.array, 51 | bar: PropTypes.string, 52 | baz: PropTypes.string, 53 | foo: PropTypes.number, 54 | onChangeBaz: PropTypes.func, 55 | 56 | // change handlers are automatically generated and passed if a config is provided 57 | // and `addChangeHandlers` isn't false. They use `replaceIn` by default, just 58 | // updating that single query parameter and keeping the other existing ones. 59 | onChangeArr: PropTypes.func, 60 | onChangeBar: PropTypes.func, 61 | onChangeFoo: PropTypes.func, 62 | } 63 | 64 | static defaultProps = { 65 | arr: [], 66 | bar: 'bar', 67 | baz: 'baz', 68 | foo: 123, 69 | } 70 | 71 | /** 72 | * Log whether or not we are getting a new array decoded for `arr` each time 73 | * we receive props. Ideally, this only happens when `arr` changes. 74 | * 75 | * We are using urlPropsQueryConfig, so the decoding is handled by addUrlProps 76 | * as opposed to mapUrlToProps. The decoding within addUrlProps only re-decodes 77 | * a value if it has changed, so we get the desired behavior. 78 | */ 79 | componentWillReceiveProps(nextProps) { 80 | const { arr } = this.props; 81 | if (arr !== nextProps.arr) { 82 | console.log('got new arr:', arr, '->', nextProps.arr); 83 | } else { 84 | console.log('arr did not change:', arr, '===', nextProps.arr); 85 | } 86 | } 87 | 88 | render() { 89 | const { arr, foo, bar, baz, onChangeArr, onChangeBar, onChangeBaz, onChangeFoo } = this.props; 90 | 91 | return ( 92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 104 | 105 | 106 | 107 | 108 | 109 | 114 | 115 | 116 | 117 | 118 | 119 | 124 | 125 | 126 | 127 | 128 | 129 | 134 | 135 | 136 |
arr{JSON.stringify(arr)}(url query param) 100 | 103 |
foo{foo}(url query param) 110 | 113 |
bar{bar}(url query param) 120 | 123 |
baz{baz}(redux state) 130 | 133 |
137 |
138 | ); 139 | } 140 | } 141 | 142 | /** 143 | * We use the addUrlProps higher-order component to map URL query parameters 144 | * to props for MainPage. In this case the mapping happens automatically by 145 | * first decoding the URL query parameters based on the urlPropsQueryConfig. 146 | */ 147 | export default addUrlProps({ urlPropsQueryConfig })(connect(mapStateToProps, mapDispatchToProps)(MainPage)); 148 | -------------------------------------------------------------------------------- /examples/redux/src/history.js: -------------------------------------------------------------------------------- 1 | import createHistory from 'history/createBrowserHistory'; 2 | 3 | const history = createHistory(); 4 | 5 | export default history; 6 | -------------------------------------------------------------------------------- /examples/redux/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { createStore } from 'redux'; 5 | 6 | import { configureUrlQuery } from 'react-url-query'; 7 | 8 | import rootReducer from './state/rootReducer'; 9 | import App from './App'; 10 | import history from './history'; 11 | 12 | // link the history used in our app to url-query so it can update the URL with it. 13 | configureUrlQuery({ history }); 14 | 15 | // create the Redux store 16 | const store = createStore(rootReducer); 17 | 18 | ReactDOM.render( 19 | 20 | 21 | , 22 | document.getElementById('root') 23 | ); 24 | -------------------------------------------------------------------------------- /examples/redux/src/state/actions.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_BAZ = 'CHANGE_BAZ'; 2 | 3 | /** 4 | * Standard redux action creator 5 | */ 6 | export function changeBaz(baz) { 7 | return { 8 | type: CHANGE_BAZ, 9 | payload: baz 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /examples/redux/src/state/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { CHANGE_BAZ } from './actions'; 2 | 3 | /** 4 | * Simple redux reducer that handles the CHANGE_BAZ action, updating 5 | * the redux store to have the new value of baz. 6 | */ 7 | export default function rootReducer(state = {}, action) { 8 | switch (action.type) { 9 | case CHANGE_BAZ: 10 | return { 11 | ...state, 12 | baz: action.payload, 13 | }; 14 | default: 15 | return state; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/testAll.js: -------------------------------------------------------------------------------- 1 | // From: https://github.com/reactjs/redux/blob/master/examples/testAll.js 2 | /** 3 | * Runs an ordered set of commands within each of the build directories. 4 | */ 5 | 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const { spawnSync } = require('child_process'); 9 | 10 | const exampleDirs = fs.readdirSync(__dirname).filter(file => 11 | fs.statSync(path.join(__dirname, file)).isDirectory()); 12 | 13 | // Ordering is important here. `npm install` must come first. 14 | const cmdArgs = [ 15 | { cmd: 'npm', args: ['install'] }, 16 | { cmd: 'npm', args: ['test'] }, 17 | ]; 18 | 19 | for (const dir of exampleDirs) { 20 | for (const cmdArg of cmdArgs) { 21 | // declare opts in this scope to avoid https://github.com/joyent/node/issues/9158 22 | const opts = { 23 | cwd: path.join(__dirname, dir), 24 | stdio: 'inherit', 25 | }; 26 | 27 | let result = {}; 28 | if (process.platform === 'win32') { 29 | result = spawnSync(`${cmdArg.cmd}.cmd`, cmdArg.args, opts); 30 | } else { 31 | result = spawnSync(cmdArg.cmd, cmdArg.args, opts); 32 | } 33 | if (result.status !== 0) { 34 | throw new Error('Building examples exited with non-zero'); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-url-query", 3 | "version": "1.5.0", 4 | "description": "A library for managing state through query parameters in the URL in React. Works well with or without Redux and React Router.", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "dist", 8 | "lib", 9 | "es", 10 | "src" 11 | ], 12 | "scripts": { 13 | "clean": "rimraf lib dist es coverage", 14 | "lint": "npm run lint:src && npm run lint:examples", 15 | "lint:src": "eslint src test build", 16 | "lint:examples": "eslint examples", 17 | "prettier": "prettier --write \"src/**/*.js\"", 18 | "test": "cross-env BABEL_ENV=commonjs jest", 19 | "test:watch": "npm test -- --watch", 20 | "test:cov": "npm test -- --coverage", 21 | "test:examples": "babel-node examples/testAll.js", 22 | "check:src": "npm run lint:src && npm run test", 23 | "check:examples": "npm run build:examples && npm run lint:examples && npm run test:examples", 24 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 25 | "build:es": "cross-env BABEL_ENV=es babel src --out-dir es", 26 | "build:umd": "cross-env BABEL_ENV=commonjs NODE_ENV=development webpack src/index.js dist/react-url-query.js", 27 | "build:umd:min": "cross-env BABEL_ENV=commonjs NODE_ENV=production webpack src/index.js dist/react-url-query.min.js", 28 | "build:examples": "babel-node examples/buildAll.js", 29 | "build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min", 30 | "dev": "cross-env BABEL_ENV=commonjs babel src --watch --out-dir lib", 31 | "prepare": "npm run clean && npm run check:src && npm run build", 32 | "docs:clean": "rimraf _book", 33 | "docs:prepare": "gitbook install", 34 | "docs:build": "npm run docs:prepare && gitbook build -g pbeshai/react-url-query", 35 | "docs:watch": "npm run docs:prepare && gitbook serve", 36 | "docs:publish": "npm run docs:clean && npm run docs:build && cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:pbeshai/react-url-query gh-pages --force" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/pbeshai/react-url-query.git" 41 | }, 42 | "keywords": [ 43 | "react", 44 | "url", 45 | "query", 46 | "query parameter", 47 | "url search", 48 | "state", 49 | "redux", 50 | "url reducer", 51 | "middleware" 52 | ], 53 | "author": "Peter Beshai (https://github.com/pbeshai)", 54 | "license": "MIT", 55 | "bugs": { 56 | "url": "https://github.com/pbeshai/react-url-query/issues" 57 | }, 58 | "homepage": "https://github.com/pbeshai/react-url-query", 59 | "devDependencies": { 60 | "babel-cli": "6.14.0", 61 | "babel-core": "6.14.0", 62 | "babel-eslint": "^7.2.3", 63 | "babel-jest": "15.0.0", 64 | "babel-loader": "6.2.5", 65 | "babel-plugin-transform-es2015-modules-commonjs": "6.14.0", 66 | "babel-preset-es2015": "^6.14.0", 67 | "babel-preset-react": "^6.11.1", 68 | "babel-preset-stage-0": "^6.5.0", 69 | "babel-register": "6.14.0", 70 | "cross-env": "3.0.0", 71 | "enzyme": "^2.4.1", 72 | "eslint": "^4.12.0", 73 | "eslint-config-react-app": "^2.0.1", 74 | "eslint-plugin-flowtype": "^2.39.1", 75 | "eslint-plugin-import": "^2.8.0", 76 | "eslint-plugin-jsx-a11y": "^5.1.1", 77 | "eslint-plugin-react": "^7.5.1", 78 | "gitbook-cli": "^2.3.0", 79 | "jest": "15.1.1", 80 | "prettier": "^1.8.2", 81 | "react": "^15.0.0", 82 | "react-addons-test-utils": "^15.3.2", 83 | "react-dom": "^15.3.2", 84 | "react-router": "^5.0.1", 85 | "rimraf": "2.5.4", 86 | "webpack": "1.13.2" 87 | }, 88 | "peerDependencies": { 89 | "react": "^15.0 || ^16.0" 90 | }, 91 | "dependencies": { 92 | "loose-envify": "^1.2.0", 93 | "prop-types": "^15.5.9", 94 | "query-string": "^4.2.3" 95 | }, 96 | "npmFileMap": [ 97 | { 98 | "basePath": "/dist/", 99 | "files": [ 100 | "*.js" 101 | ] 102 | } 103 | ], 104 | "browserify": { 105 | "transform": [ 106 | "loose-envify" 107 | ] 108 | }, 109 | "jest": { 110 | "testPathDirs": [ 111 | "src" 112 | ] 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/UrlQueryParamTypes.js: -------------------------------------------------------------------------------- 1 | const UrlQueryParamTypes = { 2 | number: 'number', 3 | string: 'string', 4 | object: 'object', 5 | array: 'array', 6 | json: 'json', 7 | date: 'date', 8 | boolean: 'boolean', 9 | numericObject: 'numericObject', 10 | numericArray: 'numericArray', 11 | }; 12 | 13 | export default UrlQueryParamTypes; 14 | -------------------------------------------------------------------------------- /src/UrlUpdateTypes.js: -------------------------------------------------------------------------------- 1 | const UrlUpdateTypes = { 2 | replace: 'replace', 3 | replaceIn: 'replaceIn', 4 | multiReplaceIn: 'multiReplaceIn', 5 | push: 'push', 6 | pushIn: 'pushIn', 7 | multiPushIn: 'multiPushIn', 8 | }; 9 | 10 | export default UrlUpdateTypes; 11 | -------------------------------------------------------------------------------- /src/__tests__/configureUrlQuery-test.js: -------------------------------------------------------------------------------- 1 | import configureUrlQuery from '../configureUrlQuery'; 2 | import urlQueryConfig from '../urlQueryConfig'; 3 | 4 | it('updates the singleton query object', () => { 5 | configureUrlQuery({ test: 99 }); 6 | expect(urlQueryConfig.test).toBe(99); 7 | 8 | configureUrlQuery({ history: 123 }); 9 | expect(urlQueryConfig.history).toBe(123); 10 | expect(urlQueryConfig.test).toBe(99); 11 | }); 12 | 13 | it('does not break on undefined options', () => { 14 | configureUrlQuery(); 15 | expect(Object.keys(urlQueryConfig).length).toBeGreaterThan(0); 16 | }); 17 | 18 | it('configures entrySeparator and keyValSeparator global values', () => { 19 | expect(urlQueryConfig.entrySeparator).toBe('_'); 20 | expect(urlQueryConfig.keyValSeparator).toBe('-'); 21 | 22 | configureUrlQuery({ entrySeparator: '__' }); 23 | expect(urlQueryConfig.entrySeparator).toBe('__'); 24 | expect(urlQueryConfig.keyValSeparator).toBe('-'); 25 | 26 | configureUrlQuery({ keyValSeparator: '--' }); 27 | expect(urlQueryConfig.entrySeparator).toBe('__'); 28 | expect(urlQueryConfig.keyValSeparator).toBe('--'); 29 | 30 | // Reset so it does not effect other tests 31 | configureUrlQuery({ entrySeparator: '_', keyValSeparator: '-' }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/__tests__/index-test.js: -------------------------------------------------------------------------------- 1 | import { 2 | configureUrlQuery, 3 | Serialize, 4 | encode, 5 | decode, 6 | replaceInUrlQuery, 7 | replaceUrlQuery, 8 | pushInUrlQuery, 9 | pushUrlQuery, 10 | multiReplaceInUrlQuery, 11 | multiPushInUrlQuery, 12 | UrlQueryParamTypes, 13 | UrlUpdateTypes, 14 | addUrlProps, 15 | RouterToUrlQuery, 16 | replaceInUrlQueryFromAction, 17 | replaceUrlQueryFromAction, 18 | pushInUrlQueryFromAction, 19 | pushUrlQueryFromAction, 20 | urlAction, 21 | urlReplaceAction, 22 | urlPushAction, 23 | urlReplaceInAction, 24 | urlPushInAction, 25 | urlQueryMiddleware, 26 | urlQueryReducer, 27 | subquery, 28 | subqueryOmit, 29 | } from '../index'; 30 | 31 | describe('index', () => { 32 | it('includes all expected functions without crashing', () => { 33 | expect(configureUrlQuery).toBeDefined(); 34 | expect(Serialize).toBeDefined(); 35 | expect(encode).toBeDefined(); 36 | expect(decode).toBeDefined(); 37 | expect(replaceInUrlQuery).toBeDefined(); 38 | expect(replaceUrlQuery).toBeDefined(); 39 | expect(pushInUrlQuery).toBeDefined(); 40 | expect(pushUrlQuery).toBeDefined(); 41 | expect(multiReplaceInUrlQuery).toBeDefined(); 42 | expect(multiPushInUrlQuery).toBeDefined(); 43 | expect(UrlQueryParamTypes).toBeDefined(); 44 | expect(UrlUpdateTypes).toBeDefined(); 45 | expect(addUrlProps).toBeDefined(); 46 | expect(RouterToUrlQuery).toBeDefined(); 47 | expect(replaceInUrlQueryFromAction).toBeDefined(); 48 | expect(replaceUrlQueryFromAction).toBeDefined(); 49 | expect(pushInUrlQueryFromAction).toBeDefined(); 50 | expect(pushUrlQueryFromAction).toBeDefined(); 51 | expect(urlAction).toBeDefined(); 52 | expect(urlReplaceAction).toBeDefined(); 53 | expect(urlPushAction).toBeDefined(); 54 | expect(urlReplaceInAction).toBeDefined(); 55 | expect(urlPushInAction).toBeDefined(); 56 | expect(urlQueryMiddleware).toBeDefined(); 57 | expect(urlQueryReducer).toBeDefined(); 58 | expect(subquery).toBeDefined(); 59 | expect(subqueryOmit).toBeDefined(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/__tests__/urlQueryConfig-test.js: -------------------------------------------------------------------------------- 1 | import urlQueryConfig from '../urlQueryConfig'; 2 | 3 | it('provides defaults for all options', () => { 4 | expect(Object.keys(urlQueryConfig)).toContain('addUrlChangeHandlers'); 5 | expect(Object.keys(urlQueryConfig)).toContain('addRouterParams'); 6 | expect(Object.keys(urlQueryConfig)).toContain('changeHandlerName'); 7 | expect(Object.keys(urlQueryConfig)).toContain('history'); 8 | expect(Object.keys(urlQueryConfig)).toContain('readLocationFromStore'); 9 | }); 10 | 11 | it('changeHandlerName produces a string based on the prop name', () => { 12 | expect(urlQueryConfig.changeHandlerName('foo').toLowerCase()).toContain( 13 | 'foo' 14 | ); 15 | }); 16 | 17 | it('provides a history with push and replace functions', () => { 18 | expect(typeof urlQueryConfig.history.push).toBe('function'); 19 | expect(typeof urlQueryConfig.history.replace).toBe('function'); 20 | }); 21 | 22 | it('provides readLocationFromStore that reads from react-router-redux location', () => { 23 | expect(typeof urlQueryConfig.readLocationFromStore).toBe('function'); 24 | const reactRouterReduxState = { 25 | routing: { locationBeforeTransitions: { foo: 'bar' } }, 26 | }; 27 | expect(urlQueryConfig.readLocationFromStore(reactRouterReduxState)).toEqual({ 28 | foo: 'bar', 29 | }); 30 | 31 | expect(urlQueryConfig.readLocationFromStore()).not.toBeDefined(); 32 | expect(urlQueryConfig.readLocationFromStore({})).not.toBeDefined(); 33 | expect( 34 | urlQueryConfig.readLocationFromStore({ routing: {} }) 35 | ).not.toBeDefined(); 36 | }); 37 | -------------------------------------------------------------------------------- /src/__tests__/urlQueryDecoder-test.js: -------------------------------------------------------------------------------- 1 | import urlQueryDecoder from '../urlQueryDecoder'; 2 | import UrlQueryParamTypes from '../UrlQueryParamTypes'; 3 | 4 | it('works with basic configuration', () => { 5 | const urlPropsQueryConfig = { 6 | foo: { type: UrlQueryParamTypes.number }, 7 | bar: { type: UrlQueryParamTypes.string }, 8 | }; 9 | 10 | const decode = urlQueryDecoder(urlPropsQueryConfig); 11 | const decoded = decode({ foo: '137', bar: 'str' }); 12 | 13 | expect(decoded).toEqual({ foo: 137, bar: 'str' }); 14 | }); 15 | 16 | it('works with different named query param', () => { 17 | const urlPropsQueryConfig = { 18 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 19 | bar: { type: UrlQueryParamTypes.string }, 20 | }; 21 | 22 | const decode = urlQueryDecoder(urlPropsQueryConfig); 23 | const decoded = decode({ fooInUrl: '137', bar: 'str' }); 24 | 25 | expect(decoded).toEqual({ foo: 137, bar: 'str' }); 26 | }); 27 | 28 | it('validate filters out invalid params', () => { 29 | const urlPropsQueryConfig = { 30 | foo: { type: UrlQueryParamTypes.number, validate: foo => foo > 100 }, 31 | bar: { type: UrlQueryParamTypes.string }, 32 | }; 33 | 34 | const decode = urlQueryDecoder(urlPropsQueryConfig); 35 | expect(decode({ foo: '137', bar: 'str' })).toEqual({ foo: 137, bar: 'str' }); 36 | expect(decode({ foo: '99', bar: 'str' })).toEqual({ bar: 'str' }); 37 | }); 38 | 39 | it('uses cached decoded values if encoded values have not changed', () => { 40 | const urlPropsQueryConfig = { 41 | foo: { type: UrlQueryParamTypes.array }, 42 | bar: { type: UrlQueryParamTypes.string }, 43 | }; 44 | 45 | const decode = urlQueryDecoder(urlPropsQueryConfig); 46 | const decoded = decode({ foo: '137_94', bar: 'str' }); 47 | expect(decode({ foo: '137_94', bar: 'bar' }).foo).toBe(decoded.foo); 48 | expect(decode({ foo: '137_95', bar: 'bar' }).foo).not.toBe(decoded.foo); 49 | }); 50 | 51 | it('respects the `defaultValue` configuration', () => { 52 | const urlPropsQueryConfig = { 53 | foo: { type: UrlQueryParamTypes.number, defaultValue: 42 }, 54 | bar: { type: UrlQueryParamTypes.string }, 55 | }; 56 | 57 | const decode = urlQueryDecoder(urlPropsQueryConfig); 58 | const decoded = decode({ bar: 'str' }); 59 | 60 | expect(decoded).toEqual({ foo: 42, bar: 'str' }); 61 | }); 62 | 63 | it('respects the `defaultValue` configuration for the properties with named `queryParam`', () => { 64 | const urlPropsQueryConfig = { 65 | foo: { 66 | type: UrlQueryParamTypes.number, 67 | queryParam: 'fooInUrl', 68 | defaultValue: 42, 69 | }, 70 | bar: { type: UrlQueryParamTypes.string }, 71 | }; 72 | 73 | const decode = urlQueryDecoder(urlPropsQueryConfig); 74 | const decoded = decode({ bar: 'str' }); 75 | 76 | expect(decoded).toEqual({ foo: 42, bar: 'str' }); 77 | }); 78 | 79 | it('respects custom decoders', () => { 80 | const urlPropsQueryConfig = { 81 | foo: { type: value => Number(value) + 1 }, 82 | bar: { type: UrlQueryParamTypes.string }, 83 | }; 84 | 85 | const decode = urlQueryDecoder(urlPropsQueryConfig); 86 | const decoded = decode({ foo: '137', bar: 'str' }); 87 | 88 | expect(decoded).toEqual({ foo: 138, bar: 'str' }); 89 | }); 90 | 91 | it('respects custom decoders with named `queryParam`', () => { 92 | const urlPropsQueryConfig = { 93 | foo: { type: value => Number(value) + 1, queryParam: 'fooInUrl' }, 94 | bar: { type: UrlQueryParamTypes.string }, 95 | }; 96 | 97 | const decode = urlQueryDecoder(urlPropsQueryConfig); 98 | const decoded = decode({ fooInUrl: '137', bar: 'str' }); 99 | 100 | expect(decoded).toEqual({ foo: 138, bar: 'str' }); 101 | }); 102 | 103 | it('respects the `defaultValue` configuration with a custom decoder', () => { 104 | const urlPropsQueryConfig = { 105 | foo: { 106 | type: (value, defaultValue) => 107 | value === '137' ? defaultValue : Number(value), 108 | defaultValue: 42, 109 | }, 110 | bar: { type: UrlQueryParamTypes.string }, 111 | }; 112 | 113 | const decode = urlQueryDecoder(urlPropsQueryConfig); 114 | const decoded = decode({ foo: '137', bar: 'str' }); 115 | 116 | expect(decoded).toEqual({ foo: 42, bar: 'str' }); 117 | }); 118 | 119 | it('respects the `defaultValue` configuration with a custom decoder and a named `queryParam`', () => { 120 | const urlPropsQueryConfig = { 121 | foo: { 122 | type: (value, defaultValue) => 123 | value === '137' ? defaultValue : Number(value), 124 | defaultValue: 42, 125 | queryParam: 'fooInUrl', 126 | }, 127 | bar: { type: UrlQueryParamTypes.string }, 128 | }; 129 | 130 | const decode = urlQueryDecoder(urlPropsQueryConfig); 131 | const decoded = decode({ fooInUrl: '137', bar: 'str' }); 132 | 133 | expect(decoded).toEqual({ foo: 42, bar: 'str' }); 134 | }); 135 | -------------------------------------------------------------------------------- /src/__tests__/urlQueryEncoder-test.js: -------------------------------------------------------------------------------- 1 | import urlQueryEncoder from '../urlQueryEncoder'; 2 | import UrlQueryParamTypes from '../UrlQueryParamTypes'; 3 | 4 | it('works with basic configuration', () => { 5 | const urlPropsQueryConfig = { 6 | foo: { type: UrlQueryParamTypes.number }, 7 | bar: { type: UrlQueryParamTypes.string }, 8 | }; 9 | 10 | const encode = urlQueryEncoder(urlPropsQueryConfig); 11 | const encoded = encode({ foo: 137, bar: 'str' }); 12 | 13 | expect(encoded).toEqual({ foo: '137', bar: 'str' }); 14 | }); 15 | 16 | it('works with different named query param', () => { 17 | const urlPropsQueryConfig = { 18 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 19 | bar: { type: UrlQueryParamTypes.string }, 20 | }; 21 | 22 | const encode = urlQueryEncoder(urlPropsQueryConfig); 23 | const encoded = encode({ foo: 137, bar: 'str' }); 24 | 25 | expect(encoded).toEqual({ fooInUrl: '137', bar: 'str' }); 26 | }); 27 | 28 | it('works when the object to encode has got missing properties', () => { 29 | const urlPropsQueryConfig = { 30 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 31 | bar: { type: UrlQueryParamTypes.string }, 32 | }; 33 | 34 | const encode = urlQueryEncoder(urlPropsQueryConfig); 35 | const encoded = encode({ foo: 137 }); 36 | 37 | expect(encoded).toEqual({ fooInUrl: '137' }); 38 | }); 39 | 40 | it('works when the object to encode has got missing properies with named `queryParam`', () => { 41 | const urlPropsQueryConfig = { 42 | foo: { type: UrlQueryParamTypes.number, queryParam: 'fooInUrl' }, 43 | bar: { type: UrlQueryParamTypes.string }, 44 | }; 45 | 46 | const encode = urlQueryEncoder(urlPropsQueryConfig); 47 | const encoded = encode({ bar: 'str' }); 48 | 49 | expect(encoded).toEqual({ bar: 'str' }); 50 | }); 51 | 52 | it('respects custom encoders', () => { 53 | const urlPropsQueryConfig = { 54 | foo: { type: number => (number + 1).toString() }, 55 | bar: { type: UrlQueryParamTypes.string }, 56 | }; 57 | 58 | const encode = urlQueryEncoder(urlPropsQueryConfig); 59 | const encoded = encode({ foo: 137, bar: 'str' }); 60 | 61 | expect(encoded).toEqual({ foo: '138', bar: 'str' }); 62 | }); 63 | 64 | it('respects custom encoders with named `queryParam`', () => { 65 | const urlPropsQueryConfig = { 66 | foo: { type: number => (number + 1).toString(), queryParam: 'fooInUrl' }, 67 | bar: { type: UrlQueryParamTypes.string }, 68 | }; 69 | 70 | const encode = urlQueryEncoder(urlPropsQueryConfig); 71 | const encoded = encode({ foo: 137, bar: 'str' }); 72 | 73 | expect(encoded).toEqual({ fooInUrl: '138', bar: 'str' }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/configureUrlQuery.js: -------------------------------------------------------------------------------- 1 | import urlQueryConfig from './urlQueryConfig'; 2 | 3 | export default function configureUrlQuery(options) { 4 | // update the url options singleton 5 | Object.assign(urlQueryConfig, options); 6 | } 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export configureUrlQuery from './configureUrlQuery'; 2 | 3 | export * as Serialize, { encode, decode } from './serialize'; 4 | export { 5 | replaceInUrlQuery, 6 | replaceUrlQuery, 7 | pushInUrlQuery, 8 | pushUrlQuery, 9 | multiReplaceInUrlQuery, 10 | multiPushInUrlQuery, 11 | } from './updateUrlQuery'; 12 | export urlQueryDecoder from './urlQueryDecoder'; 13 | export urlQueryEncoder from './urlQueryEncoder'; 14 | export UrlQueryParamTypes from './UrlQueryParamTypes'; 15 | export UrlUpdateTypes from './UrlUpdateTypes'; 16 | 17 | /** React */ 18 | export addUrlProps from './react/addUrlProps'; 19 | export RouterToUrlQuery from './react/RouterToUrlQuery'; 20 | 21 | /** Redux */ 22 | export { 23 | replaceInUrlQueryFromAction, 24 | replaceUrlQueryFromAction, 25 | multiReplaceInUrlQueryFromAction, 26 | pushInUrlQueryFromAction, 27 | pushUrlQueryFromAction, 28 | multiPushInUrlQueryFromAction, 29 | } from './redux/updateUrlQueryFromAction'; 30 | export urlAction, { 31 | urlReplaceAction, 32 | urlPushAction, 33 | urlReplaceInAction, 34 | urlPushInAction, 35 | urlMultiReplaceInAction, 36 | urlMultiPushInAction, 37 | } from './redux/urlAction'; 38 | export urlQueryMiddleware from './redux/urlQueryMiddleware'; 39 | export urlQueryReducer from './redux/urlQueryReducer'; 40 | 41 | /** Utils */ 42 | export subquery from './utils/subquery'; 43 | export subqueryOmit from './utils/subqueryOmit'; 44 | -------------------------------------------------------------------------------- /src/react/RouterToUrlQuery.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import configureUrlQuery from '../configureUrlQuery'; 4 | 5 | /** 6 | * This class exists to read in the router from context (useful in react-router v4) 7 | * to get an equivalent history object so we can push and replace the URL. 8 | */ 9 | export default class RouterToUrlQuery extends Component { 10 | static propTyps = { 11 | routerContext: PropTypes.object 12 | }; 13 | static contextTypes = { 14 | router: PropTypes.object 15 | }; 16 | 17 | render() { 18 | const { router: routerOldContext } = this.context; 19 | const { routerContext: RouterContext } = this.props; 20 | 21 | if (typeof RouterContext === "undefined") { 22 | return ( 23 | 26 | {React.Children.only(this.props.children)} 27 | 28 | ) 29 | } 30 | 31 | return ( 32 | 33 | {routerNewContext => ( 34 | 37 | {React.Children.only(this.props.children)} 38 | 39 | )} 40 | 41 | ); 42 | } 43 | } 44 | 45 | class RouterToUrlQueryLogic extends Component { 46 | static propTypes = { 47 | children: PropTypes.node, 48 | router: PropTypes.object, 49 | }; 50 | 51 | componentWillMount() { 52 | const { router } = this.props; 53 | 54 | if (process.env.NODE_ENV === 'development' && !router) { 55 | // eslint-disable-next-line 56 | console.warn( 57 | 'RouterToUrlQuery: `router` object not found in context. Not configuring history for react-url-query.' 58 | ); 59 | return; 60 | } 61 | 62 | let history; 63 | if (router.history && router.history.push && router.history.replace) { 64 | history = router.history; 65 | } else if (router.push && router.replace) { 66 | history = router; 67 | } else if (router.transitionTo && router.replaceWith) { 68 | history = { 69 | push: router.transitionTo, 70 | replace: router.replaceWith, 71 | }; 72 | } 73 | 74 | configureUrlQuery({ 75 | history, 76 | }); 77 | } 78 | 79 | render() { 80 | const { children } = this.props; 81 | 82 | return React.Children.only(children); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/react/__tests__/RouterToUrlQuery-test.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { mount } from 'enzyme'; 4 | import { __RouterContext as RouterContext } from 'react-router'; 5 | import RouterToUrlQuery from '../RouterToUrlQuery'; 6 | import urlQueryConfig from '../../urlQueryConfig'; 7 | 8 | /* eslint-disable react/no-multi-comp */ 9 | 10 | describe('', () => { 11 | describe('with old style context (React Router < v5)', () => { 12 | it('reads router in from context and can push and replace', () => { 13 | class PutRouterInContext extends Component { 14 | static propTypes = { 15 | children: PropTypes.node, 16 | }; 17 | 18 | static childContextTypes = { 19 | router: PropTypes.object, 20 | }; 21 | 22 | // eslint-disable-next-line 23 | getChildContext() { 24 | return { 25 | router: { 26 | replace: jest.fn().mockImplementation(location => location), 27 | push: jest.fn().mockImplementation(location => location), 28 | }, 29 | }; 30 | } 31 | 32 | render() { 33 | return React.Children.only(this.props.children); 34 | } 35 | } 36 | 37 | const wrapper = mount( 38 | 39 | 40 |
41 | 42 | 43 | ); 44 | 45 | expect(wrapper.contains(
)).toBe(true); 46 | 47 | expect(urlQueryConfig.history).toBeDefined(); 48 | expect(urlQueryConfig.history.push).toBeDefined(); 49 | expect(urlQueryConfig.history.replace).toBeDefined(); 50 | 51 | urlQueryConfig.history.push(); 52 | expect(urlQueryConfig.history.push).toBeCalled(); 53 | 54 | urlQueryConfig.history.replace(); 55 | expect(urlQueryConfig.history.replace).toBeCalled(); 56 | }); 57 | 58 | it('reads router in from context and can push and replace when router has transitionTo and replaceWith', () => { 59 | class PutRouterInContext extends Component { 60 | static propTypes = { 61 | children: PropTypes.node, 62 | }; 63 | 64 | static childContextTypes = { 65 | router: PropTypes.object, 66 | }; 67 | 68 | // eslint-disable-next-line 69 | getChildContext() { 70 | return { 71 | router: { 72 | replaceWith: jest.fn().mockImplementation(location => location), 73 | transitionTo: jest.fn().mockImplementation(location => location), 74 | }, 75 | }; 76 | } 77 | 78 | render() { 79 | return React.Children.only(this.props.children); 80 | } 81 | } 82 | 83 | const wrapper = mount( 84 | 85 | 86 |
87 | 88 | 89 | ); 90 | 91 | expect(wrapper.contains(
)).toBe(true); 92 | 93 | expect(urlQueryConfig.history).toBeDefined(); 94 | expect(urlQueryConfig.history.push).toBeDefined(); 95 | expect(urlQueryConfig.history.replace).toBeDefined(); 96 | 97 | urlQueryConfig.history.push(); 98 | expect(urlQueryConfig.history.push).toBeCalled(); 99 | 100 | urlQueryConfig.history.replace(); 101 | expect(urlQueryConfig.history.replace).toBeCalled(); 102 | }); 103 | }); 104 | 105 | describe('with new style context (React Router v5+)', () => { 106 | it('reads router in from context and can push and replace', () => { 107 | const wrapper = mount( 108 | location), 110 | push: jest.fn().mockImplementation(location => location), 111 | }}> 112 | 113 |
114 | 115 | 116 | ); 117 | 118 | expect(wrapper.contains(
)).toBe(true); 119 | 120 | expect(urlQueryConfig.history).toBeDefined(); 121 | expect(urlQueryConfig.history.push).toBeDefined(); 122 | expect(urlQueryConfig.history.replace).toBeDefined(); 123 | 124 | urlQueryConfig.history.push(); 125 | expect(urlQueryConfig.history.push).toBeCalled(); 126 | 127 | urlQueryConfig.history.replace(); 128 | expect(urlQueryConfig.history.replace).toBeCalled(); 129 | }); 130 | 131 | it('reads router in from context and can push and replace when router has transitionTo and replaceWith', () => { 132 | const wrapper = mount( 133 | location), 135 | transitionTo: jest.fn().mockImplementation(location => location), 136 | }}> 137 | 138 |
139 | 140 | 141 | ); 142 | 143 | expect(wrapper.contains(
)).toBe(true); 144 | 145 | expect(urlQueryConfig.history).toBeDefined(); 146 | expect(urlQueryConfig.history.push).toBeDefined(); 147 | expect(urlQueryConfig.history.replace).toBeDefined(); 148 | 149 | urlQueryConfig.history.push(); 150 | expect(urlQueryConfig.history.push).toBeCalled(); 151 | 152 | urlQueryConfig.history.replace(); 153 | expect(urlQueryConfig.history.replace).toBeCalled(); 154 | }); 155 | 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /src/redux/__tests__/updateUrlQueryFromAction-test.js: -------------------------------------------------------------------------------- 1 | import { 2 | replaceInUrlQueryFromAction, 3 | replaceUrlQueryFromAction, 4 | multiReplaceInUrlQueryFromAction, 5 | pushInUrlQueryFromAction, 6 | pushUrlQueryFromAction, 7 | multiPushInUrlQueryFromAction, 8 | } from '../updateUrlQueryFromAction'; 9 | 10 | import { 11 | replaceInUrlQuery, 12 | replaceUrlQuery, 13 | multiReplaceInUrlQuery, 14 | pushInUrlQuery, 15 | pushUrlQuery, 16 | multiPushInUrlQuery, 17 | } from '../../updateUrlQuery'; 18 | 19 | // mock this module so we can test if it as called with correct args 20 | jest.mock('../../updateUrlQuery'); 21 | 22 | it('replaceInUrlQueryFromAction extracts correct args from action', () => { 23 | replaceInUrlQueryFromAction( 24 | { payload: { queryParam: 'foo', encodedValue: '94' } }, 25 | 'location' 26 | ); 27 | expect(replaceInUrlQuery).toBeCalledWith('foo', '94', 'location'); 28 | }); 29 | 30 | it('pushInUrlQueryFromAction extracts correct args from action', () => { 31 | pushInUrlQueryFromAction( 32 | { payload: { queryParam: 'foo', encodedValue: '94' } }, 33 | 'location' 34 | ); 35 | expect(pushInUrlQuery).toBeCalledWith('foo', '94', 'location'); 36 | }); 37 | 38 | it('multiReplaceInUrlQueryFromAction extracts correct args from action', () => { 39 | multiReplaceInUrlQueryFromAction( 40 | { payload: { encodedQuery: { foo: '94' } } }, 41 | 'location' 42 | ); 43 | expect(multiReplaceInUrlQuery).toBeCalledWith({ foo: '94' }, 'location'); 44 | }); 45 | 46 | it('multiPushInUrlQueryFromAction extracts correct args from action', () => { 47 | multiPushInUrlQueryFromAction( 48 | { payload: { encodedQuery: { foo: '94' } } }, 49 | 'location' 50 | ); 51 | expect(multiPushInUrlQuery).toBeCalledWith({ foo: '94' }, 'location'); 52 | }); 53 | 54 | it('replaceUrlQueryFromAction extracts correct args from action', () => { 55 | replaceUrlQueryFromAction( 56 | { payload: { encodedQuery: { foo: '94' } } }, 57 | 'location' 58 | ); 59 | expect(replaceUrlQuery).toBeCalledWith({ foo: '94' }, 'location'); 60 | }); 61 | 62 | it('pushUrlQueryFromAction extracts correct args from action', () => { 63 | pushUrlQueryFromAction( 64 | { payload: { encodedQuery: { foo: '94' } } }, 65 | 'location' 66 | ); 67 | expect(pushUrlQuery).toBeCalledWith({ foo: '94' }, 'location'); 68 | }); 69 | -------------------------------------------------------------------------------- /src/redux/__tests__/urlAction-test.js: -------------------------------------------------------------------------------- 1 | import urlAction, { 2 | urlUpdateAction, 3 | urlReplaceAction, 4 | urlPushAction, 5 | urlMultiReplaceInAction, 6 | urlMultiPushInAction, 7 | urlUpdateInAction, 8 | urlReplaceInAction, 9 | urlPushInAction, 10 | } from '../urlAction'; 11 | import UrlQueryParamTypes from '../../UrlQueryParamTypes'; 12 | import UrlUpdateTypes from '../../UrlUpdateTypes'; 13 | 14 | it('urlAction creates the proper action creator -> action', () => { 15 | const creator = urlAction( 16 | 'TEST_ACTION', 17 | payload => payload.toUpperCase(), 18 | meta => meta.toLowerCase() 19 | ); 20 | const action = creator('teStStrING'); 21 | expect(action).toEqual({ 22 | type: 'TEST_ACTION', 23 | meta: { 24 | urlQuery: true, 25 | value: 'teststring', 26 | }, 27 | payload: 'TESTSTRING', 28 | }); 29 | }); 30 | 31 | it('urlAction payload and meta defaults work', () => { 32 | const creator = urlAction('TEST_ACTION'); 33 | const action = creator({ foo: '123' }); 34 | expect(action).toEqual({ 35 | type: 'TEST_ACTION', 36 | meta: { 37 | urlQuery: true, 38 | }, 39 | payload: { 40 | foo: '123', 41 | }, 42 | }); 43 | }); 44 | 45 | it('urlAction handles nully meta', () => { 46 | const creator = urlAction('TEST_ACTION', undefined, () => null); 47 | const action = creator({ foo: '123' }); 48 | expect(action).toEqual({ 49 | type: 'TEST_ACTION', 50 | meta: { 51 | urlQuery: true, 52 | }, 53 | payload: { 54 | foo: '123', 55 | }, 56 | }); 57 | }); 58 | 59 | it('urlUpdateAction creates the proper action creator -> action', () => { 60 | const creator = urlUpdateAction( 61 | 'TEST_ACTION', 62 | query => ({ foo: String(query.foo), bar: '1' }), 63 | UrlUpdateTypes.push 64 | ); 65 | const action = creator({ foo: 137 }); 66 | expect(action).toEqual({ 67 | type: 'TEST_ACTION', 68 | meta: { 69 | urlQuery: true, 70 | updateType: UrlUpdateTypes.push, 71 | }, 72 | payload: { 73 | encodedQuery: { foo: '137', bar: '1' }, 74 | decodedQuery: { foo: 137 }, 75 | }, 76 | }); 77 | }); 78 | 79 | it('urlUpdateAction default encodeQuery and updateType work', () => { 80 | const creator = urlUpdateAction('TEST_ACTION'); 81 | const action = creator({ foo: '137' }); 82 | expect(action).toEqual({ 83 | type: 'TEST_ACTION', 84 | meta: { 85 | urlQuery: true, 86 | updateType: UrlUpdateTypes.replace, 87 | }, 88 | payload: { 89 | encodedQuery: { foo: '137' }, 90 | decodedQuery: { foo: '137' }, 91 | }, 92 | }); 93 | }); 94 | 95 | it('urlReplaceAction creates the proper action creator -> action', () => { 96 | const creator = urlReplaceAction('TEST_ACTION', query => ({ 97 | foo: String(query.foo), 98 | bar: '1', 99 | })); 100 | const action = creator({ foo: 137 }); 101 | expect(action).toEqual({ 102 | type: 'TEST_ACTION', 103 | meta: { 104 | urlQuery: true, 105 | updateType: UrlUpdateTypes.replace, 106 | }, 107 | payload: { 108 | encodedQuery: { foo: '137', bar: '1' }, 109 | decodedQuery: { foo: 137 }, 110 | }, 111 | }); 112 | }); 113 | 114 | it('urlPushAction creates the proper action creator -> action', () => { 115 | const creator = urlPushAction('TEST_ACTION', query => ({ 116 | foo: String(query.foo), 117 | bar: '1', 118 | })); 119 | const action = creator({ foo: 137 }); 120 | expect(action).toEqual({ 121 | type: 'TEST_ACTION', 122 | meta: { 123 | urlQuery: true, 124 | updateType: UrlUpdateTypes.push, 125 | }, 126 | payload: { 127 | encodedQuery: { foo: '137', bar: '1' }, 128 | decodedQuery: { foo: 137 }, 129 | }, 130 | }); 131 | }); 132 | 133 | it('urlMultiReplaceInAction creates the proper action creator -> action', () => { 134 | const creator = urlMultiReplaceInAction('TEST_ACTION', query => ({ 135 | foo: String(query.foo), 136 | bar: '1', 137 | })); 138 | const action = creator({ foo: 137 }); 139 | expect(action).toEqual({ 140 | type: 'TEST_ACTION', 141 | meta: { 142 | urlQuery: true, 143 | updateType: UrlUpdateTypes.multiReplaceIn, 144 | }, 145 | payload: { 146 | encodedQuery: { foo: '137', bar: '1' }, 147 | decodedQuery: { foo: 137 }, 148 | }, 149 | }); 150 | }); 151 | 152 | it('urlMultiPushInAction creates the proper action creator -> action', () => { 153 | const creator = urlMultiPushInAction('TEST_ACTION', query => ({ 154 | foo: String(query.foo), 155 | bar: '1', 156 | })); 157 | const action = creator({ foo: 137 }); 158 | expect(action).toEqual({ 159 | type: 'TEST_ACTION', 160 | meta: { 161 | urlQuery: true, 162 | updateType: UrlUpdateTypes.multiPushIn, 163 | }, 164 | payload: { 165 | encodedQuery: { foo: '137', bar: '1' }, 166 | decodedQuery: { foo: 137 }, 167 | }, 168 | }); 169 | }); 170 | 171 | it('urlUpdateInAction creates the proper action creator -> action', () => { 172 | const creator = urlUpdateInAction( 173 | 'TEST_ACTION', 174 | 'foo', 175 | UrlQueryParamTypes.number, 176 | UrlUpdateTypes.push 177 | ); 178 | const action = creator(99); 179 | expect(action).toEqual({ 180 | type: 'TEST_ACTION', 181 | meta: { 182 | urlQuery: true, 183 | updateType: UrlUpdateTypes.push, 184 | }, 185 | payload: { 186 | queryParam: 'foo', 187 | encodedValue: '99', 188 | decodedValue: 99, 189 | type: UrlQueryParamTypes.number, 190 | }, 191 | }); 192 | }); 193 | 194 | it('urlReplaceInAction creates the proper action creator -> action', () => { 195 | const creator = urlReplaceInAction( 196 | 'TEST_ACTION', 197 | 'foo', 198 | UrlQueryParamTypes.array 199 | ); 200 | const action = creator(['bar', 'baz']); 201 | expect(action).toEqual({ 202 | type: 'TEST_ACTION', 203 | meta: { 204 | urlQuery: true, 205 | updateType: UrlUpdateTypes.replaceIn, 206 | }, 207 | payload: { 208 | queryParam: 'foo', 209 | encodedValue: 'bar_baz', 210 | decodedValue: ['bar', 'baz'], 211 | type: UrlQueryParamTypes.array, 212 | }, 213 | }); 214 | }); 215 | 216 | it('urlPushInAction creates the proper action creator -> action', () => { 217 | const creator = urlPushInAction( 218 | 'TEST_ACTION', 219 | 'foo', 220 | UrlQueryParamTypes.number 221 | ); 222 | const action = creator(123); 223 | expect(action).toEqual({ 224 | type: 'TEST_ACTION', 225 | meta: { 226 | urlQuery: true, 227 | updateType: UrlUpdateTypes.pushIn, 228 | }, 229 | payload: { 230 | queryParam: 'foo', 231 | encodedValue: '123', 232 | decodedValue: 123, 233 | type: UrlQueryParamTypes.number, 234 | }, 235 | }); 236 | }); 237 | -------------------------------------------------------------------------------- /src/redux/__tests__/urlQueryMiddleware-test.js: -------------------------------------------------------------------------------- 1 | import urlQueryMiddleware from '../urlQueryMiddleware'; 2 | 3 | import urlQueryReducer from '../urlQueryReducer'; 4 | import urlQueryConfig from '../../urlQueryConfig'; 5 | import configureUrlQuery from '../../configureUrlQuery'; 6 | 7 | jest.mock('../urlQueryReducer'); 8 | 9 | it('only runs on url query actions', () => { 10 | const options = { reducer: jest.fn(), readLocationFromStore: false }; 11 | const store = { getState: () => ({}) }; 12 | const next = jest.fn(); 13 | const action = { type: 'ACTION_TYPE' }; 14 | urlQueryMiddleware(options)(store)(next)(action); 15 | expect(next).toBeCalledWith(action); 16 | expect(options.reducer).not.toBeCalled(); 17 | 18 | const action2 = { type: 'ACTION_TYPE', meta: { one: 1 } }; 19 | urlQueryMiddleware(options)(store)(next)(action2); 20 | expect(next).toBeCalledWith(action2); 21 | expect(options.reducer).not.toBeCalled(); 22 | 23 | const action3 = { type: 'ACTION_TYPE', meta: { urlQuery: true } }; 24 | urlQueryMiddleware(options)(store)(next)(action3); 25 | expect(next).not.toBeCalledWith(action3); 26 | expect(options.reducer).toBeCalledWith(action3); 27 | }); 28 | 29 | it('passes to next reducer if shortciruit is false', () => { 30 | const options = { 31 | reducer: jest.fn(), 32 | readLocationFromStore: false, 33 | shortcircuit: false, 34 | }; 35 | const store = { getState: () => ({}) }; 36 | const next = jest.fn(); 37 | const action = { type: 'ACTION_TYPE', meta: { urlQuery: true } }; 38 | urlQueryMiddleware(options)(store)(next)(action); 39 | expect(options.reducer).toBeCalledWith(action); 40 | expect(next).toBeCalledWith(action); 41 | }); 42 | 43 | it('reads location from store', () => { 44 | const options = { 45 | reducer: jest.fn(), 46 | readLocationFromStore: state => state.location, 47 | }; 48 | const store = { getState: () => ({ location: 'location' }) }; 49 | const next = jest.fn(); 50 | const action = { type: 'ACTION_TYPE', meta: { urlQuery: true } }; 51 | urlQueryMiddleware(options)(store)(next)(action); 52 | expect(options.reducer).toBeCalledWith(action, 'location'); 53 | }); 54 | 55 | it('uses reducer from urlQueryConfig if not passed in', () => { 56 | configureUrlQuery({ reducer: jest.fn() }); 57 | const options = { readLocationFromStore: false }; 58 | const store = { getState: () => ({}) }; 59 | const next = jest.fn(); 60 | const action = { type: 'ACTION_TYPE', meta: { urlQuery: true } }; 61 | urlQueryMiddleware(options)(store)(next)(action); 62 | expect(urlQueryConfig.reducer).toBeCalledWith(action); 63 | 64 | // reset urlQueryConfig 65 | configureUrlQuery({ reducer: undefined }); 66 | }); 67 | 68 | it('uses default reducer if none in options or urlQueryConfig', () => { 69 | const options = { readLocationFromStore: false }; 70 | const store = { getState: () => ({}) }; 71 | const next = jest.fn(); 72 | const action = { type: 'ACTION_TYPE', meta: { urlQuery: true } }; 73 | urlQueryMiddleware(options)(store)(next)(action); 74 | expect(urlQueryReducer).toBeCalledWith(action); 75 | }); 76 | 77 | it('works given no options', () => { 78 | const options = undefined; 79 | const store = { getState: () => ({}) }; 80 | const next = jest.fn(); 81 | const action = { type: 'ACTION_TYPE', meta: { urlQuery: true } }; 82 | urlQueryMiddleware(options)(store)(next)(action); 83 | expect(urlQueryReducer).toBeCalledWith(action, undefined); 84 | }); 85 | -------------------------------------------------------------------------------- /src/redux/__tests__/urlQueryReducer-test.js: -------------------------------------------------------------------------------- 1 | import urlQueryReducer from '../urlQueryReducer'; 2 | import UrlUpdateTypes from '../../UrlUpdateTypes'; 3 | 4 | import { 5 | replaceInUrlQueryFromAction, 6 | replaceUrlQueryFromAction, 7 | multiReplaceInUrlQueryFromAction, 8 | pushInUrlQueryFromAction, 9 | pushUrlQueryFromAction, 10 | multiPushInUrlQueryFromAction, 11 | } from '../updateUrlQueryFromAction'; 12 | 13 | // mock this module so we can test if it as called with correct args 14 | jest.mock('../updateUrlQueryFromAction'); 15 | 16 | it('reduces replaceIn', () => { 17 | const action = { meta: { updateType: UrlUpdateTypes.replaceIn } }; 18 | urlQueryReducer(action, 'location'); 19 | expect(replaceInUrlQueryFromAction).toBeCalledWith(action, 'location'); 20 | }); 21 | 22 | it('reduces pushIn', () => { 23 | const action = { meta: { updateType: UrlUpdateTypes.pushIn } }; 24 | urlQueryReducer(action, 'location'); 25 | expect(pushInUrlQueryFromAction).toBeCalledWith(action, 'location'); 26 | }); 27 | 28 | it('reduces replace', () => { 29 | const action = { meta: { updateType: UrlUpdateTypes.replace } }; 30 | urlQueryReducer(action, 'location'); 31 | expect(replaceUrlQueryFromAction).toBeCalledWith(action, 'location'); 32 | }); 33 | 34 | it('reduces push', () => { 35 | const action = { meta: { updateType: UrlUpdateTypes.push } }; 36 | urlQueryReducer(action, 'location'); 37 | expect(pushUrlQueryFromAction).toBeCalledWith(action, 'location'); 38 | }); 39 | 40 | it('reduces multiReplaceIn', () => { 41 | const action = { meta: { updateType: UrlUpdateTypes.multiReplaceIn } }; 42 | urlQueryReducer(action, 'location'); 43 | expect(multiReplaceInUrlQueryFromAction).toBeCalledWith(action, 'location'); 44 | }); 45 | 46 | it('reduces multiPushIn', () => { 47 | const action = { meta: { updateType: UrlUpdateTypes.multiPushIn } }; 48 | urlQueryReducer(action, 'location'); 49 | expect(multiPushInUrlQueryFromAction).toBeCalledWith(action, 'location'); 50 | }); 51 | 52 | it('does not fail with nully action', () => { 53 | urlQueryReducer(undefined, 'location'); 54 | }); 55 | 56 | it('does not fail with action with no meta', () => { 57 | urlQueryReducer({}, 'location'); 58 | }); 59 | -------------------------------------------------------------------------------- /src/redux/updateUrlQueryFromAction.js: -------------------------------------------------------------------------------- 1 | import { 2 | replaceInUrlQuery, 3 | replaceUrlQuery, 4 | multiReplaceInUrlQuery, 5 | pushInUrlQuery, 6 | pushUrlQuery, 7 | multiPushInUrlQuery, 8 | } from '../updateUrlQuery'; 9 | 10 | export function replaceUrlQueryFromAction(action, location) { 11 | const { encodedQuery } = action.payload; 12 | replaceUrlQuery(encodedQuery, location); 13 | } 14 | 15 | export function pushUrlQueryFromAction(action, location) { 16 | const { encodedQuery } = action.payload; 17 | pushUrlQuery(encodedQuery, location); 18 | } 19 | 20 | export function replaceInUrlQueryFromAction(action, location) { 21 | const { queryParam, encodedValue } = action.payload; 22 | replaceInUrlQuery(queryParam, encodedValue, location); 23 | } 24 | 25 | export function pushInUrlQueryFromAction(action, location) { 26 | const { queryParam, encodedValue } = action.payload; 27 | pushInUrlQuery(queryParam, encodedValue, location); 28 | } 29 | 30 | export function multiReplaceInUrlQueryFromAction(action, location) { 31 | const { encodedQuery } = action.payload; 32 | multiReplaceInUrlQuery(encodedQuery, location); 33 | } 34 | 35 | export function multiPushInUrlQueryFromAction(action, location) { 36 | const { encodedQuery } = action.payload; 37 | multiPushInUrlQuery(encodedQuery, location); 38 | } 39 | -------------------------------------------------------------------------------- /src/redux/urlAction.js: -------------------------------------------------------------------------------- 1 | import { encode } from '../serialize'; 2 | import UrlUpdateTypes from '../UrlUpdateTypes'; 3 | 4 | export default function urlAction( 5 | actionType, 6 | payload = d => d, 7 | meta = () => {} 8 | ) { 9 | return function urlActionCreator(...args) { 10 | let metaFromAction = meta(...args); 11 | if (metaFromAction == null) { 12 | metaFromAction = {}; 13 | 14 | // we need meta to be an object so it merges in with the urlQuery meta property. 15 | } else if (typeof metaFromAction !== 'object') { 16 | metaFromAction = { value: metaFromAction }; 17 | } 18 | 19 | return { 20 | type: actionType, 21 | meta: { 22 | ...metaFromAction, 23 | // we need urlQuery set so the middleware knows to read this action 24 | urlQuery: true, 25 | }, 26 | payload: payload(...args), 27 | }; 28 | }; 29 | } 30 | 31 | /** 32 | * Helper function for creating URL action creators 33 | * 34 | * For example in your actions.js file: 35 | * 36 | * export const changeFoo = urlUpdateAction( 37 | * 'CHANGE_MANY', 38 | * (newQuery) => ({ 39 | * fooInUrl: encode(UrlQueryParamTypes.number, newQuery.foo), 40 | * bar: 'par', 41 | * arr: encode(UrlQueryParamTypes.array, ['T', 'Y']), 42 | * }), 43 | * 'replace'); 44 | * 45 | * The second parameter should be an encoder function that takes a decodedQuery 46 | * and returns an encodedQuery, 47 | * encoding each value in the decodedQuery object. 48 | * You need this because when using Redux Actions, 49 | * urlPropsQueryConfig is only used for decoding; 50 | * you have to implement the encoding here. 51 | * Also see changeMany [in the examples](https://github.com/pbeshai/react-url-query/tree/master/examples/redux-with-actions/src/state/actions.js). 52 | */ 53 | export function urlUpdateAction( 54 | actionType, 55 | encodeQuery = d => d, 56 | updateType = UrlUpdateTypes.replace 57 | ) { 58 | return urlAction( 59 | actionType, 60 | decodedQuery => ({ 61 | encodedQuery: encodeQuery(decodedQuery), 62 | decodedQuery, 63 | }), 64 | () => ({ updateType }) 65 | ); 66 | } 67 | 68 | export function urlReplaceAction(actionType, encodeQuery) { 69 | return urlUpdateAction(actionType, encodeQuery, UrlUpdateTypes.replace); 70 | } 71 | 72 | export function urlPushAction(actionType, encodeQuery) { 73 | return urlUpdateAction(actionType, encodeQuery, UrlUpdateTypes.push); 74 | } 75 | 76 | export function urlMultiReplaceInAction(actionType, encodeQuery) { 77 | return urlUpdateAction(actionType, encodeQuery, UrlUpdateTypes.multiReplaceIn); 78 | } 79 | 80 | export function urlMultiPushInAction(actionType, encodeQuery) { 81 | return urlUpdateAction(actionType, encodeQuery, UrlUpdateTypes.multiPushIn); 82 | } 83 | 84 | /** 85 | * Helper function for creating URL action creators 86 | * 87 | * For example in your actions.js file: 88 | * export const changeFoo = urlUpdateInAction('CHANGE_FOO', 'foo', 'number', 'replaceIn'); 89 | * 90 | */ 91 | export function urlUpdateInAction( 92 | actionType, 93 | queryParam, 94 | valueType, 95 | updateType 96 | ) { 97 | return urlAction( 98 | actionType, 99 | decodedValue => ({ 100 | queryParam, 101 | encodedValue: encode(valueType, decodedValue), 102 | decodedValue, 103 | type: valueType, 104 | }), 105 | () => ({ updateType }) 106 | ); 107 | } 108 | 109 | export function urlReplaceInAction(actionType, queryParam, valueType) { 110 | return urlUpdateInAction( 111 | actionType, 112 | queryParam, 113 | valueType, 114 | UrlUpdateTypes.replaceIn 115 | ); 116 | } 117 | 118 | export function urlPushInAction(actionType, queryParam, valueType) { 119 | return urlUpdateInAction( 120 | actionType, 121 | queryParam, 122 | valueType, 123 | UrlUpdateTypes.pushIn 124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /src/redux/urlQueryMiddleware.js: -------------------------------------------------------------------------------- 1 | import urlQueryReducer from './urlQueryReducer'; 2 | import urlQueryConfig from '../urlQueryConfig'; 3 | 4 | /** 5 | * Middleware to handle updating the URL query params 6 | */ 7 | const urlQueryMiddleware = (options = {}) => ({ 8 | getState, 9 | }) => next => action => { 10 | // if not a URL action, do nothing. 11 | if (!action.meta || !action.meta.urlQuery) { 12 | return next(action); 13 | } 14 | 15 | // otherwise, handle with URL handler -- doesn't go to Redux dispatcher 16 | // update the URL 17 | 18 | // use the default reducer if none provided 19 | const reducer = options.reducer || urlQueryConfig.reducer || urlQueryReducer; 20 | 21 | // if configured to read from the redux store (react-router-redux), do so and pass it to 22 | // the reducer 23 | const readLocationFromStore = 24 | options.readLocationFromStore == null 25 | ? urlQueryConfig.readLocationFromStore 26 | : options.readLocationFromStore; 27 | 28 | if (readLocationFromStore) { 29 | const location = readLocationFromStore(getState()); 30 | reducer(action, location); 31 | } else { 32 | reducer(action); 33 | } 34 | 35 | // shortcircuit by default (don't pass to redux store), unless explicitly set 36 | // to false. 37 | if (options.shortcircuit === false) { 38 | return next(action); 39 | } 40 | 41 | return undefined; 42 | }; 43 | 44 | export default urlQueryMiddleware; 45 | -------------------------------------------------------------------------------- /src/redux/urlQueryReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | replaceInUrlQueryFromAction, 3 | replaceUrlQueryFromAction, 4 | multiReplaceInUrlQueryFromAction, 5 | pushInUrlQueryFromAction, 6 | pushUrlQueryFromAction, 7 | multiPushInUrlQueryFromAction, 8 | } from './updateUrlQueryFromAction'; 9 | import UrlUpdateTypes from '../UrlUpdateTypes'; 10 | 11 | /** 12 | * Reducer that handles actions that modify the URL query parameters. 13 | * In this case, the actions replace a single query parameter at a time. 14 | * 15 | * NOTE: This is *NOT* a Redux reducer. It does not map from (state, action) -> state. 16 | * Instead it "reduces" actions into URL query parameter state. NOT redux state. 17 | */ 18 | export default function urlQueryReducer(action, location) { 19 | const updateType = action && action.meta && action.meta.updateType; 20 | 21 | switch (updateType) { 22 | case UrlUpdateTypes.replaceIn: 23 | return replaceInUrlQueryFromAction(action, location); 24 | case UrlUpdateTypes.replace: 25 | return replaceUrlQueryFromAction(action, location); 26 | case UrlUpdateTypes.multiReplaceIn: 27 | return multiReplaceInUrlQueryFromAction(action, location); 28 | case UrlUpdateTypes.pushIn: 29 | return pushInUrlQueryFromAction(action, location); 30 | case UrlUpdateTypes.push: 31 | return pushUrlQueryFromAction(action, location); 32 | case UrlUpdateTypes.multiPushIn: 33 | return multiPushInUrlQueryFromAction(action, location); 34 | default: 35 | break; 36 | } 37 | 38 | if (process.env.NODE_ENV === 'development') { 39 | console.warn( 40 | `urlQueryReducer encountered unhandled action.meta.updateType ${ 41 | updateType 42 | }.`, // eslint-disable-line no-console 43 | 'action =', 44 | action 45 | ); 46 | } 47 | 48 | return undefined; 49 | } 50 | -------------------------------------------------------------------------------- /src/updateUrlQuery.js: -------------------------------------------------------------------------------- 1 | import { stringify, parse as parseQueryString } from 'query-string'; 2 | 3 | import urlQueryConfig from './urlQueryConfig'; 4 | import UrlUpdateTypes from './UrlUpdateTypes'; 5 | 6 | function getLocation(location) { 7 | if (location) { 8 | return location; 9 | } 10 | 11 | // if no location provided, check history 12 | const { history } = urlQueryConfig; 13 | 14 | // if not in history, use window 15 | return history.location ? history.location : window.location; 16 | } 17 | 18 | function mergeLocationQueryOrSearch(location, newQuery) { 19 | // if location.query exists, update the query in location. otherwise update the search string 20 | // replace location.query 21 | if (location.query) { 22 | return { 23 | ...location, 24 | query: newQuery, 25 | search: undefined, // this is necessary at least for React Router v4 26 | }; 27 | } 28 | 29 | // replace location.search 30 | const queryStr = stringify(newQuery); 31 | return { 32 | ...location, 33 | search: queryStr.length ? `?${queryStr}` : undefined, 34 | }; 35 | } 36 | 37 | function updateLocation(newQuery, location) { 38 | location = getLocation(location); 39 | 40 | // remove query params that are nully or an empty strings. 41 | // note: these values are assumed to be already encoded as strings. 42 | const filteredQuery = Object.keys(newQuery).reduce( 43 | (queryAccumulator, queryParam) => { 44 | const encodedValue = newQuery[queryParam]; 45 | if (encodedValue != null && encodedValue !== '') { 46 | queryAccumulator[queryParam] = encodedValue; 47 | } 48 | 49 | return queryAccumulator; 50 | }, 51 | {} 52 | ); 53 | 54 | const newLocation = mergeLocationQueryOrSearch(location, filteredQuery); 55 | 56 | // remove the key from the location 57 | delete newLocation.key; 58 | 59 | return newLocation; 60 | } 61 | 62 | function updateInLocation(queryParam, encodedValue, location) { 63 | location = getLocation(location); 64 | 65 | // if a query is there, use it, otherwise parse the search string 66 | const currQuery = location.query || parseQueryString(location.search); 67 | 68 | const newQuery = { 69 | ...currQuery, 70 | [queryParam]: encodedValue, 71 | }; 72 | 73 | // remove if it is nully or an empty string when encoded 74 | if (encodedValue == null || encodedValue === '') { 75 | delete newQuery[queryParam]; 76 | } 77 | 78 | const newLocation = mergeLocationQueryOrSearch(location, newQuery); 79 | 80 | // remove the key from the location 81 | delete newLocation.key; 82 | 83 | return newLocation; 84 | } 85 | 86 | /** 87 | * Update multiple parts of the location at once 88 | */ 89 | function multiUpdateInLocation(queryReplacements, location) { 90 | location = getLocation(location); 91 | 92 | // if a query is there, use it, otherwise parse the search string 93 | const currQuery = location.query || parseQueryString(location.search); 94 | 95 | const newQuery = { 96 | ...currQuery, 97 | ...queryReplacements, 98 | }; 99 | 100 | // remove if it is nully or an empty string when encoded 101 | Object.keys(queryReplacements).forEach(queryParam => { 102 | const encodedValue = queryReplacements[queryParam]; 103 | if (encodedValue == null || encodedValue === '') { 104 | delete newQuery[queryParam]; 105 | } 106 | }); 107 | 108 | const newLocation = mergeLocationQueryOrSearch(location, newQuery); 109 | 110 | // remove the key from the location 111 | delete newLocation.key; 112 | 113 | return newLocation; 114 | } 115 | 116 | export function replaceUrlQuery(newQuery, location) { 117 | const newLocation = updateLocation(newQuery, location); 118 | return urlQueryConfig.history.replace(newLocation); 119 | } 120 | 121 | export function pushUrlQuery(newQuery, location) { 122 | const newLocation = updateLocation(newQuery, location); 123 | return urlQueryConfig.history.push(newLocation); 124 | } 125 | 126 | export function replaceInUrlQuery(queryParam, encodedValue, location) { 127 | const newLocation = updateInLocation(queryParam, encodedValue, location); 128 | return urlQueryConfig.history.replace(newLocation); 129 | } 130 | 131 | export function pushInUrlQuery(queryParam, encodedValue, location) { 132 | const newLocation = updateInLocation(queryParam, encodedValue, location); 133 | return urlQueryConfig.history.push(newLocation); 134 | } 135 | 136 | /** 137 | * Replace multiple query parameters in a URL at once with only one 138 | * call to `history.replace` 139 | * 140 | * @param {Object} queryReplacements Object representing the params and 141 | * their encoded values. { queryParam: encodedValue, ... } 142 | */ 143 | export function multiReplaceInUrlQuery(queryReplacements, location) { 144 | const newLocation = multiUpdateInLocation(queryReplacements, location); 145 | return urlQueryConfig.history.replace(newLocation); 146 | } 147 | 148 | export function multiPushInUrlQuery(queryReplacements, location) { 149 | const newLocation = multiUpdateInLocation(queryReplacements, location); 150 | return urlQueryConfig.history.push(newLocation); 151 | } 152 | 153 | /** 154 | * Updates a single value in a query based on the type 155 | */ 156 | export function updateUrlQuerySingle( 157 | updateType = UrlUpdateTypes.replaceIn, 158 | queryParam, 159 | encodedValue, 160 | location 161 | ) { 162 | if (updateType === UrlUpdateTypes.replaceIn) { 163 | return replaceInUrlQuery(queryParam, encodedValue, location); 164 | } 165 | if (updateType === UrlUpdateTypes.pushIn) { 166 | return pushInUrlQuery(queryParam, encodedValue, location); 167 | } 168 | 169 | // for these, wrap it in a whole new query object 170 | const newQuery = { [queryParam]: encodedValue }; 171 | if (updateType === UrlUpdateTypes.replace) { 172 | return replaceUrlQuery(newQuery, location); 173 | } 174 | if (updateType === UrlUpdateTypes.push) { 175 | return pushUrlQuery(newQuery, location); 176 | } 177 | 178 | return undefined; 179 | } 180 | 181 | /** 182 | * Updates a multiple values in a query based on the type 183 | */ 184 | export function updateUrlQueryMulti( 185 | updateType = UrlUpdateTypes.replaceIn, 186 | queryReplacements, 187 | location 188 | ) { 189 | if (updateType === UrlUpdateTypes.replaceIn) { 190 | return multiReplaceInUrlQuery(queryReplacements, location); 191 | } 192 | if (updateType === UrlUpdateTypes.pushIn) { 193 | return multiPushInUrlQuery(queryReplacements, location); 194 | } 195 | 196 | if (updateType === UrlUpdateTypes.replace) { 197 | return replaceUrlQuery(queryReplacements, location); 198 | } 199 | if (updateType === UrlUpdateTypes.push) { 200 | return pushUrlQuery(queryReplacements, location); 201 | } 202 | 203 | return undefined; 204 | } 205 | -------------------------------------------------------------------------------- /src/urlQueryConfig.js: -------------------------------------------------------------------------------- 1 | // function to create the singleton options object that can be shared 2 | // throughout an application 3 | function createUrlQueryConfig() { 4 | // default options 5 | return { 6 | // add in generated URL change handlers based on a urlPropsQueryConfig if provided 7 | addUrlChangeHandlers: true, 8 | 9 | // add in `props.params` from react-router to the url object 10 | addRouterParams: true, 11 | 12 | // function to specify change handler name (onChange) 13 | changeHandlerName: propName => 14 | `onChange${propName[0].toUpperCase()}${propName.substring(1)}`, 15 | 16 | // use this history if no history is specified 17 | history: { 18 | push() { 19 | // eslint-disable-next-line 20 | console.error( 21 | 'No history provided to react-url-query. Please provide one via configureUrlQuery.' 22 | ); 23 | }, 24 | replace() { 25 | // eslint-disable-next-line 26 | console.error( 27 | 'No history provided to react-url-query. Please provide one via configureUrlQuery.' 28 | ); 29 | }, 30 | }, 31 | 32 | // reads in location from react-router-redux if available and passes it 33 | // to the reducer in the urlQueryMiddleware 34 | readLocationFromStore(state) { 35 | if (state && state.routing) { 36 | return state.routing.locationBeforeTransitions; 37 | } 38 | 39 | return undefined; 40 | }, 41 | /** 42 | * The separator between entries 43 | * @default {String} "_" 44 | */ 45 | entrySeparator: '_', 46 | /** 47 | * The separator between keys and values 48 | * @default {String} "-" 49 | */ 50 | keyValSeparator: '-', 51 | }; 52 | } 53 | 54 | export default createUrlQueryConfig(); 55 | -------------------------------------------------------------------------------- /src/urlQueryDecoder.js: -------------------------------------------------------------------------------- 1 | import { decode } from './serialize'; 2 | 3 | /** 4 | * Decodes a query based on the config. It compares against cached values to see 5 | * if decoding is necessary or if it can reuse old values. 6 | * 7 | * @param {Object} query The query object (typically from props.location.query) 8 | * 9 | * @return {Object} the decoded values `{ key: decodedValue, ... }` 10 | */ 11 | export default function urlQueryDecoder(config) { 12 | let cachedQuery; 13 | let cachedDecodedQuery; 14 | 15 | return function decodeQueryWithCache(query) { 16 | // decode the query 17 | const decodedQuery = Object.keys(config).reduce((decoded, key) => { 18 | const keyConfig = config[key]; 19 | // read from the URL key if provided, otherwise use the key 20 | const { queryParam = key } = keyConfig; 21 | const encodedValue = query[queryParam]; 22 | 23 | let decodedValue; 24 | // reused cached value 25 | if ( 26 | cachedQuery && 27 | cachedQuery[queryParam] !== undefined && 28 | cachedQuery[queryParam] === encodedValue 29 | ) { 30 | decodedValue = cachedDecodedQuery[key]; 31 | 32 | // not cached, decode now 33 | // only decode if no validate provided or validate is provided and the encoded value is valid 34 | } else { 35 | decodedValue = decode( 36 | keyConfig.type, 37 | encodedValue, 38 | keyConfig.defaultValue 39 | ); 40 | } 41 | 42 | // validate the decoded value if configured. set to undefined if not valid 43 | if ( 44 | decodedValue !== undefined && 45 | keyConfig.validate && 46 | !keyConfig.validate(decodedValue) 47 | ) { 48 | decodedValue = undefined; 49 | } 50 | 51 | decoded[key] = decodedValue; 52 | return decoded; 53 | }, {}); 54 | 55 | // update the cache 56 | cachedQuery = query; 57 | cachedDecodedQuery = decodedQuery; 58 | 59 | return decodedQuery; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /src/urlQueryEncoder.js: -------------------------------------------------------------------------------- 1 | import { encode } from './serialize'; 2 | 3 | /** 4 | * Encodes a query based on the config. Similarly to `encode`, it does not respect the `defaultValue` 5 | * field, so any missing values must be specified explicitly. 6 | * 7 | * @param {Object} query The query object (typically from props.location.query) 8 | * 9 | * @return {Object} the encoded values `{ key: encodedValue, ... }` 10 | */ 11 | export default function urlQueryEncoder(config) { 12 | return function encodeQuery(query) { 13 | // encode the query 14 | const encodedQuery = Object.keys(config).reduce((encoded, key) => { 15 | const keyConfig = config[key]; 16 | // read from the URL key if provided, otherwise use the key 17 | const { queryParam = key } = keyConfig; 18 | const decodedValue = query[key]; 19 | 20 | const encodedValue = encode(keyConfig.type, decodedValue); 21 | 22 | encoded[queryParam] = encodedValue; 23 | return encoded; 24 | }, {}); 25 | 26 | return encodedQuery; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/__tests__/subquery-test.js: -------------------------------------------------------------------------------- 1 | import subquery from '../subquery'; 2 | 3 | it('works with nully query', () => { 4 | expect(subquery(undefined, 'one')).toBeFalsy(); 5 | }); 6 | 7 | it('returns empty object if no params', () => { 8 | expect(subquery({ one: 'one', two: 'two' })).toEqual({}); 9 | }); 10 | 11 | it('returns proper subquery', () => { 12 | expect( 13 | subquery({ one: 'one', two: 'two', thr: 'ree' }, 'two', 'one') 14 | ).toEqual({ one: 'one', two: 'two' }); 15 | }); 16 | 17 | it('returns a new object even if all keys match', () => { 18 | const input = { one: 'one' }; 19 | const result = subquery(input, 'one'); 20 | expect(result).toEqual({ one: 'one' }); 21 | expect(result).not.toBe(input); 22 | }); 23 | -------------------------------------------------------------------------------- /src/utils/__tests__/subqueryOmit-test.js: -------------------------------------------------------------------------------- 1 | import subqueryOmit from '../subqueryOmit'; 2 | 3 | it('works with nully query', () => { 4 | expect(subqueryOmit(undefined, 'one')).toBeFalsy(); 5 | }); 6 | 7 | it('returns full input object if no params', () => { 8 | const input = { one: 'one', two: 'two' }; 9 | expect(subqueryOmit(input)).toEqual(input); 10 | }); 11 | 12 | it('returns proper subquery', () => { 13 | expect( 14 | subqueryOmit({ one: 'one', two: 'two', thr: 'ree' }, 'two', 'one') 15 | ).toEqual({ thr: 'ree' }); 16 | }); 17 | 18 | it('returns an empty object when all keys omitted', () => { 19 | expect(subqueryOmit({ one: 'one' }, 'one')).toEqual({}); 20 | }); 21 | -------------------------------------------------------------------------------- /src/utils/subquery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function to get only parts of a query. Specify 3 | * which parameters to include. 4 | */ 5 | export default function subquery(query, ...params) { 6 | if (!query) { 7 | return query; 8 | } 9 | 10 | return params.reduce((newQuery, param) => { 11 | newQuery[param] = query[param]; 12 | return newQuery; 13 | }, {}); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/subqueryOmit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function to get only parts of a query. Specify 3 | * which parameters to omit. 4 | */ 5 | export default function subqueryOmit(query, ...omitParams) { 6 | if (!query) { 7 | return query; 8 | } 9 | 10 | return Object.keys(query) 11 | .filter(param => !omitParams.includes(param)) 12 | .reduce((newQuery, param) => { 13 | newQuery[param] = query[param]; 14 | return newQuery; 15 | }, {}); 16 | } 17 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | 5 | var env = process.env.NODE_ENV; 6 | var config = { 7 | module: { 8 | loaders: [ 9 | { test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/ } 10 | ] 11 | }, 12 | output: { 13 | library: 'react-url-query', 14 | libraryTarget: 'umd' 15 | }, 16 | plugins: [ 17 | new webpack.optimize.OccurrenceOrderPlugin(), 18 | new webpack.DefinePlugin({ 19 | 'process.env.NODE_ENV': JSON.stringify(env) 20 | }) 21 | ], 22 | externals: { 23 | "react": { 24 | "commonjs": "react", 25 | "commonjs2": "react", 26 | "amd": "react", 27 | }, 28 | "react-dom": { 29 | "commonjs": "reac-dom", 30 | "commonjs2": "reac-dom", 31 | "amd": "reac-dom", 32 | }, 33 | }, 34 | }; 35 | 36 | if (env === 'production') { 37 | config.plugins.push( 38 | new webpack.optimize.UglifyJsPlugin({ 39 | compressor: { 40 | pure_getters: true, 41 | unsafe: true, 42 | unsafe_comps: true, 43 | warnings: false 44 | } 45 | }) 46 | ); 47 | } 48 | 49 | module.exports = config; 50 | --------------------------------------------------------------------------------