From 848ba1060dd4f46ed424574ca5202097110d91a0 Mon Sep 17 00:00:00 2001 From: Dorian Maliszewski Date: Mon, 22 Mar 2021 11:07:35 +0100 Subject: [PATCH] fix: query params hook (#85) * fix: effect cleanup debounce like * fix: rebased * fix: tests and add possibility to replace * feat: split methods --- packages/use-query-params/package.json | 7 +- .../use-query-params/src/__tests__/index.js | 76 ++++++++++-- packages/use-query-params/src/index.js | 72 ++++++++---- yarn.lock | 108 ++++++++++++++++-- 4 files changed, 218 insertions(+), 45 deletions(-) diff --git a/packages/use-query-params/package.json b/packages/use-query-params/package.json index d7328bf3b..957853a52 100644 --- a/packages/use-query-params/package.json +++ b/packages/use-query-params/package.json @@ -8,6 +8,7 @@ "reactjs", "hooks", "params", + "react-router-dom", "query-params" ], "main": "dist/index.js", @@ -26,11 +27,13 @@ }, "license": "MIT", "dependencies": { - "query-string": "^6.14.1" + "query-string": "^6.14.1", + "react-router-dom": "^5.2.0" }, "peerDependencies": { "react": "17.x", - "react-dom": "17.x" + "react-dom": "17.x", + "react-router-dom": "^5.2.0" }, "devDependencies": { "@testing-library/jest-dom": "^5.11.9", diff --git a/packages/use-query-params/src/__tests__/index.js b/packages/use-query-params/src/__tests__/index.js index 35e55895c..b91d82d32 100644 --- a/packages/use-query-params/src/__tests__/index.js +++ b/packages/use-query-params/src/__tests__/index.js @@ -1,16 +1,14 @@ import { act, renderHook } from '@testing-library/react-hooks' +import React from 'react' +import { MemoryRouter } from 'react-router-dom' import useQueryParam from '../index' // eslint-disable-next-line react/prop-types -const wrapper = ({ pathname = 'one', search }) => ({ children }) => { - window.history.replaceState( - window.history.state, - null, - `${pathname}?${search}`, - ) - - return children -} +const wrapper = ({ pathname = 'one', search }) => ({ children }) => ( + + {children} + +) describe('useQueryParam', () => { it('should set one object', () => { @@ -117,7 +115,7 @@ describe('useQueryParam', () => { }) }) - it('should correctly set different objects before rerender', async () => { + it('should correctly set different objects before rerender', () => { const { result, rerender } = renderHook(() => useQueryParam(), { wrapper: wrapper({ search: '' }), }) @@ -150,4 +148,62 @@ describe('useQueryParam', () => { test: 'Scaleway', }) }) + + test('should render good params with parallel changes', async () => { + jest.useFakeTimers() + const { result } = renderHook(() => useQueryParam(), { + wrapper: wrapper({ search: '' }), + }) + act(() => { + result.current.setQueryParams({ name: 'John' }) + result.current.setQueryParams({ lastName: 'Doe' }) + result.current.setQueryParams({ compagny: 'Scaleway' }) + }) + + jest.runAllTimers() + + expect(result.current.queryParams).toEqual({ + name: 'John', + lastName: 'Doe', + compagny: 'Scaleway', + }) + + act(() => { + result.current.setQueryParams({ name: 'John' }) + }) + + jest.runAllTimers() + + expect(result.current.queryParams).toEqual({ + name: 'John', + lastName: 'Doe', + compagny: 'Scaleway', + }) + }) + + test('should erase params', () => { + const { result } = renderHook(() => useQueryParam(), { + wrapper: wrapper({ search: '' }), + }) + + act(() => { + result.current.setQueryParams({ name: 'John' }) + }) + expect(result.current.queryParams).toEqual({ + name: 'John', + }) + act(() => { + result.current.setQueryParams({ lastName: 'Doe' }) + }) + expect(result.current.queryParams).toEqual({ + name: 'John', + lastName: 'Doe', + }) + act(() => { + result.current.replaceQueryparams({ compagny: 'Scaleway' }) + }) + expect(result.current.queryParams).toEqual({ + compagny: 'Scaleway', + }) + }) }) diff --git a/packages/use-query-params/src/index.js b/packages/use-query-params/src/index.js index 21a012ee5..4a429f196 100644 --- a/packages/use-query-params/src/index.js +++ b/packages/use-query-params/src/index.js @@ -1,39 +1,65 @@ import { parse, stringify } from 'query-string' import { useCallback, useEffect, useState } from 'react' +import { useHistory } from 'react-router-dom' -const parseFormat = search => - parse(search, { - parseNumbers: true, - parseBooleans: true, - arrayFormat: 'comma', - }) +const useQueryParams = () => { + const { + location: { search, pathname }, + replace, + } = useHistory() -const stringyFormat = params => - stringify(params, { - arrayFormat: 'comma', - sort: (a, b) => a.localeCompare(b), - }) + const parseFormat = useCallback( + () => + parse(search, { + parseNumbers: true, + parseBooleans: true, + arrayFormat: 'comma', + }), + [search], + ) -const useQueryParams = () => { - const { search, pathname } = window.location + const stringyFormat = useCallback( + params => + stringify(params, { + arrayFormat: 'comma', + sort: (a, b) => a.localeCompare(b), + }), + [], + ) - const [state, setState] = useState(parseFormat(search)) + const [state, setState] = useState(parseFormat()) - const setQueryParams = useCallback(nextParams => { + /** + * Set query params in the url. It merge the existing values with the new ones. + * @param {Object} nextParams The params to set in the url as query params + */ + const setQueryParams = nextParams => { setState(prevState => ({ ...prevState, ...nextParams })) - }, []) + } + + /** + * Replace the query params in the url. It erase all current values and put the new ones + * @param {Object} newParams + */ + const replaceQueryparams = newParams => { + setState({ ...newParams }) + } useEffect(() => { - const stringifiedParams = stringyFormat(state) - window.history.replaceState( - window.history.state, - null, - `${pathname}?${stringifiedParams}`, - ) - }, [pathname, state]) + const handler = setTimeout(() => { + const stringifiedParams = stringyFormat(state) + if (search !== `?${stringifiedParams}`) + replace(`${pathname}?${stringifiedParams}`) + }, 500) + + return () => { + clearTimeout(handler) + } + }, [pathname, replace, state, search, stringyFormat]) return { queryParams: state, + replaceQueryparams, setQueryParams, } } diff --git a/yarn.lock b/yarn.lock index 50ffa672f..37c9e476a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -902,6 +902,13 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" +"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" + integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.8.4": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" @@ -909,13 +916,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.9.2": - version "7.13.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" - integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.12.13", "@babel/template@^7.3.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" @@ -4862,6 +4862,25 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hoist-non-react-statics@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.8.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" @@ -5378,6 +5397,11 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -6232,7 +6256,7 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6423,6 +6447,14 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +mini-create-react-context@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" + integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ== + dependencies: + "@babel/runtime" "^7.12.1" + tiny-warning "^1.0.3" + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -7228,6 +7260,13 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -7401,7 +7440,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.7.2: +prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7489,7 +7528,7 @@ react-error-boundary@^3.1.0: dependencies: "@babel/runtime" "^7.12.5" -react-is@^16.8.1: +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -7499,6 +7538,35 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-router-dom@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" + integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.2.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" + integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.4.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + react@^17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127" @@ -7848,6 +7916,11 @@ resolve-global@1.0.0, resolve-global@^1.0.0: dependencies: global-dirs "^0.1.1" +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -8678,6 +8751,16 @@ through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0, tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -9012,6 +9095,11 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"