From d4bc2f6bde023715863ae8562fa993a39b7b0f75 Mon Sep 17 00:00:00 2001 From: nrjinua Date: Wed, 24 Nov 2021 18:04:02 +0200 Subject: [PATCH 1/2] add createMatchSelector function from connected-react-router --- package.json | 2 + react-router/__tests__/index.test.ts | 25 +++++++++++ react-router/index.ts | 18 ++++++++ yarn.lock | 65 +++++++++++++++++++++++++--- 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 react-router/__tests__/index.test.ts create mode 100644 react-router/index.ts diff --git a/package.json b/package.json index e5bb647..ff639d3 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@types/jest": "^27.0.2", "@types/reach__router": "^1.3.9", "@types/react": "^17.0.24", + "@types/react-router": "^5.1.17", "@typescript-eslint/eslint-plugin": "^4.31.2", "@typescript-eslint/parser": "^4.31.2", "eslint": "^7.32.0", @@ -67,6 +68,7 @@ "prettier": "^2.4.1", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-router": "^5.1.0", "redux": "^4.1.1", "rimraf": "^3.0.2", "ts-jest": "^27.0.5", diff --git a/react-router/__tests__/index.test.ts b/react-router/__tests__/index.test.ts new file mode 100644 index 0000000..9eac8ed --- /dev/null +++ b/react-router/__tests__/index.test.ts @@ -0,0 +1,25 @@ +import { createMatchSelector } from '../index'; + +describe('react-router function', () => { + const fakeState = { + router: { + location: { + pathname: '/just/svv123/test/nrj123' + } + } + }; + it('createMatchSelector return matching object', () => { + const expectedOutput = { + isExact: true, + params: { + idOne: 'svv123', + idTwo: 'nrj123', + }, + path: '/just/:idOne/test/:idTwo', + url: '/just/svv123/test/nrj123' + }; + const matchingFunction = createMatchSelector('/just/:idOne/test/:idTwo'); + const matchingObject = matchingFunction(fakeState); + expect(matchingObject).toEqual(expectedOutput); + }); +}); diff --git a/react-router/index.ts b/react-router/index.ts new file mode 100644 index 0000000..62a416d --- /dev/null +++ b/react-router/index.ts @@ -0,0 +1,18 @@ +import { match, matchPath } from 'react-router'; + +export const createMatchSelector = (path: string) => { + let lastPathname: string | null = null; + let lastMatch: match<{}> | null = null; + return (state: any) => { + const { pathname } = state.router.location ?? {}; + if (pathname === lastPathname) { + return lastMatch; + } + lastPathname = pathname; + const match = matchPath(pathname, path); + if (!match || !lastMatch || (match.url !== lastMatch.url) || (match.isExact !== lastMatch.isExact)) { + lastMatch = match; + } + return lastMatch; + } +}; diff --git a/yarn.lock b/yarn.lock index 93bbc6d..500fc9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -280,7 +280,7 @@ core-js-pure "^3.19.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13": version "7.16.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== @@ -694,7 +694,7 @@ dependencies: "@types/node" "*" -"@types/history@^4.7.9": +"@types/history@*", "@types/history@^4.7.9": version "4.7.9" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724" integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ== @@ -758,6 +758,14 @@ dependencies: "@types/react" "*" +"@types/react-router@^5.1.17": + version "5.1.17" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.17.tgz#087091006213b11042f39570e5cd414863693968" + integrity sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react@*", "@types/react@^17.0.24": version "17.0.34" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102" @@ -2054,7 +2062,7 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -history@^4.10.1: +history@^4.10.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== @@ -2066,6 +2074,13 @@ history@^4.10.1: 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" + html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -2289,6 +2304,11 @@ is-weakref@^1.0.1: dependencies: call-bind "^1.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= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -2917,7 +2937,7 @@ lodash@^4.7.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.4.0: +loose-envify@^1.0.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== @@ -2990,6 +3010,14 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +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" @@ -3227,6 +3255,13 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +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@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -3308,7 +3343,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.1, prop-types@^15.7.2: +prop-types@^15.6.1, 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== @@ -3341,7 +3376,7 @@ react-dom@^17.0.2: object-assign "^4.1.1" scheduler "^0.20.2" -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== @@ -3356,6 +3391,22 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-router@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d" + integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ== + dependencies: + "@babel/runtime" "^7.12.13" + 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.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -3729,7 +3780,7 @@ tiny-invariant@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== -tiny-warning@^1.0.0: +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== From b503c8ea7daa8877265f5916d69dde31a599a710 Mon Sep 17 00:00:00 2001 From: nrjinua Date: Sun, 28 Nov 2021 14:49:16 +0200 Subject: [PATCH 2/2] add optional argument with options to createMatchSelector function --- react-router/__tests__/index.test.ts | 45 ++++++++++++++++++++-------- react-router/index.ts | 17 +++++++++-- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/react-router/__tests__/index.test.ts b/react-router/__tests__/index.test.ts index 9eac8ed..d417738 100644 --- a/react-router/__tests__/index.test.ts +++ b/react-router/__tests__/index.test.ts @@ -1,25 +1,46 @@ import { createMatchSelector } from '../index'; describe('react-router function', () => { - const fakeState = { - router: { + const getFakeState = (key: string = 'router') => ({ + [key]: { location: { pathname: '/just/svv123/test/nrj123' } } + } as any); + const expectedOutput = { + isExact: true, + params: { + idOne: 'svv123', + idTwo: 'nrj123', + }, + path: '/just/:idOne/test/:idTwo', + url: '/just/svv123/test/nrj123' }; - it('createMatchSelector return matching object', () => { - const expectedOutput = { - isExact: true, - params: { - idOne: 'svv123', - idTwo: 'nrj123', - }, - path: '/just/:idOne/test/:idTwo', - url: '/just/svv123/test/nrj123' - }; + it('createMatchSelector return matching object when options not passed', () => { + const fakeState = getFakeState(); const matchingFunction = createMatchSelector('/just/:idOne/test/:idTwo'); const matchingObject = matchingFunction(fakeState); expect(matchingObject).toEqual(expectedOutput); }); + it('createMatchSelector return matching object when selectRouterState function passed', () => { + const selectRouterState = (state: any) => state.ownrouterkey; + const fakeState = getFakeState('ownrouterkey'); + const matchingFunction = createMatchSelector('/just/:idOne/test/:idTwo', { selectRouterState }); + const matchingObject = matchingFunction(fakeState); + expect(matchingObject).toEqual(expectedOutput); + }); + it('createMatchSelector return matching object when routerReducerKey passed', () => { + const routerReducerKey = 'ownrouterkey'; + const fakeState = getFakeState(routerReducerKey); + const matchingFunction = createMatchSelector('/just/:idOne/test/:idTwo', { routerReducerKey }); + const matchingObject = matchingFunction(fakeState); + expect(matchingObject).toEqual(expectedOutput); + }); + it('createMatchSelector return matching object when empty object passed', () => { + const fakeState = getFakeState(); + const matchingFunction = createMatchSelector('/just/:idOne/test/:idTwo', {}); + const matchingObject = matchingFunction(fakeState); + expect(matchingObject).toEqual(expectedOutput); + }); }); diff --git a/react-router/index.ts b/react-router/index.ts index 62a416d..900b193 100644 --- a/react-router/index.ts +++ b/react-router/index.ts @@ -1,10 +1,21 @@ import { match, matchPath } from 'react-router'; +import { RouterState } from '../src/reducer'; -export const createMatchSelector = (path: string) => { +interface Options { + selectRouterState?: (state: S) => RouterState; + routerReducerKey?: string; +} + +export const createMatchSelector = (path: string, options?: Options) => { let lastPathname: string | null = null; let lastMatch: match<{}> | null = null; - return (state: any) => { - const { pathname } = state.router.location ?? {}; + let { selectRouterState, routerReducerKey = 'router' } = options ?? {}; + if (typeof selectRouterState !== 'function') { + selectRouterState = (state: S): RouterState => state[routerReducerKey]; + } + return (state: S): match<{}> | null => { + const routerState = selectRouterState!(state); + const pathname = routerState?.location?.pathname ?? ''; if (pathname === lastPathname) { return lastMatch; }