From 3a8b4aa3b256fdb933b4d4d5dba3964e64a54459 Mon Sep 17 00:00:00 2001 From: klis87 Date: Wed, 24 Feb 2021 00:26:15 +0100 Subject: [PATCH 01/14] Added import/named to eslint rules --- .eslintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc b/.eslintrc index db431f3ab..835f1c944 100644 --- a/.eslintrc +++ b/.eslintrc @@ -77,6 +77,7 @@ ], "import/no-extraneous-dependencies": 2, "import/order": [2, { "newlines-between": "always" }], + "import/named": 2, "react/prop-types": 0, "react/no-array-index-key": 0, From cecb761436e38d954f59feed0c43c0a9ce43ebcd Mon Sep 17 00:00:00 2001 From: klis87 Date: Wed, 24 Feb 2021 00:30:04 +0100 Subject: [PATCH 02/14] Removed fsa support --- packages/redux-requests/src/actions.js | 42 +++------- packages/redux-requests/src/actions.spec.js | 59 -------------- .../create-send-requests-middleware.js | 49 +++--------- .../create-subscriptions-middleware.js | 17 ++-- .../src/reducers/queries-reducer.js | 12 +-- .../src/reducers/queries-reducer.spec.js | 80 ------------------- 6 files changed, 30 insertions(+), 229 deletions(-) diff --git a/packages/redux-requests/src/actions.js b/packages/redux-requests/src/actions.js index 8f8fabaa6..ab2bca3a4 100644 --- a/packages/redux-requests/src/actions.js +++ b/packages/redux-requests/src/actions.js @@ -27,11 +27,9 @@ export const error = getActionWithSuffix(ERROR_SUFFIX); export const abort = getActionWithSuffix(ABORT_SUFFIX); -const isFSA = action => !!action.payload; - export const createSuccessAction = (action, response) => ({ type: success(action.type), - ...(isFSA(action) ? { payload: response } : { response }), + response, meta: { ...action.meta, requestAction: action, @@ -40,14 +38,7 @@ export const createSuccessAction = (action, response) => ({ export const createErrorAction = (action, errorData) => ({ type: error(action.type), - ...(isFSA(action) - ? { - payload: errorData, - error: true, - } - : { - error: errorData, - }), + error: errorData, meta: { ...action.meta, requestAction: action, @@ -62,28 +53,19 @@ export const createAbortAction = action => ({ }, }); -export const getActionPayload = action => - action.payload === undefined ? action : action.payload; - -// eslint-disable-next-line import/no-unused-modules -export const getResponseFromSuccessAction = action => - action.payload ? action.payload : action.response; - export const isRequestAction = action => { - const actionPayload = getActionPayload(action); - return ( - !!actionPayload?.request && + !!action?.request && !!( - Array.isArray(actionPayload.request) || - actionPayload.request.url || - actionPayload.request.query || - actionPayload.request.promise || - actionPayload.request.response || - actionPayload.request.error + Array.isArray(action.request) || + action.request.url || + action.request.query || + action.request.promise || + action.request.response || + action.request.error ) && - !actionPayload.response && - !(actionPayload instanceof Error) + !action.response && + !(action instanceof Error) ); }; @@ -106,7 +88,7 @@ const isRequestQuery = request => (request.query && !request.query.trim().startsWith('mutation')); export const isRequestActionQuery = action => { - const { request } = getActionPayload(action); + const { request } = action; if (action.meta?.asMutation !== undefined) { return !action.meta.asMutation; diff --git a/packages/redux-requests/src/actions.spec.js b/packages/redux-requests/src/actions.spec.js index 93bc0ce37..cb49af8ef 100644 --- a/packages/redux-requests/src/actions.spec.js +++ b/packages/redux-requests/src/actions.spec.js @@ -5,7 +5,6 @@ import { createSuccessAction, createErrorAction, createAbortAction, - getActionPayload, isRequestAction, getRequestActionFromResponse, isSuccessAction, @@ -51,25 +50,6 @@ describe('actions', () => { }); }); - it('handles FSA actions', () => { - const requestAction = { - type: 'REQUEST', - payload: { - request: { url: '/' }, - }, - }; - - expect(createSuccessAction(requestAction, { data: 'data' })).toEqual({ - type: 'REQUEST_SUCCESS', - payload: { - data: 'data', - }, - meta: { - requestAction, - }, - }); - }); - it('should merge request meta', () => { const requestAction = { type: 'REQUEST', @@ -106,24 +86,6 @@ describe('actions', () => { }); }); - it('handles FSA actions', () => { - const requestAction = { - type: 'REQUEST', - payload: { - request: { url: '/' }, - }, - }; - - expect(createErrorAction(requestAction, 'errorData')).toEqual({ - type: 'REQUEST_ERROR', - payload: 'errorData', - error: true, - meta: { - requestAction, - }, - }); - }); - it('should merge request meta', () => { const requestAction = { type: 'REQUEST', @@ -178,18 +140,6 @@ describe('actions', () => { }); }); - describe('getActionPayload', () => { - it('just returns not FSA action', () => { - const action = { type: 'ACTION' }; - expect(getActionPayload(action)).toEqual(action); - }); - - it('returns payload of FSA action', () => { - const action = { type: 'ACTION', payload: 'payload' }; - expect(getActionPayload(action)).toBe('payload'); - }); - }); - describe('isRequestAction', () => { it('recognizes request action', () => { expect(isRequestAction({ type: 'ACTION', request: { url: '/' } })).toBe( @@ -197,15 +147,6 @@ describe('actions', () => { ); }); - it('recognizes request FSA action', () => { - expect( - isRequestAction({ - type: 'ACTION', - payload: { request: { url: '/' } }, - }), - ).toBe(true); - }); - it('recognizes request action with multiple requests', () => { expect( isRequestAction({ diff --git a/packages/redux-requests/src/middleware/create-send-requests-middleware.js b/packages/redux-requests/src/middleware/create-send-requests-middleware.js index ae996b388..c0609d686 100644 --- a/packages/redux-requests/src/middleware/create-send-requests-middleware.js +++ b/packages/redux-requests/src/middleware/create-send-requests-middleware.js @@ -1,5 +1,4 @@ import { - getActionPayload, createSuccessAction, createErrorAction, createAbortAction, @@ -38,11 +37,10 @@ const isActionRehydrated = action => // TODO: remove to more functional style, we need object maps and filters const abortPendingRequests = (action, pendingRequests) => { - const payload = getActionPayload(action); - const clearAll = !payload.requests; - const keys = !clearAll && getKeys(payload.requests); + const clearAll = !action.requests; + const keys = !clearAll && getKeys(action.requests); - if (!payload.requests) { + if (!action.requests) { Object.values(pendingRequests).forEach(requests => requests.forEach(r => r.cancel()), ); @@ -61,26 +59,14 @@ const isTakeLatest = (action, config) => : config.takeLatest; const maybeCallOnRequestInterceptor = (action, config, store) => { - const payload = getActionPayload(action); - if ( config.onRequest && (!action.meta || (action.meta.runOnRequest !== false && !action.meta.ssrDuplicate)) ) { - if (action.request) { - return { - ...action, - request: config.onRequest(payload.request, action, store), - }; - } - return { ...action, - payload: { - ...action.payload, - request: config.onRequest(payload.request, action, store), - }, + request: config.onRequest(action.request, action, store), }; } @@ -88,22 +74,10 @@ const maybeCallOnRequestInterceptor = (action, config, store) => { }; const maybeCallOnRequestMeta = (action, store) => { - const payload = getActionPayload(action); - if (action.meta?.onRequest && !action.meta.ssrDuplicate) { - if (action.request) { - return { - ...action, - request: action.meta.onRequest(payload.request, action, store), - }; - } - return { ...action, - payload: { - ...action.payload, - request: action.meta.onRequest(payload.request, action, store), - }, + request: action.meta.onRequest(action.request, action, store), }; } @@ -158,8 +132,7 @@ const defer = () => { }; const getResponsePromises = (action, config, pendingRequests, store) => { - const actionPayload = getActionPayload(action); - const isBatchedRequest = Array.isArray(actionPayload.request); + const isBatchedRequest = Array.isArray(action.request); if (action.meta?.cacheResponse) { return [Promise.resolve(action.meta.cacheResponse)]; @@ -179,8 +152,8 @@ const getResponsePromises = (action, config, pendingRequests, store) => { } const responsePromises = isBatchedRequest - ? actionPayload.request.map(r => driver(r, action, driverActions)) - : [driver(actionPayload.request, action, driverActions)]; + ? action.request.map(r => driver(r, action, driverActions)) + : [driver(action.request, action, driverActions)]; if (responsePromises[0].cancel) { pendingRequests[lastActionKey] = responsePromises; @@ -253,8 +226,7 @@ const getInitialBatchObject = responseKeys => }, {}); const maybeTransformBatchRequestResponse = (action, response) => { - const actionPayload = getActionPayload(action); - const isBatchedRequest = Array.isArray(actionPayload.request); + const isBatchedRequest = Array.isArray(action.request); const responseKeys = Object.keys(response[0]); return isBatchedRequest && !isActionRehydrated(action) @@ -319,7 +291,6 @@ const createSendRequestMiddleware = config => { const allPendingRequests = {}; // for joining return store => next => action => { - const payload = getActionPayload(action); const requestsStore = createRequestsStore(store); if (action.type === JOIN_REQUEST) { @@ -329,7 +300,7 @@ const createSendRequestMiddleware = config => { if ( action.type === ABORT_REQUESTS || - (action.type === RESET_REQUESTS && payload.abortPending) + (action.type === RESET_REQUESTS && action.abortPending) ) { abortPendingRequests(action, pendingRequests); return next(action); diff --git a/packages/redux-requests/src/middleware/create-subscriptions-middleware.js b/packages/redux-requests/src/middleware/create-subscriptions-middleware.js index 06a3e4a2d..b15b4c2c8 100644 --- a/packages/redux-requests/src/middleware/create-subscriptions-middleware.js +++ b/packages/redux-requests/src/middleware/create-subscriptions-middleware.js @@ -4,7 +4,6 @@ import { websocketClosed, openWebsocket, closeWebsocket, - getActionPayload, } from '../actions'; import { GET_WEBSOCKET, @@ -300,14 +299,12 @@ export default ({ return response; } else if (action.type === WEBSOCKET_OPENED) { Object.values(subscriptions).forEach(subscriptionAction => { - const actionPayload = getActionPayload(subscriptionAction); - - if (actionPayload.subscription) { + if (subscriptionAction.subscription) { ws.send( JSON.stringify( onSend - ? onSend(actionPayload.subscription, subscriptionAction) - : actionPayload.subscription, + ? onSend(subscriptionAction.subscription, subscriptionAction) + : subscriptionAction.subscription, ), ); } @@ -350,14 +347,10 @@ export default ({ }; } - const actionPayload = getActionPayload(action); - - if (actionPayload.subscription && ws && active) { + if (action.subscription && ws && active) { ws.send( JSON.stringify( - onSend - ? onSend(actionPayload.subscription, action) - : actionPayload.subscription, + onSend ? onSend(action.subscription, action) : action.subscription, ), ); } diff --git a/packages/redux-requests/src/reducers/queries-reducer.js b/packages/redux-requests/src/reducers/queries-reducer.js index c5a359a16..1d6db7f76 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.js +++ b/packages/redux-requests/src/reducers/queries-reducer.js @@ -23,9 +23,6 @@ const getInitialQuery = normalized => ({ usedKeys: normalized ? {} : null, }); -const getDataFromResponseAction = action => - action.payload ? action.payload.data : action.response.data; - const shouldBeNormalized = (action, config) => action.meta?.normalize !== undefined ? action.meta.normalize @@ -64,7 +61,7 @@ const queryReducer = (state, action, actionType, config, normalizedData) => { ? state : { ...state, - data: getDataFromResponseAction(action), + data: action.response.data, pending: state.pending - 1, error: null, }; @@ -76,7 +73,7 @@ const queryReducer = (state, action, actionType, config, normalizedData) => { ...state, data: null, pending: state.pending - 1, - error: action.payload ? action.payload : action.error, + error: action.error, }; case abort(actionType): { if (state.pending === 1 && state.data === null && state.error === null) { @@ -138,10 +135,7 @@ const updateNormalizedData = (normalizedData, action, config) => { shouldBeNormalized(action, config) && !config.isRequestActionQuery(getRequestActionFromResponse(action)) ) { - const [, newNormalizedData] = normalize( - getDataFromResponseAction(action), - config, - ); + const [, newNormalizedData] = normalize(action.response.data, config); return mergeData(normalizedData, newNormalizedData); } diff --git a/packages/redux-requests/src/reducers/queries-reducer.spec.js b/packages/redux-requests/src/reducers/queries-reducer.spec.js index 2e8c7fd3c..60f74b966 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.spec.js @@ -105,51 +105,6 @@ describe('reducers', () => { normalizedData: {}, }); }); - - it('supports FSA actions for getting data and error by default', () => { - const action = { - type: 'FETCH_BOOK', - payload: { - request: { - url: '/', - }, - }, - }; - - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createSuccessAction(action, { data: 'data' }), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - data: 'data', - }, - }, - normalizedData: {}, - }); - - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createErrorAction(action, 'error'), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - error: 'error', - }, - }, - normalizedData: {}, - }); - }); }); describe('with requestKey', () => { @@ -349,41 +304,6 @@ describe('reducers', () => { }); }); - it('handles updateData customized per mutation in FSA action', () => { - expect( - queriesReducer( - initialState, - createSuccessAction( - { - type: MUTATION_ACTION, - payload: { - request: { url: '/books', method: 'post' }, - }, - meta: { - mutations: { - FETCH_BOOK: (data, mutationData) => - data + mutationData.nested, - }, - }, - }, - { data: { nested: 'suffix' } }, - ), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'datasuffix', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - it('handles updateData customized per mutation defined in updateData object key', () => { expect( queriesReducer( From b8fdee1e1e66e9f5bfb86ce35341d3ce3bfd5d61 Mon Sep 17 00:00:00 2001 From: klis87 Date: Fri, 26 Feb 2021 22:57:16 +0100 Subject: [PATCH 03/14] Removed all FSA legacy code --- .../create-subscriptions-middleware.js | 5 +-- .../src/reducers/mutations-reducer.js | 2 +- packages/redux-requests/types/index.d.ts | 38 +++++-------------- 3 files changed, 12 insertions(+), 33 deletions(-) diff --git a/packages/redux-requests/src/middleware/create-subscriptions-middleware.js b/packages/redux-requests/src/middleware/create-subscriptions-middleware.js index b15b4c2c8..c10fd67ae 100644 --- a/packages/redux-requests/src/middleware/create-subscriptions-middleware.js +++ b/packages/redux-requests/src/middleware/create-subscriptions-middleware.js @@ -332,10 +332,7 @@ export default ({ action.subscriptions.includes(k) ? undefined : v, ); } - } else if ( - action.subscription !== undefined || - action.payload?.subscription !== undefined - ) { + } else if (action.subscription !== undefined) { if ( action.meta?.onMessage || action.meta?.mutations || diff --git a/packages/redux-requests/src/reducers/mutations-reducer.js b/packages/redux-requests/src/reducers/mutations-reducer.js index fcbd767b0..45ad5d551 100644 --- a/packages/redux-requests/src/reducers/mutations-reducer.js +++ b/packages/redux-requests/src/reducers/mutations-reducer.js @@ -29,7 +29,7 @@ export default (state, action) => { return { ...state, [mutationType]: { - error: action.payload ? action.payload : action.error, + error: action.error, pending: state[mutationType].pending - 1, ref: state[mutationType].ref, }, diff --git a/packages/redux-requests/types/index.d.ts b/packages/redux-requests/types/index.d.ts index 19c5e1db1..e095f14b1 100644 --- a/packages/redux-requests/types/index.d.ts +++ b/packages/redux-requests/types/index.d.ts @@ -74,21 +74,11 @@ interface RequestActionMeta { [extraProperty: string]: any; } -export type RequestAction = - | { - type?: string; - payload?: never; - request: any | any[]; - meta?: RequestActionMeta; - } - | { - type?: string; - payload: { - request: any | any[]; - }; - request?: never; - meta?: RequestActionMeta; - }; +export type RequestAction = { + type?: string; + request: any | any[]; + meta?: RequestActionMeta; +}; export type LocalMutationAction = { type?: string; @@ -119,19 +109,11 @@ interface SubscriptionActionMeta { [extraProperty: string]: any; } -export type SubscriptionAction = - | { - type?: string; - subscription: any; - meta?: SubscriptionActionMeta; - } - | { - type?: string; - payload: { - subscription: any; - }; - meta?: SubscriptionActionMeta; - }; +export type SubscriptionAction = { + type?: string; + subscription: any; + meta?: SubscriptionActionMeta; +}; type ResponseData< Request extends (...args: any[]) => RequestAction From d9b8c8536879dac7c3078101f83ada8df7085933 Mon Sep 17 00:00:00 2001 From: klis87 Date: Fri, 26 Feb 2021 23:07:44 +0100 Subject: [PATCH 04/14] Removed legacy Query and Mutation components --- packages/redux-requests-react/package.json | 7 +- .../src/__snapshots__/mutation.spec.jsx.snap | 35 -- .../src/__snapshots__/query.spec.jsx.snap | 69 --- packages/redux-requests-react/src/index.js | 2 - .../redux-requests-react/src/mutation.jsx | 35 -- .../src/mutation.spec.jsx | 172 ------- .../src/propTypesValidators.js | 11 - packages/redux-requests-react/src/query.jsx | 83 ---- .../redux-requests-react/src/query.spec.jsx | 429 ------------------ .../types/index.d.spec.tsx | 117 +---- .../redux-requests-react/types/index.d.ts | 78 +--- yarn.lock | 2 +- 12 files changed, 19 insertions(+), 1021 deletions(-) delete mode 100644 packages/redux-requests-react/src/__snapshots__/mutation.spec.jsx.snap delete mode 100644 packages/redux-requests-react/src/__snapshots__/query.spec.jsx.snap delete mode 100644 packages/redux-requests-react/src/mutation.jsx delete mode 100644 packages/redux-requests-react/src/mutation.spec.jsx delete mode 100644 packages/redux-requests-react/src/propTypesValidators.js delete mode 100644 packages/redux-requests-react/src/query.jsx delete mode 100644 packages/redux-requests-react/src/query.spec.jsx diff --git a/packages/redux-requests-react/package.json b/packages/redux-requests-react/package.json index 430b19dea..ab1fd2bff 100644 --- a/packages/redux-requests-react/package.json +++ b/packages/redux-requests-react/package.json @@ -22,8 +22,8 @@ "scripts": { "clean": "rimraf es lib dist", "lint": "eslint 'src/**'", - "test": "jest src", - "test:cover": "jest --coverage src", + "test": "jest --passWithNoTests src", + "test:cover": "jest --passWithNoTests --coverage src", "test-types": "tsc types/index.d.spec.tsx --noEmit --strict --lib es2015 --jsx react", "build:commonjs": "cross-env BABEL_ENV=cjs babel src --out-dir lib --ignore 'src/**/*.spec.js'", "build:es": "babel src --out-dir es --ignore 'src/**/*.spec.js'", @@ -40,8 +40,7 @@ "redux": ">=4.0.0" }, "dependencies": { - "prop-types": "^15.5.7", - "react-is": ">=16.13.1" + "prop-types": "^15.5.7" }, "devDependencies": { "@babel/cli": "7.12.8", diff --git a/packages/redux-requests-react/src/__snapshots__/mutation.spec.jsx.snap b/packages/redux-requests-react/src/__snapshots__/mutation.spec.jsx.snap deleted file mode 100644 index 8b831591c..000000000 --- a/packages/redux-requests-react/src/__snapshots__/mutation.spec.jsx.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Mutation doesnt crush if an mutation with a requestKey doesnt exist 1`] = `
`; - -exports[`Mutation maps type to default mutation when no type found 1`] = `
`; - -exports[`Mutation maps type to mutation 1`] = ` -
- loading - error -
-`; - -exports[`Mutation renders custom component as prop with extra prop 1`] = ` -
- loading - - error - - extra -
-`; - -exports[`Mutation renders loading and error of mutation with requestKey 1`] = ` -
- loading - error -
-`; - -exports[`Mutation supports custom selector 1`] = ` -
- error -
-`; diff --git a/packages/redux-requests-react/src/__snapshots__/query.spec.jsx.snap b/packages/redux-requests-react/src/__snapshots__/query.spec.jsx.snap deleted file mode 100644 index 5fc73f4f0..000000000 --- a/packages/redux-requests-react/src/__snapshots__/query.spec.jsx.snap +++ /dev/null @@ -1,69 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Query allows passing no data message 1`] = `"no data"`; - -exports[`Query allows rendering custom error component with extra props 1`] = ` - - error - - extra - -`; - -exports[`Query allows rendering custom loading component with extra props 1`] = ` - - loading... - extra - -`; - -exports[`Query maps type to default query data when request state not found 1`] = `null`; - -exports[`Query maps type to query 1`] = ` -
- data -
-`; - -exports[`Query renders custom prop component with extra props 1`] = ` - - data - - extra - -`; - -exports[`Query renders data even when pending is positive if showLoaderDuringRefetch is false 1`] = ` -
- data -
-`; - -exports[`Query renders null when custom isDataEmpty returns true 1`] = `null`; - -exports[`Query renders null when data is empty array by default 1`] = `null`; - -exports[`Query renders null when data is falsy by default 1`] = `null`; - -exports[`Query renders null when error is truthy by default 1`] = `null`; - -exports[`Query renders null when pending is positive by default 1`] = `null`; - -exports[`Query supports custom query selector 1`] = ` -
- data -
-`; - -exports[`Query uses array as empty data when multiple is true 1`] = ` -
- array length: - 0 -
-`; - -exports[`Query uses defaultData prop when data is null 1`] = ` -
- 1 -
-`; diff --git a/packages/redux-requests-react/src/index.js b/packages/redux-requests-react/src/index.js index d6b80746c..4ac5a099b 100644 --- a/packages/redux-requests-react/src/index.js +++ b/packages/redux-requests-react/src/index.js @@ -1,7 +1,5 @@ export { default as useQuery } from './use-query'; -export { default as Query } from './query'; export { default as useMutation } from './use-mutation'; -export { default as Mutation } from './mutation'; export { default as useSubscription } from './use-subscription'; export { default as useDispatchRequest } from './use-dispatch-request'; export { default as RequestsProvider } from './requests-provider'; diff --git a/packages/redux-requests-react/src/mutation.jsx b/packages/redux-requests-react/src/mutation.jsx deleted file mode 100644 index ba03def58..000000000 --- a/packages/redux-requests-react/src/mutation.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useSelector } from 'react-redux'; -import { getMutationSelector } from '@redux-requests/core'; - -import { reactComponentPropType } from './propTypesValidators'; - -const Mutation = ({ - type, - selector, - requestKey, - children, - component: Component, - ...extraProps -}) => { - const mutation = useSelector( - selector || getMutationSelector({ type, requestKey }), - ); - - if (children) { - return children(mutation); - } - - return ; -}; - -Mutation.propTypes = { - selector: PropTypes.func, - type: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - requestKey: PropTypes.string, - children: PropTypes.func, - component: reactComponentPropType('Mutation'), -}; - -export default Mutation; diff --git a/packages/redux-requests-react/src/mutation.spec.jsx b/packages/redux-requests-react/src/mutation.spec.jsx deleted file mode 100644 index 629b80466..000000000 --- a/packages/redux-requests-react/src/mutation.spec.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import renderer from 'react-test-renderer'; -import React from 'react'; -import { Provider } from 'react-redux'; -import configureStore from 'redux-mock-store'; - -import Mutation from './mutation'; - -const mockStore = configureStore(); - -describe('Mutation', () => { - const MUTATION_TYPE = 'MUTATION_TYPE'; - - it('supports custom selector', () => { - const component = renderer.create( - - state.request.mutations[MUTATION_TYPE]}> - {({ loading, error }) => ( -
- {loading && 'loading'} - {error} -
- )} -
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('maps type to mutation', () => { - const component = renderer.create( - - - {({ loading, error }) => ( -
- {loading && 'loading'} - {error} -
- )} -
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('maps type to default mutation when no type found', () => { - const component = renderer.create( - - - {({ loading, error }) => ( -
- {loading && 'loading'} - {error} -
- )} -
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('renders loading and error of mutation with requestKey', () => { - const component = renderer.create( - - - {({ loading, error }) => ( -
- {loading && 'loading'} - {error} -
- )} -
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('doesnt crush if an mutation with a requestKey doesnt exist', () => { - const component = renderer.create( - - - {({ loading, error }) => ( -
- {loading && 'loading'} - {error} -
- )} -
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('renders custom component as prop with extra prop', () => { - const MutationComponent = ({ mutation: { loading, error }, extra }) => ( -
- {loading && 'loading'} {error} {extra} -
- ); - - const component = renderer.create( - - - , - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); -}); diff --git a/packages/redux-requests-react/src/propTypesValidators.js b/packages/redux-requests-react/src/propTypesValidators.js deleted file mode 100644 index 292ab8966..000000000 --- a/packages/redux-requests-react/src/propTypesValidators.js +++ /dev/null @@ -1,11 +0,0 @@ -import { isValidElementType } from 'react-is'; - -export const reactComponentPropType = componentName => (props, propName) => { - if (props[propName] && !isValidElementType(props[propName])) { - return new Error( - `Invalid prop '${propName}' supplied to '${componentName}': the prop is not a valid React component`, - ); - } - - return null; -}; diff --git a/packages/redux-requests-react/src/query.jsx b/packages/redux-requests-react/src/query.jsx deleted file mode 100644 index 847642de8..000000000 --- a/packages/redux-requests-react/src/query.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useSelector } from 'react-redux'; -import { getQuerySelector } from '@redux-requests/core'; - -import { reactComponentPropType } from './propTypesValidators'; - -const Query = ({ - type, - requestKey, - selector, - defaultData, - multiple, - children, - component: Component, - isDataEmpty, - showLoaderDuringRefetch, - noDataMessage, - errorComponent: ErrorComponent, - errorComponentProps, - loadingComponent: LoadingComponent, - loadingComponentProps, - ...extraProps -}) => { - const query = useSelector( - selector || getQuerySelector({ type, requestKey, defaultData, multiple }), - ); - - const dataEmpty = isDataEmpty(query); - - if (query.loading && (showLoaderDuringRefetch || dataEmpty)) { - return LoadingComponent ? ( - - ) : null; - } - - if (query.error) { - return ErrorComponent ? ( - - ) : null; - } - - if (dataEmpty) { - return noDataMessage; - } - - if (children) { - return children(query); - } - - return ; -}; - -Query.defaultProps = { - isDataEmpty: query => - Array.isArray(query.data) ? query.data.length === 0 : !query.data, - showLoaderDuringRefetch: true, - noDataMessage: null, - multiple: false, -}; - -Query.propTypes = { - type: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - requestKey: PropTypes.string, - selector: PropTypes.func, - multiple: PropTypes.bool, - defaultData: PropTypes.any, - children: PropTypes.func, - component: reactComponentPropType('Query'), - isDataEmpty: PropTypes.func, - showLoaderDuringRefetch: PropTypes.bool, - noDataMessage: PropTypes.node, - errorComponent: reactComponentPropType('Query'), - errorComponentProps: PropTypes.objectOf(PropTypes.any), - loadingComponent: reactComponentPropType('Query'), - loadingComponentProps: PropTypes.objectOf(PropTypes.any), -}; - -export default Query; diff --git a/packages/redux-requests-react/src/query.spec.jsx b/packages/redux-requests-react/src/query.spec.jsx deleted file mode 100644 index b1c68ce9b..000000000 --- a/packages/redux-requests-react/src/query.spec.jsx +++ /dev/null @@ -1,429 +0,0 @@ -import renderer from 'react-test-renderer'; -import React from 'react'; -import { Provider } from 'react-redux'; -import configureStore from 'redux-mock-store'; - -import Query from './query'; - -const mockStore = configureStore(); - -describe('Query', () => { - const QUERY_TYPE = 'QUERY_TYPE'; - - it('supports custom query selector', () => { - const component = renderer.create( - - state.request}> - {({ data }) =>
{data}
} -
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('maps type to query', () => { - const component = renderer.create( - - {({ data }) =>
{data}
}
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('maps type to default query data when request state not found', () => { - const component = renderer.create( - - {({ data }) =>
{data}
}
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('renders null when data is falsy by default', () => { - const component = renderer.create( - - {({ data }) =>
{data}
}
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('renders null when data is empty array by default', () => { - const component = renderer.create( - - - {({ data }) => ( -
- {data.map(v => ( - {v} - ))} -
- )} -
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('renders null when custom isDataEmpty returns true', () => { - const component = renderer.create( - - !query.data || query.data === 'empty'} - > - {({ data }) =>
{data}
} -
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('uses defaultData prop when data is null', () => { - const component = renderer.create( - - - {({ data }) =>
{data}
} -
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('uses array as empty data when multiple is true', () => { - const component = renderer.create( - - !query.data}> - {({ data }) =>
array length: {data.length}
} -
-
, - ); - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('allows passing no data message', () => { - const component = renderer.create( - - - {({ data }) =>
{data}
} -
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('renders null when pending is positive by default', () => { - const component = renderer.create( - - {({ data }) =>
{data}
}
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('allows rendering custom loading component with extra props', () => { - const Spinner = ({ extra }) => loading... {extra}; - - const component = renderer.create( - - - {({ data }) =>
{data}
} -
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('throws when passing node as loadingComponent', () => { - const loggerSpy = jest - .spyOn(console, 'error') - .mockImplementation(() => null); - - try { - expect(() => - renderer.create( - - loading
}> - {({ data }) =>
{data}
} - - , - ), - ).toThrow(); - expect(loggerSpy).toBeCalled(); - } finally { - loggerSpy.mockRestore(); - } - }); - - it('renders data even when pending is positive if showLoaderDuringRefetch is false', () => { - const component = renderer.create( - - - {({ data }) =>
{data}
} -
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('renders null when error is truthy by default', () => { - const component = renderer.create( - - {({ data }) =>
{data}
}
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('allows rendering custom error component with extra props', () => { - const Error = ({ error, extra }) => ( - - {error} {extra} - - ); - - const component = renderer.create( - - - {({ data }) =>
{data}
} -
-
, - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); - - it('throws when passing node as errorComponent', () => { - const loggerSpy = jest - .spyOn(console, 'error') - .mockImplementation(() => null); - - try { - expect(() => - renderer.create( - - error
}> - {({ data }) =>
{data}
} - - , - ), - ).toThrow(); - expect(loggerSpy).toBeCalled(); - } finally { - loggerSpy.mockRestore(); - } - }); - - it('renders custom prop component with extra props', () => { - const CustomComponent = ({ query, extra }) => ( - - {query.data} {extra} - - ); - - const component = renderer.create( - - - , - ); - - expect(component.toJSON()).toMatchSnapshot(); - }); -}); diff --git a/packages/redux-requests-react/types/index.d.spec.tsx b/packages/redux-requests-react/types/index.d.spec.tsx index 0dee1da82..5f8adb541 100644 --- a/packages/redux-requests-react/types/index.d.spec.tsx +++ b/packages/redux-requests-react/types/index.d.spec.tsx @@ -1,13 +1,7 @@ import * as React from 'react'; import { MutationState, QueryState, RequestAction } from '@redux-requests/core'; -import { - Query, - Mutation, - useQuery, - useMutation, - useDispatchRequest, -} from './index'; +import { useQuery, useMutation, useDispatchRequest } from './index'; function fetchBooks( x: number, @@ -51,8 +45,6 @@ function updateBook(id: string): RequestAction<{ id: string; title: string }> { const query = useQuery({ type: 'Query' }); const query2 = useQuery({ type: 'Query', - multiple: true, - defaultData: {}, variables: [{ x: 1, y: 2 }], }); query2.data; @@ -80,113 +72,6 @@ const mutation3 = useMutation({ }); const r2 = mutation3.mutate(); -function BasicQuery() { - return ( - - selector={state => ({ - data: 'x', - error: null, - loading: true, - pending: 1, - pristine: false, - downloadProgress: null, - uploadProgress: null, - })} - type="TYPE" - > - {({ data }) => data} - - ); -} - -function BasicQuery2() { - return {({ data }) => data}; -} - -function Spinner({ extra }: { extra: any }) { - return loading... {extra}; -} - -function Error({ error, extra }: { error: Error, extra: any }) { - return ( - - {error} {extra} - - ); -} - -function Component({ query, extra }: { query: QueryState, extra: any }) { - return ( -
- {query.data} {extra} -
- ); -} - -function QueryWithComponents() { - return ( - - ); -} - -function BasicMutation() { - return ( - - {({ loading, error }) => ( -
- {loading && 'loading'} - {error} -
- )} -
- ); -} - -function MutationComponent({ mutation, extra }: { mutation: MutationState, extra: any }) { - return ( -
- {mutation.loading && 'loading'} - {mutation.error} - {extra} -
- ); -} - -function MutationWithCustomComponent() { - return ( - - ); -} - -function MutationWithSelector() { - return ( - ({ - error: null, - loading: false, - pending: 1, - downloadProgress: null, - uploadProgress: null, - })} - > - {({ loading, error }) => ( -
- {loading && 'loading'} - {error} -
- )} -
- ); -} - const QueryDispatcher = () => { const dispatch = useDispatchRequest(); diff --git a/packages/redux-requests-react/types/index.d.ts b/packages/redux-requests-react/types/index.d.ts index f6d28cd0a..8ef43e1e3 100644 --- a/packages/redux-requests-react/types/index.d.ts +++ b/packages/redux-requests-react/types/index.d.ts @@ -10,61 +10,6 @@ import { SubscriptionAction, } from '@redux-requests/core'; -interface LoadingProps { - downloadProgress?: number | null; - uploadProgress?: number | null; - [loadingProp: string]: any; -} - -interface ErrorProps { - error?: any; - [errorProp: string]: any; -} - -interface QueryCustomComponentProps { - query: QueryState; - [extraProperty: string]: any; -} - -interface QueryProps { - type?: string | ((...params: any[]) => RequestAction); - action?: (...params: any[]) => RequestAction; - requestKey?: string; - multiple?: boolean; - defaultData?: any; - selector?: (state: any) => QueryState; - children?: (query: QueryState) => React.ReactNode; - component?: CustomComponentProps extends QueryCustomComponentProps ? React.ComponentType : never; - isDataEmpty?: (query: QueryState) => boolean; - showLoaderDuringRefetch?: boolean; - noDataMessage?: React.ReactNode; - errorComponent?: React.ComponentType; - errorComponentProps?: { [errorProp: string]: any }; - loadingComponent?: React.ComponentType; - loadingComponentProps?: { [loadingProp: string]: any }; - [extraProperty: string]: any; -} - -export class Query extends React.Component< - QueryProps -> {} - -interface MutationCustomComponentProps { - mutation: MutationState; - [extraProperty: string]: any; -} - -interface MutationProps { - type?: string | ((...params: any[]) => RequestAction); - requestKey?: string; - selector?: (state: any) => MutationState; - children?: (mutation: MutationState) => React.ReactNode; - component?: CustomComponentProps extends MutationCustomComponentProps ? React.ComponentType : never; - [extraProperty: string]: any; -} - -export class Mutation extends React.Component> {} - interface RequestCreator { (...args: any[]): RequestAction; } @@ -86,8 +31,6 @@ export function useQuery< type: string | QueryCreator; action?: QueryCreator; requestKey?: string; - multiple?: boolean; - defaultData?: any; variables?: Parameters; autoLoad?: boolean; autoReset?: boolean; @@ -140,13 +83,20 @@ export function useSubscription(props: { export function useDispatchRequest(): DispatchRequest; -type RequestsProviderProps = - ({ requestsConfig: HandleRequestConfig; - extraReducers?: Reducer[]; - getMiddleware?: (extraMiddleware: Middleware[]) => Middleware[]; - store?: never; } - | { store: Store; requestsConfig?: never; extraReducers?: never; getMiddleware?: never; }) -& { +type RequestsProviderProps = ( + | { + requestsConfig: HandleRequestConfig; + extraReducers?: Reducer[]; + getMiddleware?: (extraMiddleware: Middleware[]) => Middleware[]; + store?: never; + } + | { + store: Store; + requestsConfig?: never; + extraReducers?: never; + getMiddleware?: never; + } +) & { children: React.ReactNode; autoLoad?: boolean; autoReset?: boolean; diff --git a/yarn.lock b/yarn.lock index c30dd5410..b8fe6342b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8722,7 +8722,7 @@ react-dom@17.0.1: object-assign "^4.1.1" scheduler "^0.20.1" -react-is@>=16.13.1, "react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1: +"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== From a043dea4b9be99cf44e9b4989d16d9684c9a4095 Mon Sep 17 00:00:00 2001 From: klis87 Date: Fri, 26 Feb 2021 23:18:42 +0100 Subject: [PATCH 05/14] Removed multiple and defaultData from query selectors --- .../redux-requests/src/selectors/get-query.js | 68 ++----------------- .../src/selectors/get-query.spec.js | 54 --------------- packages/redux-requests/types/index.d.ts | 4 -- 3 files changed, 5 insertions(+), 121 deletions(-) diff --git a/packages/redux-requests/src/selectors/get-query.js b/packages/redux-requests/src/selectors/get-query.js index 3d5972801..a69d4d0f9 100644 --- a/packages/redux-requests/src/selectors/get-query.js +++ b/packages/redux-requests/src/selectors/get-query.js @@ -14,14 +14,6 @@ const isQueryEqual = (currentVal, previousVal) => { return false; } - if ( - currentVal.data === null && - (currentVal.multiple !== previousVal.multiple || - currentVal.defaultData !== previousVal.defaultData) - ) { - return false; - } - if ( currentVal.normalized && currentVal.normalizedData !== previousVal.normalizedData @@ -59,28 +51,12 @@ const createCustomSelector = createSelectorCreator( isQueryEqual, ); -const getData = (data, multiple, defaultData) => { - if (data !== null) { - return data; - } - - if (defaultData !== undefined) { - return defaultData; - } - - if (multiple) { - return []; - } - - return data; -}; - const getQueryState = (state, type, requestKey = '') => state.requests.queries[type + requestKey]; const createQuerySelector = (type, requestKey) => createCustomSelector( - (state, defaultData, multiple) => { + state => { // in order not to keep queryState.ref reference in selector memoize const { data, @@ -98,8 +74,6 @@ const createQuerySelector = (type, requestKey) => pristine, normalized, usedKeys, - multiple, - defaultData, normalizedData: state.requests.normalizedData, downloadProgress: state.requests.downloadProgress[type + (requestKey || '')] ?? null, @@ -115,18 +89,10 @@ const createQuerySelector = (type, requestKey) => usedKeys, normalized, normalizedData, - defaultData, - multiple, downloadProgress, uploadProgress, }) => ({ - data: normalized - ? denormalize( - getData(data, multiple, defaultData), - normalizedData, - usedKeys, - ) - : getData(data, multiple, defaultData), + data: normalized ? denormalize(data, normalizedData, usedKeys) : data, pending, loading: pending > 0, error, @@ -146,42 +112,18 @@ const defaultQuery = { uploadProgress: null, }; -const defaultQueryMultiple = { - ...defaultQuery, - data: [], -}; - -const defaultQueriesWithCustomData = new Map(); - -const getDefaultQuery = (defaultData, multiple) => { - if ( - defaultData !== undefined && - defaultQueriesWithCustomData.get(defaultData) - ) { - return defaultQueriesWithCustomData.get(defaultData); - } - - if (defaultData !== undefined) { - const query = { ...defaultQuery, data: defaultData }; - defaultQueriesWithCustomData.set(defaultData, query); - return query; - } - - return multiple ? defaultQueryMultiple : defaultQuery; -}; - const querySelectors = new WeakMap(); -export default (state, { type, requestKey, defaultData, multiple = false }) => { +export default (state, { type, requestKey }) => { const queryState = getQueryState(state, type, requestKey); if (!queryState) { - return getDefaultQuery(defaultData, multiple); + return defaultQuery; } if (!querySelectors.get(queryState.ref)) { querySelectors.set(queryState.ref, createQuerySelector(type, requestKey)); } - return querySelectors.get(queryState.ref)(state, defaultData, multiple); + return querySelectors.get(queryState.ref)(state); }; diff --git a/packages/redux-requests/src/selectors/get-query.spec.js b/packages/redux-requests/src/selectors/get-query.spec.js index 0690578dc..b62cf5998 100644 --- a/packages/redux-requests/src/selectors/get-query.spec.js +++ b/packages/redux-requests/src/selectors/get-query.spec.js @@ -89,60 +89,6 @@ describe('selectors', () => { }); }); - it('replaces data as null with [] when multiple true', () => { - expect( - getQuery( - { - requests: { - queries: {}, - mutations: {}, - downloadProgress: {}, - uploadProgress: {}, - }, - }, - { type: 'QUERY', multiple: true }, - ), - ).toEqual({ - data: [], - pending: 0, - loading: false, - error: null, - pristine: true, - downloadProgress: null, - uploadProgress: null, - }); - }); - - it('replaces data as custom object with {} when defaultData defined', () => { - expect( - getQuery( - { - requests: { - queries: {}, - mutations: {}, - downloadProgress: {}, - uploadProgress: {}, - }, - }, - { type: 'QUERY', defaultData: {} }, - ), - ).toEqual({ - data: {}, - loading: false, - pending: 0, - error: null, - pristine: true, - downloadProgress: null, - uploadProgress: null, - }); - }); - - it('doesnt recompute when multiple is changed when data not empty', () => { - expect(getQuery(state, { type: 'QUERY', multiple: true })).toBe( - getQuery(state, { type: 'QUERY', multiple: false }), - ); - }); - it('returns transformed query state if found', () => { expect(getQuery(state, { type: 'QUERY' })).toEqual({ data: 'data', diff --git a/packages/redux-requests/types/index.d.ts b/packages/redux-requests/types/index.d.ts index e095f14b1..60a96bce9 100644 --- a/packages/redux-requests/types/index.d.ts +++ b/packages/redux-requests/types/index.d.ts @@ -291,8 +291,6 @@ export function getQuery( type: string | ((...params: any[]) => RequestAction); action?: (...params: any[]) => RequestAction; requestKey?: string; - multiple?: boolean; - defaultData?: any; }, ): QueryState; @@ -300,8 +298,6 @@ export function getQuerySelector(props: { type: string | ((...params: any[]) => RequestAction); action?: (...params: any[]) => RequestAction; requestKey?: string; - multiple?: boolean; - defaultData?: any; }): (state: any) => QueryState; export interface MutationState { From 237019bf214c3ffd441b41616c6374dfc8acec60 Mon Sep 17 00:00:00 2001 From: klis87 Date: Fri, 26 Feb 2021 23:23:37 +0100 Subject: [PATCH 06/14] Removed cache flag from handleRequests config --- examples/server-side-rendering/src/store/index.js | 1 - examples/showcase/src/store/index.js | 1 - packages/redux-requests/src/default-config.js | 1 - packages/redux-requests/src/handle-requests.js | 2 +- packages/redux-requests/types/index.d.ts | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/server-side-rendering/src/store/index.js b/examples/server-side-rendering/src/store/index.js index ee880b93e..a773b69f3 100644 --- a/examples/server-side-rendering/src/store/index.js +++ b/examples/server-side-rendering/src/store/index.js @@ -19,7 +19,6 @@ export const configureStore = (initialState = undefined) => { }), ), ssr: ssr ? 'server' : 'client', - cache: true, }); const reducers = combineReducers({ diff --git a/examples/showcase/src/store/index.js b/examples/showcase/src/store/index.js index cb74bd36f..3c5000568 100644 --- a/examples/showcase/src/store/index.js +++ b/examples/showcase/src/store/index.js @@ -8,7 +8,6 @@ import { fetchBooks, fetchPosts, fetchGroups } from './actions'; export const configureStore = () => { const { requestsReducer, requestsMiddleware } = handleRequests({ driver: createDriver(axios), - cache: true, }); const reducers = combineReducers({ diff --git a/packages/redux-requests/src/default-config.js b/packages/redux-requests/src/default-config.js index 3f9ed2c4d..8a817659c 100644 --- a/packages/redux-requests/src/default-config.js +++ b/packages/redux-requests/src/default-config.js @@ -6,7 +6,6 @@ export default { onSuccess: null, onError: null, onAbort: null, - cache: false, ssr: null, disableRequestsPromise: false, isRequestAction, diff --git a/packages/redux-requests/src/handle-requests.js b/packages/redux-requests/src/handle-requests.js index aca69a734..aac4a842a 100644 --- a/packages/redux-requests/src/handle-requests.js +++ b/packages/redux-requests/src/handle-requests.js @@ -37,7 +37,7 @@ const handleRequests = userConfig => { !config.disableRequestsPromise && createServerSsrMiddleware(requestsPromise, config), config.ssr === 'client' && createClientSsrMiddleware(config), - config.cache && createRequestsCacheMiddleware(config), + createRequestsCacheMiddleware(config), createSendRequestsMiddleware(config), ].filter(Boolean), requestsPromise, diff --git a/packages/redux-requests/types/index.d.ts b/packages/redux-requests/types/index.d.ts index 60a96bce9..5ad609238 100644 --- a/packages/redux-requests/types/index.d.ts +++ b/packages/redux-requests/types/index.d.ts @@ -177,7 +177,6 @@ export interface HandleRequestConfig { ) => any; onError?: (error: any, action: RequestAction, store: RequestsStore) => any; onAbort?: (action: RequestAction, store: RequestsStore) => void; - cache?: boolean; ssr?: null | 'client' | 'server'; disableRequestsPromise?: boolean; isRequestAction?: (action: AnyAction) => boolean; From ec29c5653331f5d34bb75698a0a387d5910f8d92 Mon Sep 17 00:00:00 2001 From: klis87 Date: Fri, 26 Feb 2021 23:33:37 +0100 Subject: [PATCH 07/14] Splitted queries reducer tests into multiple files --- .../reducers/queries-reducer.simple.spec.js | 110 ++ .../src/reducers/queries-reducer.spec.js | 1034 ----------------- ...h-mutations-with-query-request-key.spec.js | 150 +++ .../queries-reducer.with-mutations.spec.js | 283 +++++ ...queries-reducer.with-normalisation.spec.js | 425 +++++++ .../queries-reducer.with-request-key.js | 106 ++ 6 files changed, 1074 insertions(+), 1034 deletions(-) create mode 100644 packages/redux-requests/src/reducers/queries-reducer.simple.spec.js delete mode 100644 packages/redux-requests/src/reducers/queries-reducer.spec.js create mode 100644 packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js create mode 100644 packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js create mode 100644 packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js create mode 100644 packages/redux-requests/src/reducers/queries-reducer.with-request-key.js diff --git a/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js b/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js new file mode 100644 index 000000000..466339c00 --- /dev/null +++ b/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js @@ -0,0 +1,110 @@ +import defaultConfig from '../default-config'; +import { + createSuccessAction, + createErrorAction, + createAbortAction, +} from '../actions'; + +import queriesReducer from './queries-reducer'; + +describe('reducers', () => { + describe('queriesReducer', () => { + describe('simple', () => { + const defaultState = { + data: null, + error: null, + pending: 0, + pristine: true, + normalized: false, + ref: {}, + usedKeys: null, + }; + const requestAction = { + type: 'FETCH_BOOK', + request: { url: '/ ' }, + }; + + it('returns the same state for not handled action', () => { + const state = { queries: {}, normalizedData: {} }; + expect(queriesReducer(state, { type: 'STH ' }, defaultConfig)).toBe( + state, + ); + }); + + it('handles request query action', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + requestAction, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + ...defaultState, + pending: 1, + pristine: false, + }, + }, + normalizedData: {}, + }); + }); + + it('handles success query action', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createSuccessAction(requestAction, { data: 'data' }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + ...defaultState, + pending: -1, + data: 'data', + }, + }, + normalizedData: {}, + }); + }); + + it('handles error query action', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createErrorAction(requestAction, 'error'), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + ...defaultState, + pending: -1, + error: 'error', + }, + }, + normalizedData: {}, + }); + }); + + it('handles abort query action', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createAbortAction(requestAction), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + ...defaultState, + pending: -1, + }, + }, + normalizedData: {}, + }); + }); + }); + }); +}); diff --git a/packages/redux-requests/src/reducers/queries-reducer.spec.js b/packages/redux-requests/src/reducers/queries-reducer.spec.js deleted file mode 100644 index 60f74b966..000000000 --- a/packages/redux-requests/src/reducers/queries-reducer.spec.js +++ /dev/null @@ -1,1034 +0,0 @@ -import defaultConfig from '../default-config'; -import { - createSuccessAction, - createErrorAction, - createAbortAction, -} from '../actions'; - -import queriesReducer from './queries-reducer'; - -describe('reducers', () => { - describe('queriesReducer', () => { - describe('simple', () => { - const defaultState = { - data: null, - error: null, - pending: 0, - pristine: true, - normalized: false, - ref: {}, - usedKeys: null, - }; - const requestAction = { - type: 'FETCH_BOOK', - request: { url: '/ ' }, - }; - - it('returns the same state for not handled action', () => { - const state = { queries: {}, normalizedData: {} }; - expect(queriesReducer(state, { type: 'STH ' }, defaultConfig)).toBe( - state, - ); - }); - - it('handles request query action', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - requestAction, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: 1, - pristine: false, - }, - }, - normalizedData: {}, - }); - }); - - it('handles success query action', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createSuccessAction(requestAction, { data: 'data' }), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - data: 'data', - }, - }, - normalizedData: {}, - }); - }); - - it('handles error query action', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createErrorAction(requestAction, 'error'), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - error: 'error', - }, - }, - normalizedData: {}, - }); - }); - - it('handles abort query action', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createAbortAction(requestAction), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - }, - }, - normalizedData: {}, - }); - }); - }); - - describe('with requestKey', () => { - const defaultState = { - data: null, - error: null, - pending: 0, - pristine: true, - normalized: false, - ref: {}, - usedKeys: null, - }; - const requestAction = { - type: 'FETCH_BOOK', - request: { url: '/ ' }, - meta: { - requestKey: 1, - }, - }; - - it('handles request query action', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - requestAction, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK1: { - ...defaultState, - pending: 1, - pristine: false, - }, - }, - normalizedData: {}, - }); - }); - - it('handles success query action', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createSuccessAction(requestAction, { data: 'data' }), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK1: { - ...defaultState, - pending: -1, - data: 'data', - }, - }, - normalizedData: {}, - }); - }); - - it('handles error query action', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createErrorAction(requestAction, 'error'), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK1: { - ...defaultState, - pending: -1, - error: 'error', - }, - }, - normalizedData: {}, - }); - }); - - it('handles abort query action', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createAbortAction(requestAction), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK1: { - ...defaultState, - pending: -1, - }, - }, - normalizedData: {}, - }); - }); - }); - - describe('with mutations', () => { - const initialState = { - queries: { - FETCH_BOOK: { - data: 'data', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }; - - const MUTATION_ACTION = 'MUTATION_ACTION'; - - it('can update data optimistic', () => { - expect( - queriesReducer( - initialState, - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK: { - updateDataOptimistic: data => `${data} suffix`, - }, - }, - }, - }, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'data suffix', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - - it('keeps data updated optimistic on mutation success if updateData undefined', () => { - expect( - queriesReducer( - initialState, - createSuccessAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK: { - updateDataOptimistic: data => `${data} suffix`, - }, - }, - }, - }, - { data: 'updated data' }, - ), - defaultConfig, - ), - ).toEqual(initialState); - }); - - it('handles updateData customized per mutation', () => { - expect( - queriesReducer( - initialState, - createSuccessAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK: (data, mutationData) => - data + mutationData.nested, - }, - }, - }, - { data: { nested: 'suffix' } }, - ), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'datasuffix', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - - it('handles updateData customized per mutation defined in updateData object key', () => { - expect( - queriesReducer( - initialState, - createSuccessAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK: { - updateData: (data, mutationData) => - data + mutationData.nested, - }, - }, - }, - }, - { data: { nested: 'suffix' } }, - ), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'datasuffix', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - - it('reverts optimistic update on mutation error', () => { - expect( - queriesReducer( - initialState, - createErrorAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK: { - updateDataOptimistic: () => 'data2', - revertData: data => `${data} reverted`, - }, - }, - }, - }, - 'error', - ), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'data reverted', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - - it('doesnt change data on mutation error without optimistic update revertData', () => { - expect( - queriesReducer( - initialState, - createErrorAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK: { - updateDataOptimistic: () => 'data2', - }, - }, - }, - }, - 'error', - ), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'data', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - - it('reverts optimistic update on mutation abort', () => { - expect( - queriesReducer( - initialState, - createAbortAction({ - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK: { - updateDataOptimistic: () => 'data2', - revertData: data => `${data} reverted`, - }, - }, - }, - }), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'data reverted', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - - it('handles local mutations', () => { - expect( - queriesReducer( - initialState, - { - type: 'LOCAL_MUTATION_ACTION', - meta: { - mutations: { - FETCH_BOOK: { - local: true, - updateData: data => `${data} suffix`, - }, - }, - }, - }, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'data suffix', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - }); - - describe('with mutations with query request key', () => { - const initialState = { - queries: { - FETCH_BOOK: { - data: 'data', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - FETCH_BOOK1: { - data: 'data', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }; - - const MUTATION_ACTION = 'MUTATION_ACTION'; - - it('can update data optimistic', () => { - expect( - queriesReducer( - initialState, - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK1: { - updateDataOptimistic: data => `${data} suffix`, - }, - }, - }, - }, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'data', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - FETCH_BOOK1: { - data: 'data suffix', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - - it('handles updateData customized per mutation defined in updateData object key', () => { - expect( - queriesReducer( - initialState, - createSuccessAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK1: { - updateData: (data, mutationData) => - data + mutationData.nested, - }, - }, - }, - }, - { data: { nested: 'suffix' } }, - ), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'data', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - FETCH_BOOK1: { - data: 'datasuffix', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - - it('handles local mutations', () => { - expect( - queriesReducer( - initialState, - { - type: 'LOCAL_MUTATION_ACTION', - meta: { - mutations: { - FETCH_BOOK1: { - local: true, - updateData: data => `${data} suffix`, - }, - }, - }, - }, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: 'data', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - FETCH_BOOK1: { - data: 'data suffix', - error: null, - pending: 0, - pristine: false, - normalized: false, - }, - }, - normalizedData: {}, - }); - }); - }); - - describe('with normalization', () => { - const defaultState = { - data: null, - error: null, - pending: 0, - pristine: true, - normalized: true, - usedKeys: [], - ref: {}, - }; - const requestAction = { - type: 'FETCH_BOOK', - request: { url: '/ ' }, - meta: { - normalize: true, - }, - }; - - it('should normalize data on query success', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createSuccessAction(requestAction, { - data: { id: '1', name: 'name' }, - }), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - data: '@@1', - usedKeys: { '': ['id', 'name'] }, - }, - }, - normalizedData: { '@@1': { id: '1', name: 'name' } }, - }); - }); - - it('should not touch normalized data if query data is the same', () => { - const initialState = { - queries: { - FETCH_BOOK: { - data: 'data', - pending: 0, - error: null, - normalized: true, - usedKeys: { '': ['id', 'name'] }, - ref: {}, - }, - }, - normalizedData: {}, - }; - const state = queriesReducer( - initialState, - createSuccessAction(requestAction, { data: 'data' }), - defaultConfig, - ); - - expect(state.normalizedData).toBe(initialState.normalizedData); - }); - - it('should normalize data with nested ids and arrays on query success', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createSuccessAction(requestAction, { - data: { - root: { - id: '1', - name: 'name', - nested: [ - { id: '2', v: 2 }, - { id: '3', v: 3 }, - ], - }, - }, - }), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - data: { - root: '@@1', - }, - usedKeys: { - '.root': ['id', 'name', 'nested'], - '.root.nested': ['id', 'v'], - }, - }, - }, - normalizedData: { - '@@1': { - id: '1', - name: 'name', - nested: ['@@2', '@@3'], - }, - '@@2': { id: '2', v: 2 }, - '@@3': { id: '3', v: 3 }, - }, - }); - }); - - it('should merge normalized data on query success', () => { - expect( - queriesReducer( - { - queries: {}, - normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } }, - }, - createSuccessAction(requestAction, { - data: { id: '1', a: 'd', c: 'c' }, - }), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - data: '@@1', - usedKeys: { '': ['id', 'a', 'c'] }, - }, - }, - normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } }, - }); - }); - - it('should update normalized data on mutation success', () => { - expect( - queriesReducer( - { - queries: {}, - normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } }, - }, - createSuccessAction( - { - type: 'UPDATE_BOOK', - request: { url: '/', method: 'put' }, - meta: { - normalize: true, - }, - }, - { - data: { id: '1', a: 'd', c: 'c' }, - }, - ), - defaultConfig, - ), - ).toEqual({ - queries: {}, - normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } }, - }); - }); - - it('should update normalized query data on mutation success if defined in meta', () => { - const updateData = jest.fn((data, mutationData) => [ - ...data, - mutationData, - { id: '3', x: 3 }, - ]); - expect( - queriesReducer( - { - queries: { - FETCH_BOOK: { - data: ['@@1'], - pending: 0, - error: null, - normalized: true, - ref: {}, - usedKeys: { '': ['id', 'x'] }, - }, - }, - normalizedData: { '@@1': { id: '1', x: 1 } }, - }, - createSuccessAction( - { - type: 'ADD_BOOK', - request: { url: '/', method: 'put' }, - meta: { - normalize: true, - mutations: { - FETCH_BOOK: updateData, - }, - }, - }, - { - data: { id: '2', x: 2 }, - }, - ), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: ['@@1', '@@2', '@@3'], - pending: 0, - error: null, - normalized: true, - usedKeys: { '': ['id', 'x'] }, - ref: {}, - }, - }, - normalizedData: { - '@@1': { id: '1', x: 1 }, - '@@2': { id: '2', x: 2 }, - '@@3': { id: '3', x: 3 }, - }, - }); - expect(updateData).toBeCalledWith([{ id: '1', x: 1 }], { - id: '2', - x: 2, - }); - }); - - it('should update normalized query data with local mutation', () => { - expect( - queriesReducer( - { - queries: { - FETCH_BOOK: { - data: ['@@1'], - pending: 0, - error: null, - normalized: true, - usedKeys: { - '': ['id', 'x'], - }, - ref: {}, - }, - }, - normalizedData: { '@@1': { id: '1', x: 1 } }, - }, - { - type: 'ADD_BOOK_LOCALLY', - meta: { - // normalize: true, - mutations: { - FETCH_BOOK: { - updateData: data => [...data, { id: '2', x: 2 }], - local: true, - }, - }, - }, - }, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: ['@@1', '@@2'], - pending: 0, - error: null, - normalized: true, - usedKeys: { '': ['id', 'x'] }, - ref: {}, - }, - }, - normalizedData: { - '@@1': { id: '1', x: 1 }, - '@@2': { id: '2', x: 2 }, - }, - }); - }); - - it('should update normalized query data with localData', () => { - expect( - queriesReducer( - { - queries: { - FETCH_BOOK: { - data: ['@@1'], - pending: 0, - error: null, - normalized: true, - ref: {}, - }, - }, - normalizedData: { '@@1': { id: '1', x: 1 } }, - }, - { - type: 'UPDATE_BOOK_LOCALLY', - meta: { - localData: { id: '1', x: 2 }, - }, - }, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: ['@@1'], - pending: 0, - error: null, - normalized: true, - ref: {}, - }, - }, - normalizedData: { - '@@1': { id: '1', x: 2 }, - }, - }); - }); - - it('should update normalized query data with optimisticData', () => { - expect( - queriesReducer( - { - queries: { - FETCH_BOOK: { - data: ['@@1'], - pending: 0, - error: null, - normalized: true, - ref: {}, - }, - }, - normalizedData: { '@@1': { id: '1', x: 1 } }, - }, - { - type: 'UPDATE_BOOK', - request: { url: '/books', method: 'post' }, - meta: { - optimisticData: { id: '1', x: 2 }, - }, - }, - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: ['@@1'], - pending: 0, - error: null, - normalized: true, - ref: {}, - }, - }, - normalizedData: { - '@@1': { id: '1', x: 2 }, - }, - }); - }); - - it('should update normalized query data with revertedData on response error', () => { - expect( - queriesReducer( - { - queries: { - FETCH_BOOK: { - data: ['@@1'], - pending: 0, - error: null, - normalized: true, - ref: {}, - }, - }, - normalizedData: { '@@1': { id: '1', x: 2 } }, - }, - createErrorAction({ - type: 'UPDATE_BOOK', - request: { url: '/books', method: 'post' }, - meta: { - optimisticData: { id: '1', x: 2 }, - revertedData: { id: '1', x: 1 }, - }, - }), - defaultConfig, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - data: ['@@1'], - pending: 0, - error: null, - normalized: true, - ref: {}, - }, - }, - normalizedData: { - '@@1': { id: '1', x: 1 }, - }, - }); - }); - - it('should allow custom shouldObjectBeNormalized and getNormalisationObjectKey', () => { - expect( - queriesReducer( - { queries: {}, normalizedData: {} }, - createSuccessAction(requestAction, { - data: { _id: '1', name: 'name' }, - }), - { - ...defaultConfig, - getNormalisationObjectKey: obj => obj._id, - shouldObjectBeNormalized: obj => !!obj._id, - }, - ), - ).toEqual({ - queries: { - FETCH_BOOK: { - ...defaultState, - pending: -1, - data: '@@1', - usedKeys: { '': ['_id', 'name'] }, - }, - }, - normalizedData: { '@@1': { _id: '1', name: 'name' } }, - }); - }); - }); - }); -}); diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js new file mode 100644 index 000000000..7baaf29d6 --- /dev/null +++ b/packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js @@ -0,0 +1,150 @@ +import defaultConfig from '../default-config'; +import { createSuccessAction } from '../actions'; + +import queriesReducer from './queries-reducer'; + +describe('reducers', () => { + describe('queriesReducer', () => { + describe('with mutations with query request key', () => { + const initialState = { + queries: { + FETCH_BOOK: { + data: 'data', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + FETCH_BOOK1: { + data: 'data', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }; + + const MUTATION_ACTION = 'MUTATION_ACTION'; + + it('can update data optimistic', () => { + expect( + queriesReducer( + initialState, + { + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK1: { + updateDataOptimistic: data => `${data} suffix`, + }, + }, + }, + }, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'data', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + FETCH_BOOK1: { + data: 'data suffix', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + + it('handles updateData customized per mutation defined in updateData object key', () => { + expect( + queriesReducer( + initialState, + createSuccessAction( + { + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK1: { + updateData: (data, mutationData) => + data + mutationData.nested, + }, + }, + }, + }, + { data: { nested: 'suffix' } }, + ), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'data', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + FETCH_BOOK1: { + data: 'datasuffix', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + + it('handles local mutations', () => { + expect( + queriesReducer( + initialState, + { + type: 'LOCAL_MUTATION_ACTION', + meta: { + mutations: { + FETCH_BOOK1: { + local: true, + updateData: data => `${data} suffix`, + }, + }, + }, + }, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'data', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + FETCH_BOOK1: { + data: 'data suffix', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + }); + }); +}); diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js new file mode 100644 index 000000000..3cdf7dd5d --- /dev/null +++ b/packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js @@ -0,0 +1,283 @@ +import defaultConfig from '../default-config'; +import { + createSuccessAction, + createErrorAction, + createAbortAction, +} from '../actions'; + +import queriesReducer from './queries-reducer'; + +describe('reducers', () => { + describe('queriesReducer', () => { + describe('with mutations', () => { + const initialState = { + queries: { + FETCH_BOOK: { + data: 'data', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }; + + const MUTATION_ACTION = 'MUTATION_ACTION'; + + it('can update data optimistic', () => { + expect( + queriesReducer( + initialState, + { + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK: { + updateDataOptimistic: data => `${data} suffix`, + }, + }, + }, + }, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'data suffix', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + + it('keeps data updated optimistic on mutation success if updateData undefined', () => { + expect( + queriesReducer( + initialState, + createSuccessAction( + { + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK: { + updateDataOptimistic: data => `${data} suffix`, + }, + }, + }, + }, + { data: 'updated data' }, + ), + defaultConfig, + ), + ).toEqual(initialState); + }); + + it('handles updateData customized per mutation', () => { + expect( + queriesReducer( + initialState, + createSuccessAction( + { + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK: (data, mutationData) => + data + mutationData.nested, + }, + }, + }, + { data: { nested: 'suffix' } }, + ), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'datasuffix', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + + it('handles updateData customized per mutation defined in updateData object key', () => { + expect( + queriesReducer( + initialState, + createSuccessAction( + { + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK: { + updateData: (data, mutationData) => + data + mutationData.nested, + }, + }, + }, + }, + { data: { nested: 'suffix' } }, + ), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'datasuffix', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + + it('reverts optimistic update on mutation error', () => { + expect( + queriesReducer( + initialState, + createErrorAction( + { + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK: { + updateDataOptimistic: () => 'data2', + revertData: data => `${data} reverted`, + }, + }, + }, + }, + 'error', + ), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'data reverted', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + + it('doesnt change data on mutation error without optimistic update revertData', () => { + expect( + queriesReducer( + initialState, + createErrorAction( + { + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK: { + updateDataOptimistic: () => 'data2', + }, + }, + }, + }, + 'error', + ), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'data', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + + it('reverts optimistic update on mutation abort', () => { + expect( + queriesReducer( + initialState, + createAbortAction({ + type: MUTATION_ACTION, + request: { url: '/books', method: 'post' }, + meta: { + mutations: { + FETCH_BOOK: { + updateDataOptimistic: () => 'data2', + revertData: data => `${data} reverted`, + }, + }, + }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'data reverted', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + + it('handles local mutations', () => { + expect( + queriesReducer( + initialState, + { + type: 'LOCAL_MUTATION_ACTION', + meta: { + mutations: { + FETCH_BOOK: { + local: true, + updateData: data => `${data} suffix`, + }, + }, + }, + }, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: 'data suffix', + error: null, + pending: 0, + pristine: false, + normalized: false, + }, + }, + normalizedData: {}, + }); + }); + }); + }); +}); diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js new file mode 100644 index 000000000..5e75285e6 --- /dev/null +++ b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js @@ -0,0 +1,425 @@ +import defaultConfig from '../default-config'; +import { createSuccessAction, createErrorAction } from '../actions'; + +import queriesReducer from './queries-reducer'; + +describe('reducers', () => { + describe('queriesReducer', () => { + describe('with normalization', () => { + const defaultState = { + data: null, + error: null, + pending: 0, + pristine: true, + normalized: true, + usedKeys: [], + ref: {}, + }; + const requestAction = { + type: 'FETCH_BOOK', + request: { url: '/ ' }, + meta: { + normalize: true, + }, + }; + + it('should normalize data on query success', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createSuccessAction(requestAction, { + data: { id: '1', name: 'name' }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + ...defaultState, + pending: -1, + data: '@@1', + usedKeys: { '': ['id', 'name'] }, + }, + }, + normalizedData: { '@@1': { id: '1', name: 'name' } }, + }); + }); + + it('should not touch normalized data if query data is the same', () => { + const initialState = { + queries: { + FETCH_BOOK: { + data: 'data', + pending: 0, + error: null, + normalized: true, + usedKeys: { '': ['id', 'name'] }, + ref: {}, + }, + }, + normalizedData: {}, + }; + const state = queriesReducer( + initialState, + createSuccessAction(requestAction, { data: 'data' }), + defaultConfig, + ); + + expect(state.normalizedData).toBe(initialState.normalizedData); + }); + + it('should normalize data with nested ids and arrays on query success', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createSuccessAction(requestAction, { + data: { + root: { + id: '1', + name: 'name', + nested: [ + { id: '2', v: 2 }, + { id: '3', v: 3 }, + ], + }, + }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + ...defaultState, + pending: -1, + data: { + root: '@@1', + }, + usedKeys: { + '.root': ['id', 'name', 'nested'], + '.root.nested': ['id', 'v'], + }, + }, + }, + normalizedData: { + '@@1': { + id: '1', + name: 'name', + nested: ['@@2', '@@3'], + }, + '@@2': { id: '2', v: 2 }, + '@@3': { id: '3', v: 3 }, + }, + }); + }); + + it('should merge normalized data on query success', () => { + expect( + queriesReducer( + { + queries: {}, + normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } }, + }, + createSuccessAction(requestAction, { + data: { id: '1', a: 'd', c: 'c' }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + ...defaultState, + pending: -1, + data: '@@1', + usedKeys: { '': ['id', 'a', 'c'] }, + }, + }, + normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } }, + }); + }); + + it('should update normalized data on mutation success', () => { + expect( + queriesReducer( + { + queries: {}, + normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } }, + }, + createSuccessAction( + { + type: 'UPDATE_BOOK', + request: { url: '/', method: 'put' }, + meta: { + normalize: true, + }, + }, + { + data: { id: '1', a: 'd', c: 'c' }, + }, + ), + defaultConfig, + ), + ).toEqual({ + queries: {}, + normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } }, + }); + }); + + it('should update normalized query data on mutation success if defined in meta', () => { + const updateData = jest.fn((data, mutationData) => [ + ...data, + mutationData, + { id: '3', x: 3 }, + ]); + expect( + queriesReducer( + { + queries: { + FETCH_BOOK: { + data: ['@@1'], + pending: 0, + error: null, + normalized: true, + ref: {}, + usedKeys: { '': ['id', 'x'] }, + }, + }, + normalizedData: { '@@1': { id: '1', x: 1 } }, + }, + createSuccessAction( + { + type: 'ADD_BOOK', + request: { url: '/', method: 'put' }, + meta: { + normalize: true, + mutations: { + FETCH_BOOK: updateData, + }, + }, + }, + { + data: { id: '2', x: 2 }, + }, + ), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: ['@@1', '@@2', '@@3'], + pending: 0, + error: null, + normalized: true, + usedKeys: { '': ['id', 'x'] }, + ref: {}, + }, + }, + normalizedData: { + '@@1': { id: '1', x: 1 }, + '@@2': { id: '2', x: 2 }, + '@@3': { id: '3', x: 3 }, + }, + }); + expect(updateData).toBeCalledWith([{ id: '1', x: 1 }], { + id: '2', + x: 2, + }); + }); + + it('should update normalized query data with local mutation', () => { + expect( + queriesReducer( + { + queries: { + FETCH_BOOK: { + data: ['@@1'], + pending: 0, + error: null, + normalized: true, + usedKeys: { + '': ['id', 'x'], + }, + ref: {}, + }, + }, + normalizedData: { '@@1': { id: '1', x: 1 } }, + }, + { + type: 'ADD_BOOK_LOCALLY', + meta: { + // normalize: true, + mutations: { + FETCH_BOOK: { + updateData: data => [...data, { id: '2', x: 2 }], + local: true, + }, + }, + }, + }, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: ['@@1', '@@2'], + pending: 0, + error: null, + normalized: true, + usedKeys: { '': ['id', 'x'] }, + ref: {}, + }, + }, + normalizedData: { + '@@1': { id: '1', x: 1 }, + '@@2': { id: '2', x: 2 }, + }, + }); + }); + + it('should update normalized query data with localData', () => { + expect( + queriesReducer( + { + queries: { + FETCH_BOOK: { + data: ['@@1'], + pending: 0, + error: null, + normalized: true, + ref: {}, + }, + }, + normalizedData: { '@@1': { id: '1', x: 1 } }, + }, + { + type: 'UPDATE_BOOK_LOCALLY', + meta: { + localData: { id: '1', x: 2 }, + }, + }, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: ['@@1'], + pending: 0, + error: null, + normalized: true, + ref: {}, + }, + }, + normalizedData: { + '@@1': { id: '1', x: 2 }, + }, + }); + }); + + it('should update normalized query data with optimisticData', () => { + expect( + queriesReducer( + { + queries: { + FETCH_BOOK: { + data: ['@@1'], + pending: 0, + error: null, + normalized: true, + ref: {}, + }, + }, + normalizedData: { '@@1': { id: '1', x: 1 } }, + }, + { + type: 'UPDATE_BOOK', + request: { url: '/books', method: 'post' }, + meta: { + optimisticData: { id: '1', x: 2 }, + }, + }, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: ['@@1'], + pending: 0, + error: null, + normalized: true, + ref: {}, + }, + }, + normalizedData: { + '@@1': { id: '1', x: 2 }, + }, + }); + }); + + it('should update normalized query data with revertedData on response error', () => { + expect( + queriesReducer( + { + queries: { + FETCH_BOOK: { + data: ['@@1'], + pending: 0, + error: null, + normalized: true, + ref: {}, + }, + }, + normalizedData: { '@@1': { id: '1', x: 2 } }, + }, + createErrorAction({ + type: 'UPDATE_BOOK', + request: { url: '/books', method: 'post' }, + meta: { + optimisticData: { id: '1', x: 2 }, + revertedData: { id: '1', x: 1 }, + }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + data: ['@@1'], + pending: 0, + error: null, + normalized: true, + ref: {}, + }, + }, + normalizedData: { + '@@1': { id: '1', x: 1 }, + }, + }); + }); + + it('should allow custom shouldObjectBeNormalized and getNormalisationObjectKey', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createSuccessAction(requestAction, { + data: { _id: '1', name: 'name' }, + }), + { + ...defaultConfig, + getNormalisationObjectKey: obj => obj._id, + shouldObjectBeNormalized: obj => !!obj._id, + }, + ), + ).toEqual({ + queries: { + FETCH_BOOK: { + ...defaultState, + pending: -1, + data: '@@1', + usedKeys: { '': ['_id', 'name'] }, + }, + }, + normalizedData: { '@@1': { _id: '1', name: 'name' } }, + }); + }); + }); + }); +}); diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-request-key.js b/packages/redux-requests/src/reducers/queries-reducer.with-request-key.js new file mode 100644 index 000000000..d512cbf27 --- /dev/null +++ b/packages/redux-requests/src/reducers/queries-reducer.with-request-key.js @@ -0,0 +1,106 @@ +import defaultConfig from '../default-config'; +import { + createSuccessAction, + createErrorAction, + createAbortAction, +} from '../actions'; + +import queriesReducer from './queries-reducer'; + +describe('reducers', () => { + describe('queriesReducer', () => { + describe('with requestKey', () => { + const defaultState = { + data: null, + error: null, + pending: 0, + pristine: true, + normalized: false, + ref: {}, + usedKeys: null, + }; + const requestAction = { + type: 'FETCH_BOOK', + request: { url: '/ ' }, + meta: { + requestKey: 1, + }, + }; + + it('handles request query action', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + requestAction, + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK1: { + ...defaultState, + pending: 1, + pristine: false, + }, + }, + normalizedData: {}, + }); + }); + + it('handles success query action', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createSuccessAction(requestAction, { data: 'data' }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK1: { + ...defaultState, + pending: -1, + data: 'data', + }, + }, + normalizedData: {}, + }); + }); + + it('handles error query action', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createErrorAction(requestAction, 'error'), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK1: { + ...defaultState, + pending: -1, + error: 'error', + }, + }, + normalizedData: {}, + }); + }); + + it('handles abort query action', () => { + expect( + queriesReducer( + { queries: {}, normalizedData: {} }, + createAbortAction(requestAction), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOK1: { + ...defaultState, + pending: -1, + }, + }, + normalizedData: {}, + }); + }); + }); + }); +}); From 0c10665de78c10c933548e6b6e9c942b8091caf1 Mon Sep 17 00:00:00 2001 From: klis87 Date: Sat, 27 Feb 2021 00:16:37 +0100 Subject: [PATCH 08/14] Added dependencies key to query in reducer --- .../src/reducers/queries-reducer.js | 1 + .../reducers/queries-reducer.simple.spec.js | 1 + ...queries-reducer.with-normalisation.spec.js | 1 + .../src/reducers/requests-reducer.spec.js | 23 +++++++++++++++++++ 4 files changed, 26 insertions(+) diff --git a/packages/redux-requests/src/reducers/queries-reducer.js b/packages/redux-requests/src/reducers/queries-reducer.js index 1d6db7f76..4d5ed7f5b 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.js +++ b/packages/redux-requests/src/reducers/queries-reducer.js @@ -21,6 +21,7 @@ const getInitialQuery = normalized => ({ ref: {}, normalized, usedKeys: normalized ? {} : null, + dependencies: normalized ? [] : null, }); const shouldBeNormalized = (action, config) => diff --git a/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js b/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js index 466339c00..827478b88 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js @@ -18,6 +18,7 @@ describe('reducers', () => { normalized: false, ref: {}, usedKeys: null, + dependencies: null, }; const requestAction = { type: 'FETCH_BOOK', diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js index 5e75285e6..c14e53132 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js @@ -13,6 +13,7 @@ describe('reducers', () => { pristine: true, normalized: true, usedKeys: [], + dependencies: [], ref: {}, }; const requestAction = { diff --git a/packages/redux-requests/src/reducers/requests-reducer.spec.js b/packages/redux-requests/src/reducers/requests-reducer.spec.js index 9c78e17eb..032677ec2 100644 --- a/packages/redux-requests/src/reducers/requests-reducer.spec.js +++ b/packages/redux-requests/src/reducers/requests-reducer.spec.js @@ -72,6 +72,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }); @@ -85,6 +86,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, REQUEST_2: { @@ -94,6 +96,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }); @@ -110,6 +113,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, REQUEST_2: { @@ -119,6 +123,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }); @@ -132,6 +137,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, REQUEST_2: { @@ -141,6 +147,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }); @@ -198,6 +205,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -229,6 +237,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -255,6 +264,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -282,6 +292,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -309,6 +320,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -339,6 +351,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -376,6 +389,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -411,6 +425,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -455,6 +470,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -499,6 +515,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -543,6 +560,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -596,6 +614,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -643,6 +662,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -692,6 +712,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -737,6 +758,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, @@ -770,6 +792,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, ref: {}, }, }, From 6466d9dbe0bdcb2726e083c003d6c41a0fd56182 Mon Sep 17 00:00:00 2001 From: klis87 Date: Sat, 27 Feb 2021 23:38:59 +0100 Subject: [PATCH 09/14] Calculate dependencies for normalized queries in queries reducer --- .../src/reducers/queries-reducer.js | 23 ++- ...queries-reducer.with-normalisation.spec.js | 162 ++++++++++++++++++ 2 files changed, 182 insertions(+), 3 deletions(-) diff --git a/packages/redux-requests/src/reducers/queries-reducer.js b/packages/redux-requests/src/reducers/queries-reducer.js index 4d5ed7f5b..59767f947 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.js +++ b/packages/redux-requests/src/reducers/queries-reducer.js @@ -9,7 +9,7 @@ import { isSuccessAction, } from '../actions'; import { getQuery } from '../selectors'; -import { normalize, mergeData } from '../normalizers'; +import { normalize, mergeData, getDependentKeys } from '../normalizers'; import updateData from './update-data'; @@ -178,8 +178,18 @@ export default (state, action, config = defaultConfig) => { updatedQuery.data, config, ); + const dependencies = getDependentKeys( + newdata, + newNormalizedData, + usedKeys, + ); normalizedData = mergeData(normalizedData, newNormalizedData); - prev[actionType] = { ...updatedQuery, data: newdata, usedKeys }; + prev[actionType] = { + ...updatedQuery, + data: newdata, + dependencies: [...dependencies], + usedKeys, + }; } else { prev[actionType] = updatedQuery; } @@ -225,10 +235,17 @@ export default (state, action, config = defaultConfig) => { config, ); + const dependencies = getDependentKeys(data, newNormalizedData, usedKeys); + return { queries: { ...state.queries, - [queryType]: { ...updatedQuery, data, usedKeys }, + [queryType]: { + ...updatedQuery, + data, + usedKeys, + dependencies: [...dependencies], + }, }, normalizedData: mergeData(normalizedData, newNormalizedData), }; diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js index c14e53132..d408bf79c 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js @@ -3,6 +3,153 @@ import { createSuccessAction, createErrorAction } from '../actions'; import queriesReducer from './queries-reducer'; +/* +1) fetchBooks +[ + { id: 1, name: 'Harry', author: { id: 100, surname: 'Harry author' }, likers: [], }, + { id: 2, name: 'Lord', author: { id: 101, surname: 'Lord author' }, likers: [], }, +] + +fetchBooks: ['@@1', '@@2'], +@@1: { id: 1, name: 'Harry', author: '@@100', likers: [] } +@@2: { id: 2, name: 'Lord', author: '@@101', likers: [] } +@@100: { id: 100, surname: 'Harry author' } +@@101: { id: 101, surname: 'Lord author' } + +dependencies: +fetchBooks: [@@1, @@2, @@100, @@101] + +dependents: +@@1: [fetchBooks] +@@2: [fetchBooks] +@@100: [fetchBooks] +@@101: [fetchBooks] + +2) fetchBook 1 + +{ id: 1, name: 'Harry', author: { id: 100, surname: 'Harry author' } } + +fetchBooks: ['@@1', '@@2'] +fetchBook: '@@1' +@@1: { id: 1, name: 'Harry', author: '@@100', likers: [] } +@@2: { id: 2, name: 'Lord', author: '@@101', likers: [] } +@@100: { id: 100, surname: 'Harry author' } +@@101: { id: 101, surname: 'Lord author' } + +dependencies: +fetchBooks: [@@1, @@2, @@100, @@101] +fetchBooks: [@@1, @@100] + +dependents: +@@1: [fetchBooks, fetchBook] +@@2: [fetchBooks] +@@100: [fetchBooks, fetchBook] +@@101: [fetchBooks] + +3) updateBook 1 + +{ id: 1, name: 'Harry 2', author: { id: 100, surname: 'Harry 2 author' }, liker: { id: 1000 } } + +mutation dependencies: [@@1, @@100, @1000] +getting affected queries: [fetchBooks, fetchBook] +recalculate them, nothing changed re dependencies + +!!!!! what to do about this 1000 we must ignore it, not a dependency! + +fetchBooks: ['@@1', '@@2'] +fetchBook: '@@1' +@@1: { id: 1, name: 'Harry 2', author: '@@100' } +@@2: { id: 2, name: 'Lord', author: '@@101' } +@@100: { id: 100, surname: 'Harry 2 author' } +@@101: { id: 101, surname: 'Lord author' } + +dependencies: +fetchBooks: [@@1, @@2, @@100, @@101] +fetchBooks: [@@1, @@100] + +dependents: +@@1: [fetchBooks, fetchBook] +@@2: [fetchBooks] +@@100: [fetchBooks, fetchBook] +@@101: [fetchBooks] + +4) updateBook 1 - change author! + +{ id: 1, author: { id: 102, surname: 'Harry 2 new author' }, liker: { id: 1000 } } + +mutation dependencies: [@@1, @@102, @1000] +@@1 found, getting affected queries: [fetchBooks, fetchBook] +recalculate them, new dependencies: +dependencies: +fetchBooks: [@@1, @@2, @@100, @@101] => [@@1, @@2, @@102, @@101] 100 gone, 102 added +fetchBook: [@@1, @@100] => [@@1, @@102] 100 gone, 102 added + +!!!!! what to do about this 1000 we must ignore it, not a dependency! + +fetchBooks: ['@@1', '@@2'] +fetchBook: '@@1' +@@1: { id: 1, name: 'Harry 2', author: '@@102' } +@@2: { id: 2, name: 'Lord', author: '@@101' } +@@102: { id: 102, surname: 'Harry 2 new author' } +@@101: { id: 101, surname: 'Lord author' } + + +dependents: +@@1: [fetchBooks, fetchBook] +@@2: [fetchBooks] +@@102: [fetchBooks, fetchBook] +@@101: [fetchBooks] +@@100: [] to remove + +5) updateBook 1 - add liker! + +{ id: 1, likers: [{ id: 1000 }] } + +mutation dependencies: [@@1, @@1000] +@@1 found, getting affected queries: [fetchBooks, fetchBook] + +recalculate them, new dependencies: +dependencies: +fetchBooks: [@@1, @@2, @@102, @@101] => [@@1, @@2, @@102, @@101, @@1000] 1000 added! +fetchBook: [@@1, @@102] => [@@1, @@102, @@1000] 1000 added + + +fetchBooks: ['@@1', '@@2'] +fetchBook: '@@1' +@@1: { id: 1, name: 'Harry 2', author: '@@102' } +@@2: { id: 2, name: 'Lord', author: '@@101' } +@@102: { id: 102, surname: 'Harry 2 new author' } +@@101: { id: 101, surname: 'Lord author' } +@@1000: { id: 1000 } + +dependents: +@@1: [fetchBooks, fetchBook] +@@2: [fetchBooks] +@@102: [fetchBooks, fetchBook] +@@101: [fetchBooks] +@@1000: [fetchBooks, fetchBook] + +6) reset fetchBooks + +fetchBooks: null +fetchBook: '@@1' +@@1: { id: 1, name: 'Harry', author: '@@100' } +@@2: { id: 2, name: 'Lord', author: '@@101' } +@@100: { id: 100, surname: 'Harry author' } +@@101: { id: 101, surname: 'Lord author' } + +dependencies: +fetchBooks: [] diff -1, 2, 100, 101 +fetchBooks: [@@1, @@100] + +dependents: +@@1: [fetchBook] +@@2: [] // safe to remove +@@100: [fetchBook] +@@101: [] // safe to remove + +*/ + describe('reducers', () => { describe('queriesReducer', () => { describe('with normalization', () => { @@ -40,6 +187,7 @@ describe('reducers', () => { pending: -1, data: '@@1', usedKeys: { '': ['id', 'name'] }, + dependencies: ['@@1'], }, }, normalizedData: { '@@1': { id: '1', name: 'name' } }, @@ -55,6 +203,7 @@ describe('reducers', () => { error: null, normalized: true, usedKeys: { '': ['id', 'name'] }, + dependencies: [], ref: {}, }, }, @@ -99,6 +248,7 @@ describe('reducers', () => { '.root': ['id', 'name', 'nested'], '.root.nested': ['id', 'v'], }, + dependencies: ['@@1', '@@2', '@@3'], }, }, normalizedData: { @@ -132,6 +282,7 @@ describe('reducers', () => { pending: -1, data: '@@1', usedKeys: { '': ['id', 'a', 'c'] }, + dependencies: ['@@1'], }, }, normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } }, @@ -182,6 +333,7 @@ describe('reducers', () => { normalized: true, ref: {}, usedKeys: { '': ['id', 'x'] }, + dependencies: ['@@1'], }, }, normalizedData: { '@@1': { id: '1', x: 1 } }, @@ -211,6 +363,7 @@ describe('reducers', () => { error: null, normalized: true, usedKeys: { '': ['id', 'x'] }, + dependencies: ['@@1', '@@2', '@@3'], ref: {}, }, }, @@ -239,6 +392,7 @@ describe('reducers', () => { usedKeys: { '': ['id', 'x'], }, + dependencies: ['@@1'], ref: {}, }, }, @@ -266,6 +420,7 @@ describe('reducers', () => { error: null, normalized: true, usedKeys: { '': ['id', 'x'] }, + dependencies: ['@@1', '@@2'], ref: {}, }, }, @@ -286,6 +441,7 @@ describe('reducers', () => { pending: 0, error: null, normalized: true, + dependencies: ['@@1'], ref: {}, }, }, @@ -306,6 +462,7 @@ describe('reducers', () => { pending: 0, error: null, normalized: true, + dependencies: ['@@1'], ref: {}, }, }, @@ -325,6 +482,7 @@ describe('reducers', () => { pending: 0, error: null, normalized: true, + dependencies: ['@@1'], ref: {}, }, }, @@ -346,6 +504,7 @@ describe('reducers', () => { pending: 0, error: null, normalized: true, + dependencies: ['@@1'], ref: {}, }, }, @@ -365,6 +524,7 @@ describe('reducers', () => { pending: 0, error: null, normalized: true, + dependencies: ['@@1'], ref: {}, }, }, @@ -387,6 +547,7 @@ describe('reducers', () => { pending: 0, error: null, normalized: true, + dependencies: ['@@1'], ref: {}, }, }, @@ -415,6 +576,7 @@ describe('reducers', () => { ...defaultState, pending: -1, data: '@@1', + dependencies: ['@@1'], usedKeys: { '': ['_id', 'name'] }, }, }, From 030fb049599a0ef8332a448512a8dba6cbe38232 Mon Sep 17 00:00:00 2001 From: klis87 Date: Sun, 28 Feb 2021 15:53:33 +0100 Subject: [PATCH 10/14] Added dependentQueries to queries reducer --- .../src/reducers/queries-reducer.js | 44 ++++++++++++--- ...queries-reducer.with-normalisation.spec.js | 53 +++++++++++++++++-- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/packages/redux-requests/src/reducers/queries-reducer.js b/packages/redux-requests/src/reducers/queries-reducer.js index 59767f947..df3cf3be5 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.js +++ b/packages/redux-requests/src/reducers/queries-reducer.js @@ -29,6 +29,19 @@ const shouldBeNormalized = (action, config) => ? action.meta.normalize : config.normalize; +const addQueryAsDependency = (dependentQueries, dependencies, queryType) => + dependencies.reduce((prev, current) => { + if (!prev[current]) { + return { ...prev, [current]: [queryType] }; + } + + if (!prev[current].includes(queryType)) { + return { ...prev, [current]: [...prev.current, queryType] }; + } + + return prev; + }, dependentQueries); + const queryReducer = (state, action, actionType, config, normalizedData) => { if (state === undefined) { state = getInitialQuery(shouldBeNormalized(action, config)); @@ -156,6 +169,8 @@ export default (state, action, config = defaultConfig) => { ); if (action.meta?.mutations) { + let { dependentQueries } = state; + return { queries: { ...state.queries, @@ -178,18 +193,22 @@ export default (state, action, config = defaultConfig) => { updatedQuery.data, config, ); - const dependencies = getDependentKeys( - newdata, - newNormalizedData, - usedKeys, - ); + const dependencies = [ + ...getDependentKeys(newdata, newNormalizedData, usedKeys), + ]; normalizedData = mergeData(normalizedData, newNormalizedData); prev[actionType] = { ...updatedQuery, data: newdata, - dependencies: [...dependencies], + dependencies, usedKeys, }; + + dependentQueries = addQueryAsDependency( + dependentQueries, + dependencies, + actionType, + ); } else { prev[actionType] = updatedQuery; } @@ -197,6 +216,7 @@ export default (state, action, config = defaultConfig) => { }, {}), }, normalizedData, + dependentQueries, }; } @@ -221,6 +241,7 @@ export default (state, action, config = defaultConfig) => { return { queries: remainingQueries, normalizedData, + dependentQueries: state.dependentQueries, }; } @@ -235,7 +256,9 @@ export default (state, action, config = defaultConfig) => { config, ); - const dependencies = getDependentKeys(data, newNormalizedData, usedKeys); + const dependencies = [ + ...getDependentKeys(data, newNormalizedData, usedKeys), + ]; return { queries: { @@ -244,10 +267,15 @@ export default (state, action, config = defaultConfig) => { ...updatedQuery, data, usedKeys, - dependencies: [...dependencies], + dependencies, }, }, normalizedData: mergeData(normalizedData, newNormalizedData), + dependentQueries: addQueryAsDependency( + state.dependentQueries, + dependencies, + queryType, + ), }; } diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js index d408bf79c..b5d9d4052 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js @@ -174,7 +174,7 @@ describe('reducers', () => { it('should normalize data on query success', () => { expect( queriesReducer( - { queries: {}, normalizedData: {} }, + { queries: {}, normalizedData: {}, dependentQueries: {} }, createSuccessAction(requestAction, { data: { id: '1', name: 'name' }, }), @@ -191,6 +191,9 @@ describe('reducers', () => { }, }, normalizedData: { '@@1': { id: '1', name: 'name' } }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }); }); @@ -208,6 +211,7 @@ describe('reducers', () => { }, }, normalizedData: {}, + dependentQueries: {}, }; const state = queriesReducer( initialState, @@ -221,7 +225,7 @@ describe('reducers', () => { it('should normalize data with nested ids and arrays on query success', () => { expect( queriesReducer( - { queries: {}, normalizedData: {} }, + { queries: {}, normalizedData: {}, dependentQueries: {} }, createSuccessAction(requestAction, { data: { root: { @@ -260,6 +264,11 @@ describe('reducers', () => { '@@2': { id: '2', v: 2 }, '@@3': { id: '3', v: 3 }, }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + '@@2': ['FETCH_BOOK'], + '@@3': ['FETCH_BOOK'], + }, }); }); @@ -269,6 +278,7 @@ describe('reducers', () => { { queries: {}, normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } }, + dependentQueries: {}, }, createSuccessAction(requestAction, { data: { id: '1', a: 'd', c: 'c' }, @@ -286,6 +296,9 @@ describe('reducers', () => { }, }, normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }); }); @@ -295,6 +308,7 @@ describe('reducers', () => { { queries: {}, normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } }, + dependentQueries: {}, }, createSuccessAction( { @@ -313,6 +327,7 @@ describe('reducers', () => { ).toEqual({ queries: {}, normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } }, + dependentQueries: {}, }); }); @@ -337,6 +352,7 @@ describe('reducers', () => { }, }, normalizedData: { '@@1': { id: '1', x: 1 } }, + dependentQueries: { '@@1': ['FETCH_BOOK'] }, }, createSuccessAction( { @@ -372,6 +388,11 @@ describe('reducers', () => { '@@2': { id: '2', x: 2 }, '@@3': { id: '3', x: 3 }, }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + '@@2': ['FETCH_BOOK'], + '@@3': ['FETCH_BOOK'], + }, }); expect(updateData).toBeCalledWith([{ id: '1', x: 1 }], { id: '2', @@ -397,6 +418,7 @@ describe('reducers', () => { }, }, normalizedData: { '@@1': { id: '1', x: 1 } }, + dependentQueries: { '@@1': ['FETCH_BOOK'] }, }, { type: 'ADD_BOOK_LOCALLY', @@ -428,6 +450,10 @@ describe('reducers', () => { '@@1': { id: '1', x: 1 }, '@@2': { id: '2', x: 2 }, }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + '@@2': ['FETCH_BOOK'], + }, }); }); @@ -446,6 +472,9 @@ describe('reducers', () => { }, }, normalizedData: { '@@1': { id: '1', x: 1 } }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }, { type: 'UPDATE_BOOK_LOCALLY', @@ -469,6 +498,9 @@ describe('reducers', () => { normalizedData: { '@@1': { id: '1', x: 2 }, }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }); }); @@ -487,6 +519,9 @@ describe('reducers', () => { }, }, normalizedData: { '@@1': { id: '1', x: 1 } }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }, { type: 'UPDATE_BOOK', @@ -511,6 +546,9 @@ describe('reducers', () => { normalizedData: { '@@1': { id: '1', x: 2 }, }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }); }); @@ -529,6 +567,9 @@ describe('reducers', () => { }, }, normalizedData: { '@@1': { id: '1', x: 2 } }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }, createErrorAction({ type: 'UPDATE_BOOK', @@ -554,13 +595,16 @@ describe('reducers', () => { normalizedData: { '@@1': { id: '1', x: 1 }, }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }); }); it('should allow custom shouldObjectBeNormalized and getNormalisationObjectKey', () => { expect( queriesReducer( - { queries: {}, normalizedData: {} }, + { queries: {}, normalizedData: {}, dependentQueries: {} }, createSuccessAction(requestAction, { data: { _id: '1', name: 'name' }, }), @@ -581,6 +625,9 @@ describe('reducers', () => { }, }, normalizedData: { '@@1': { _id: '1', name: 'name' } }, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }); }); }); From 45143c8500ea7beaf8ece4ae2cf134d22655e4d0 Mon Sep 17 00:00:00 2001 From: klis87 Date: Sat, 13 Mar 2021 23:56:36 +0100 Subject: [PATCH 11/14] Clean redundant data in dependentQueries and normalizedData --- .../src/reducers/queries-reducer.js | 191 +++++-- ...queries-reducer.with-normalisation.spec.js | 471 +++++++++++++++++- .../src/reducers/requests-reset-reducer.js | 1 + .../reducers/requests-reset-reducer.spec.js | 12 + 4 files changed, 634 insertions(+), 41 deletions(-) diff --git a/packages/redux-requests/src/reducers/queries-reducer.js b/packages/redux-requests/src/reducers/queries-reducer.js index df3cf3be5..5256eec97 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.js +++ b/packages/redux-requests/src/reducers/queries-reducer.js @@ -10,6 +10,7 @@ import { } from '../actions'; import { getQuery } from '../selectors'; import { normalize, mergeData, getDependentKeys } from '../normalizers'; +import { mapObject } from '../helpers'; import updateData from './update-data'; @@ -29,18 +30,50 @@ const shouldBeNormalized = (action, config) => ? action.meta.normalize : config.normalize; -const addQueryAsDependency = (dependentQueries, dependencies, queryType) => - dependencies.reduce((prev, current) => { - if (!prev[current]) { - return { ...prev, [current]: [queryType] }; +const addQueryAsDependency = (dependentQueries, dependencies, queryType) => { + dependencies.forEach(dependency => { + if (!dependentQueries[dependency]) { + dependentQueries = { ...dependentQueries, [dependency]: [queryType] }; } - if (!prev[current].includes(queryType)) { - return { ...prev, [current]: [...prev.current, queryType] }; + if (!dependentQueries[dependency].includes(queryType)) { + dependentQueries = { + ...dependentQueries, + [dependency]: [...dependentQueries[dependency], queryType], + }; + } + }); + + return dependentQueries; +}; + +const removeQueryAsDependency = (dependentQueries, dependencies, queryType) => { + dependencies.forEach(dependency => { + if (dependentQueries[dependency].length > 1) { + dependentQueries = { + ...dependentQueries, + [dependency]: dependentQueries[dependency].filter(v => v !== queryType), + }; + } else { + dependentQueries = mapObject(dependentQueries, (k, v) => + k === dependency ? undefined : v, + ); } + }); + + return dependentQueries; +}; - return prev; - }, dependentQueries); +const getDependenciesDiff = (oldDependencies, newDependencies) => { + return { + addedDependencies: newDependencies.filter( + v => !oldDependencies.includes(v), + ), + removedDependencies: oldDependencies.filter( + v => !newDependencies.includes(v), + ), + }; +}; const queryReducer = (state, action, actionType, config, normalizedData) => { if (state === undefined) { @@ -128,10 +161,9 @@ const maybeGetQueryActionType = (action, config) => { return null; }; -const updateNormalizedData = (normalizedData, action, config) => { +const maybeGetMutationData = (action, config) => { if (config.isRequestAction(action) && action.meta?.optimisticData) { - const [, newNormalizedData] = normalize(action.meta.optimisticData, config); - return mergeData(normalizedData, newNormalizedData); + return action.meta.optimisticData; } if ( @@ -139,8 +171,7 @@ const updateNormalizedData = (normalizedData, action, config) => { isErrorAction(action) && action.meta.revertedData ) { - const [, newNormalizedData] = normalize(action.meta.revertedData, config); - return mergeData(normalizedData, newNormalizedData); + return action.meta.revertedData; } if ( @@ -149,36 +180,121 @@ const updateNormalizedData = (normalizedData, action, config) => { shouldBeNormalized(action, config) && !config.isRequestActionQuery(getRequestActionFromResponse(action)) ) { - const [, newNormalizedData] = normalize(action.response.data, config); - return mergeData(normalizedData, newNormalizedData); + return action.response.data; } if (action.meta?.localData) { - const [, newNormalizedData] = normalize(action.meta.localData, config); - return mergeData(normalizedData, newNormalizedData); + return action.meta.localData; } - return normalizedData; + return null; +}; + +const updateNormalizedData = (normalizedData, mutationData, config) => { + const [, newNormalizedData] = normalize(mutationData, config); + return mergeData(normalizedData, newNormalizedData); +}; + +const getQueriesDependentOnMutation = ( + dependentQueries, + mutationDependencies, +) => { + const queries = []; + const orphanDependencies = []; + + mutationDependencies.forEach(dependency => { + if (dependentQueries[dependency]) { + queries.push(...dependentQueries[dependency]); + } else { + orphanDependencies.push(dependency); + } + }); + + return { foundQueries: [...new Set(queries)], orphanDependencies }; }; export default (state, action, config = defaultConfig) => { - let normalizedData = updateNormalizedData( - state.normalizedData, - action, - config, - ); + let { normalizedData, queries, dependentQueries } = state; + const mutationDataToNormalize = maybeGetMutationData(action, config); - if (action.meta?.mutations) { - let { dependentQueries } = state; + if (mutationDataToNormalize) { + const [, mutationNormalizedData] = normalize( + mutationDataToNormalize, + config, + ); + const mutationDependencies = Object.keys(mutationNormalizedData); + const { foundQueries, orphanDependencies } = getQueriesDependentOnMutation( + dependentQueries, + mutationDependencies, + ); + const recalculatedQueries = {}; + normalizedData = updateNormalizedData( + normalizedData, + mutationDataToNormalize, + config, + ); + const potentialDependenciesToRemove = new Set(orphanDependencies); + + foundQueries.forEach(query => { + // const [newdata, newNormalizedData, usedKeys] = normalize( + // queries[query], + // config, + // ); + + const dependencies = [ + ...getDependentKeys( + queries[query].data, + normalizedData, + queries[query].usedKeys, + ), + ]; + + const { addedDependencies, removedDependencies } = getDependenciesDiff( + queries[query].dependencies, + dependencies, + ); + + removedDependencies.forEach(v => { + potentialDependenciesToRemove.add(v); + }); + + dependentQueries = addQueryAsDependency( + dependentQueries, + addedDependencies, + query, + ); + + dependentQueries = removeQueryAsDependency( + dependentQueries, + removedDependencies, + query, + ); + + recalculatedQueries[query] = { + ...queries[query], + dependencies, + }; + }); + + queries = { ...queries, ...recalculatedQueries }; + const reallyRemovedDeps = [...potentialDependenciesToRemove].filter( + v => !dependentQueries[v], + ); + normalizedData = mapObject(normalizedData, (k, v) => + reallyRemovedDeps.includes(k) ? undefined : v, + ); + } + + if (action.meta?.mutations) { return { queries: { - ...state.queries, + ...queries, ...Object.keys(action.meta.mutations) - .filter(actionType => !!state.queries[actionType]) + .filter(actionType => !!queries[actionType]) .reduce((prev, actionType) => { const updatedQuery = queryReducer( - state.queries[actionType], + queries[actionType], action, actionType, config, @@ -187,7 +303,7 @@ export default (state, action, config = defaultConfig) => { if ( updatedQuery.normalized && - updatedQuery.data !== state.queries[actionType].data + updatedQuery.data !== queries[actionType].data ) { const [newdata, newNormalizedData, usedKeys] = normalize( updatedQuery.data, @@ -228,7 +344,7 @@ export default (state, action, config = defaultConfig) => { ? queryActionType + action.meta.requestKey : queryActionType; const updatedQuery = queryReducer( - state.queries[queryType], + queries[queryType], action, queryActionType, config, @@ -236,20 +352,19 @@ export default (state, action, config = defaultConfig) => { if (updatedQuery === undefined) { // eslint-disable-next-line no-unused-vars - const { [queryType]: toRemove, ...remainingQueries } = state.queries; + const { [queryType]: toRemove, ...remainingQueries } = queries; return { queries: remainingQueries, normalizedData, - dependentQueries: state.dependentQueries, + dependentQueries, }; } if ( updatedQuery.normalized && updatedQuery.data && - (!state.queries[queryType] || - state.queries[queryType].data !== updatedQuery.data) + (!queries[queryType] || queries[queryType].data !== updatedQuery.data) ) { const [data, newNormalizedData, usedKeys] = normalize( updatedQuery.data, @@ -262,7 +377,7 @@ export default (state, action, config = defaultConfig) => { return { queries: { - ...state.queries, + ...queries, [queryType]: { ...updatedQuery, data, @@ -272,7 +387,7 @@ export default (state, action, config = defaultConfig) => { }, normalizedData: mergeData(normalizedData, newNormalizedData), dependentQueries: addQueryAsDependency( - state.dependentQueries, + dependentQueries, dependencies, queryType, ), @@ -281,7 +396,7 @@ export default (state, action, config = defaultConfig) => { return { queries: { - ...state.queries, + ...queries, [queryType]: updatedQuery, }, normalizedData, @@ -293,5 +408,7 @@ export default (state, action, config = defaultConfig) => { : { ...state, normalizedData, + queries, + dependentQueries, }; }; diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js index b5d9d4052..98cd6a96e 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js @@ -152,6 +152,433 @@ dependents: describe('reducers', () => { describe('queriesReducer', () => { + describe('normalisation garbage collecting story', () => { + const defaultState = { + data: null, + error: null, + pending: 0, + pristine: true, + normalized: true, + usedKeys: [], + dependencies: [], + ref: {}, + }; + + it('handles initial books fetch', () => { + const fetchBooks = { + type: 'FETCH_BOOKS', + request: { url: '/books' }, + meta: { normalize: true }, + }; + + expect( + queriesReducer( + { + queries: { FETCH_BOOKS: defaultState }, + normalizedData: {}, + dependentQueries: {}, + }, + createSuccessAction(fetchBooks, { + data: [ + { + id: 1, + name: 'Harry', + author: { id: 100, surname: 'Harry author' }, + likers: [], + }, + { + id: 2, + name: 'Lord', + author: { id: 101, surname: 'Lord author' }, + likers: [], + }, + ], + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOKS: { + ...defaultState, + pending: -1, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100', '@@2', '@@101'], + }, + }, + normalizedData: { + '@@1': { id: 1, name: 'Harry', author: '@@100', likers: [] }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@100': { id: 100, surname: 'Harry author' }, + '@@101': { id: 101, surname: 'Lord author' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS'], + '@@2': ['FETCH_BOOKS'], + '@@100': ['FETCH_BOOKS'], + '@@101': ['FETCH_BOOKS'], + }, + }); + }); + + it('handles book detail fetch', () => { + const fetchBook = { + type: 'FETCH_BOOK', + request: { url: '/book/1' }, + meta: { normalize: true }, + }; + + expect( + queriesReducer( + { + queries: { + FETCH_BOOKS: { + ...defaultState, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100', '@@2', '@@101'], + }, + FETCH_BOOK: defaultState, + }, + normalizedData: { + '@@1': { id: 1, name: 'Harry', author: '@@100', likers: [] }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@100': { id: 100, surname: 'Harry author' }, + '@@101': { id: 101, surname: 'Lord author' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS'], + '@@2': ['FETCH_BOOKS'], + '@@100': ['FETCH_BOOKS'], + '@@101': ['FETCH_BOOKS'], + }, + }, + createSuccessAction(fetchBook, { + data: { + id: 1, + name: 'Harry', + author: { id: 100, surname: 'Harry author' }, + likers: [], + }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOKS: { + ...defaultState, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100', '@@2', '@@101'], + }, + FETCH_BOOK: { + ...defaultState, + pending: -1, + data: '@@1', + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100'], + }, + }, + normalizedData: { + '@@1': { id: 1, name: 'Harry', author: '@@100', likers: [] }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@100': { id: 100, surname: 'Harry author' }, + '@@101': { id: 101, surname: 'Lord author' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@2': ['FETCH_BOOKS'], + '@@100': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@101': ['FETCH_BOOKS'], + }, + }); + }); + + it('handles book update', () => { + const updateBook = { + type: 'UPDATE_BOOK', + request: { url: '/book/1', method: 'put' }, + meta: { normalize: true }, + }; + + expect( + queriesReducer( + { + queries: { + FETCH_BOOKS: { + ...defaultState, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100', '@@2', '@@101'], + }, + FETCH_BOOK: { + ...defaultState, + data: '@@1', + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100'], + }, + }, + normalizedData: { + '@@1': { id: 1, name: 'Harry', author: '@@100', likers: [] }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@100': { id: 100, surname: 'Harry author' }, + '@@101': { id: 101, surname: 'Lord author' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@2': ['FETCH_BOOKS'], + '@@100': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@101': ['FETCH_BOOKS'], + }, + }, + createSuccessAction(updateBook, { + data: { + id: 1, + name: 'Harry 2', + author: { id: 100, surname: 'Harry 2 author' }, + }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOKS: { + ...defaultState, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100', '@@2', '@@101'], + }, + FETCH_BOOK: { + ...defaultState, + data: '@@1', + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100'], + }, + }, + normalizedData: { + '@@1': { id: 1, name: 'Harry 2', author: '@@100', likers: [] }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@100': { id: 100, surname: 'Harry 2 author' }, + '@@101': { id: 101, surname: 'Lord author' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@2': ['FETCH_BOOKS'], + '@@100': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@101': ['FETCH_BOOKS'], + }, + }); + }); + + it('handles book author change and orphan object', () => { + const updateBookAuthor = { + type: 'UPDATE_BOOK_AUTHOR', + request: { url: '/book/1/author', method: 'put' }, + meta: { normalize: true }, + }; + + expect( + queriesReducer( + { + queries: { + FETCH_BOOKS: { + ...defaultState, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100', '@@2', '@@101'], + }, + FETCH_BOOK: { + ...defaultState, + data: '@@1', + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@100'], + }, + }, + normalizedData: { + '@@1': { id: 1, name: 'Harry 2', author: '@@100', likers: [] }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@100': { id: 100, surname: 'Harry 2 author' }, + '@@101': { id: 101, surname: 'Lord author' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@2': ['FETCH_BOOKS'], + '@@100': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@101': ['FETCH_BOOKS'], + }, + }, + createSuccessAction(updateBookAuthor, { + data: { + book: { + id: 1, + author: { id: 102, surname: 'Harry 2 new author' }, + }, + orphan: { id: 1000 }, + }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOKS: { + ...defaultState, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@102', '@@2', '@@101'], + }, + FETCH_BOOK: { + ...defaultState, + data: '@@1', + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@102'], + }, + }, + normalizedData: { + '@@1': { id: 1, name: 'Harry 2', author: '@@102', likers: [] }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@101': { id: 101, surname: 'Lord author' }, + '@@102': { id: 102, surname: 'Harry 2 new author' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@2': ['FETCH_BOOKS'], + '@@101': ['FETCH_BOOKS'], + '@@102': ['FETCH_BOOKS', 'FETCH_BOOK'], + }, + }); + }); + + it('handles added liker', () => { + const addBookLiker = { + type: 'ADD_BOOK_LIKER', + request: { url: '/book/1/liker', method: 'put' }, + meta: { normalize: true }, + }; + + expect( + queriesReducer( + { + queries: { + FETCH_BOOKS: { + ...defaultState, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@102', '@@2', '@@101'], + }, + FETCH_BOOK: { + ...defaultState, + data: '@@1', + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@102'], + }, + }, + normalizedData: { + '@@1': { id: 1, name: 'Harry 2', author: '@@102', likers: [] }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@101': { id: 101, surname: 'Lord author' }, + '@@102': { id: 102, surname: 'Harry 2 new author' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@2': ['FETCH_BOOKS'], + '@@101': ['FETCH_BOOKS'], + '@@102': ['FETCH_BOOKS', 'FETCH_BOOK'], + }, + }, + createSuccessAction(addBookLiker, { + data: { + id: 1, + likers: [{ id: 1000, name: 'Liker 1' }], + }, + }), + defaultConfig, + ), + ).toEqual({ + queries: { + FETCH_BOOKS: { + ...defaultState, + data: ['@@1', '@@2'], + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@102', '@@1000', '@@2', '@@101'], + }, + FETCH_BOOK: { + ...defaultState, + data: '@@1', + usedKeys: { + '': ['id', 'name', 'author', 'likers'], + '.author': ['id', 'surname'], + }, + dependencies: ['@@1', '@@102', '@@1000'], + }, + }, + normalizedData: { + '@@1': { + id: 1, + name: 'Harry 2', + author: '@@102', + likers: ['@@1000'], + }, + '@@2': { id: 2, name: 'Lord', author: '@@101', likers: [] }, + '@@101': { id: 101, surname: 'Lord author' }, + '@@102': { id: 102, surname: 'Harry 2 new author' }, + '@@1000': { id: 1000, name: 'Liker 1' }, + }, + dependentQueries: { + '@@1': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@2': ['FETCH_BOOKS'], + '@@101': ['FETCH_BOOKS'], + '@@102': ['FETCH_BOOKS', 'FETCH_BOOK'], + '@@1000': ['FETCH_BOOKS', 'FETCH_BOOK'], + }, + }); + }); + }); + describe('with normalization', () => { const defaultState = { data: null, @@ -306,9 +733,18 @@ describe('reducers', () => { expect( queriesReducer( { - queries: {}, + queries: { + FETCH_BOOK: { + ...defaultState, + data: '@@1', + usedKeys: { '': ['id', 'a', 'b'] }, + dependencies: ['@@1'], + }, + }, normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } }, - dependentQueries: {}, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }, createSuccessAction( { @@ -325,9 +761,18 @@ describe('reducers', () => { defaultConfig, ), ).toEqual({ - queries: {}, + queries: { + FETCH_BOOK: { + ...defaultState, + data: '@@1', + usedKeys: { '': ['id', 'a', 'b'] }, + dependencies: ['@@1'], + }, + }, normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } }, - dependentQueries: {}, + dependentQueries: { + '@@1': ['FETCH_BOOK'], + }, }); }); @@ -469,6 +914,9 @@ describe('reducers', () => { normalized: true, dependencies: ['@@1'], ref: {}, + usedKeys: { + '': ['id', 'x'], + }, }, }, normalizedData: { '@@1': { id: '1', x: 1 } }, @@ -493,6 +941,9 @@ describe('reducers', () => { normalized: true, dependencies: ['@@1'], ref: {}, + usedKeys: { + '': ['id', 'x'], + }, }, }, normalizedData: { @@ -516,6 +967,9 @@ describe('reducers', () => { normalized: true, dependencies: ['@@1'], ref: {}, + usedKeys: { + '': ['id', 'x'], + }, }, }, normalizedData: { '@@1': { id: '1', x: 1 } }, @@ -541,6 +995,9 @@ describe('reducers', () => { normalized: true, dependencies: ['@@1'], ref: {}, + usedKeys: { + '': ['id', 'x'], + }, }, }, normalizedData: { @@ -564,6 +1021,9 @@ describe('reducers', () => { normalized: true, dependencies: ['@@1'], ref: {}, + usedKeys: { + '': ['id', 'x'], + }, }, }, normalizedData: { '@@1': { id: '1', x: 2 } }, @@ -590,6 +1050,9 @@ describe('reducers', () => { normalized: true, dependencies: ['@@1'], ref: {}, + usedKeys: { + '': ['id', 'x'], + }, }, }, normalizedData: { diff --git a/packages/redux-requests/src/reducers/requests-reset-reducer.js b/packages/redux-requests/src/reducers/requests-reset-reducer.js index 67b4cfc75..5d7543cc7 100644 --- a/packages/redux-requests/src/reducers/requests-reset-reducer.js +++ b/packages/redux-requests/src/reducers/requests-reset-reducer.js @@ -20,6 +20,7 @@ const resetQuery = query => error: null, pristine: true, usedKeys: query.normalized ? {} : null, + dependencies: query.normalized ? [] : null, }; const resetMutation = mutation => diff --git a/packages/redux-requests/src/reducers/requests-reset-reducer.spec.js b/packages/redux-requests/src/reducers/requests-reset-reducer.spec.js index 95d8d13be..ba2af6a32 100644 --- a/packages/redux-requests/src/reducers/requests-reset-reducer.spec.js +++ b/packages/redux-requests/src/reducers/requests-reset-reducer.spec.js @@ -21,6 +21,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, }, QUERY2: { data: 'data', @@ -29,6 +30,7 @@ describe('reducers', () => { pristine: false, normalized: true, usedKeys: { x: 1 }, + dependencies: [], }, }, mutations: { @@ -49,6 +51,7 @@ describe('reducers', () => { pristine: true, normalized: false, usedKeys: null, + dependencies: null, }, QUERY2: { data: null, @@ -57,6 +60,7 @@ describe('reducers', () => { pristine: true, normalized: true, usedKeys: {}, + dependencies: [], }, }, mutations: { @@ -80,6 +84,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, }, QUERY2: { data: 'data', @@ -88,6 +93,7 @@ describe('reducers', () => { pristine: false, normalized: true, usedKeys: { x: 1 }, + dependencies: [], }, QUERY3: { data: 'data', @@ -96,6 +102,7 @@ describe('reducers', () => { pristine: false, normalized: true, usedKeys: { x: 1 }, + dependencies: [], }, }, mutations: { @@ -122,6 +129,7 @@ describe('reducers', () => { pristine: true, normalized: false, usedKeys: null, + dependencies: null, }, QUERY2: { data: null, @@ -130,6 +138,7 @@ describe('reducers', () => { pristine: true, normalized: true, usedKeys: {}, + dependencies: [], }, QUERY3: { data: 'data', @@ -138,6 +147,7 @@ describe('reducers', () => { pristine: false, normalized: true, usedKeys: { x: 1 }, + dependencies: [], }, }, mutations: { @@ -166,6 +176,7 @@ describe('reducers', () => { pristine: false, normalized: false, usedKeys: null, + dependencies: null, }, }, mutations: {}, @@ -184,6 +195,7 @@ describe('reducers', () => { pristine: true, normalized: false, usedKeys: null, + dependencies: null, }, }, mutations: {}, From 7e11d5af7d4a57371e7ebc4c789fed7b3711bbda Mon Sep 17 00:00:00 2001 From: klis87 Date: Sun, 14 Mar 2021 13:37:54 +0100 Subject: [PATCH 12/14] Added requests creators prototype --- packages/redux-requests/src/index.js | 5 +++ .../src/reducers/queries-reducer.js | 5 --- .../redux-requests/src/requests-creators.js | 21 +++++++++ .../src/requests-creators.spec.js | 45 +++++++++++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 packages/redux-requests/src/requests-creators.js create mode 100644 packages/redux-requests/src/requests-creators.spec.js diff --git a/packages/redux-requests/src/index.js b/packages/redux-requests/src/index.js index 813f0c438..a220d5aca 100644 --- a/packages/redux-requests/src/index.js +++ b/packages/redux-requests/src/index.js @@ -16,6 +16,11 @@ export { openWebsocket, closeWebsocket, } from './actions'; +export { + createQuery, + createMutation, + createSubscription, +} from './requests-creators'; export { default as handleRequests } from './handle-requests'; export { getQuery, diff --git a/packages/redux-requests/src/reducers/queries-reducer.js b/packages/redux-requests/src/reducers/queries-reducer.js index 5256eec97..e21f89829 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.js +++ b/packages/redux-requests/src/reducers/queries-reducer.js @@ -236,11 +236,6 @@ export default (state, action, config = defaultConfig) => { const potentialDependenciesToRemove = new Set(orphanDependencies); foundQueries.forEach(query => { - // const [newdata, newNormalizedData, usedKeys] = normalize( - // queries[query], - // config, - // ); - const dependencies = [ ...getDependentKeys( queries[query].data, diff --git a/packages/redux-requests/src/requests-creators.js b/packages/redux-requests/src/requests-creators.js new file mode 100644 index 000000000..59fd54eca --- /dev/null +++ b/packages/redux-requests/src/requests-creators.js @@ -0,0 +1,21 @@ +const createRequest = requestType => (name, requestConfig, metaConfig) => { + const actionCreator = (...params) => ({ + type: name, + payload: + typeof requestConfig === 'function' + ? requestConfig(...params) + : requestConfig, + meta: { + ...(typeof metaConfig === 'function' + ? metaConfig(...params) + : metaConfig), + requestType, + }, + }); + actionCreator.toString = () => name; + return actionCreator; +}; + +export const createQuery = createRequest('QUERY'); +export const createMutation = createRequest('MUTATION'); +export const createSubscription = createRequest('SUBSCRIPTION'); diff --git a/packages/redux-requests/src/requests-creators.spec.js b/packages/redux-requests/src/requests-creators.spec.js new file mode 100644 index 000000000..92545a6f2 --- /dev/null +++ b/packages/redux-requests/src/requests-creators.spec.js @@ -0,0 +1,45 @@ +import { createQuery } from './requests-creators'; + +describe('requestsCreators', () => { + describe('createQuery', () => { + it('adds toString method', () => { + const queryCreator = createQuery('QUERY', () => ({ url: '/' })); + expect(queryCreator.toString()).toBe('QUERY'); + }); + + it('can create queries with only request config', () => { + const queryCreator = createQuery('QUERY', { url: '/' }); + expect(queryCreator()).toEqual({ + type: 'QUERY', + payload: { url: '/' }, + meta: { requestType: 'QUERY' }, + }); + }); + + it('merges meta properly', () => { + const queryCreator = createQuery( + 'QUERY', + { url: '/' }, + { normalize: true }, + ); + expect(queryCreator()).toEqual({ + type: 'QUERY', + payload: { url: '/' }, + meta: { requestType: 'QUERY', normalize: true }, + }); + }); + + it('allows callbacks configs', () => { + const queryCreator = createQuery( + 'QUERY', + id => ({ url: `/${id}` }), + id => ({ requestKey: id }), + ); + expect(queryCreator('1')).toEqual({ + type: 'QUERY', + payload: { url: '/1' }, + meta: { requestType: 'QUERY', requestKey: '1' }, + }); + }); + }); +}); From ffd22a2684fb70672cfc032ba19c30c2afed8ed1 Mon Sep 17 00:00:00 2001 From: klis87 Date: Wed, 15 Dec 2021 00:44:36 +0100 Subject: [PATCH 13/14] Update code to use request creators --- .eslintrc | 11 +- packages/redux-requests-axios/package.json | 2 +- packages/redux-requests-fetch/package.json | 2 +- packages/redux-requests-graphql/package.json | 2 +- packages/redux-requests-mock/package.json | 2 +- packages/redux-requests-promise/package.json | 2 +- packages/redux-requests-react/package.json | 4 +- packages/redux-requests-react/src/index.js | 2 +- .../src/requests-provider.jsx | 14 +- ...se-dispatch-request.js => use-dispatch.js} | 0 .../redux-requests-react/src/use-mutation.js | 18 +- .../redux-requests-react/src/use-query.js | 22 +- .../src/use-subscription.js | 15 +- packages/redux-requests/package.json | 4 +- packages/redux-requests/src/actions.js | 39 ++-- packages/redux-requests/src/actions.spec.js | 90 +------- packages/redux-requests/src/index.js | 2 +- .../create-client-ssr-middleware.js | 4 +- .../create-client-ssr-middleware.spec.js | 18 +- .../middleware/create-polling-middleware.js | 6 +- .../create-polling-middleware.spec.js | 88 +++----- .../create-requests-cache-middleware.js | 4 +- .../create-requests-cache-middleware.spec.js | 55 ++--- .../src/middleware/create-requests-store.js | 6 - .../create-send-requests-middleware.js | 109 ++++----- .../create-send-requests-middleware.spec.js | 206 ++++++++---------- .../create-server-ssr-middleware.js | 2 +- .../create-server-ssr-middleware.spec.js | 93 ++++---- .../create-subscriptions-middleware.js | 72 +++--- .../create-subscriptions-middleware.spec.js | 60 ++--- .../redux-requests/src/middleware/index.js | 1 - .../src/reducers/cache-reducer.js | 7 +- .../src/reducers/cache-reducer.spec.js | 24 +- .../src/reducers/mutations-reducer.js | 57 ++--- .../src/reducers/progress-reducer.js | 8 +- .../src/reducers/queries-reducer.js | 7 +- .../reducers/queries-reducer.simple.spec.js | 6 +- ...h-mutations-with-query-request-key.spec.js | 35 ++- .../queries-reducer.with-mutations.spec.js | 97 ++++----- ...queries-reducer.with-normalisation.spec.js | 138 ++++++------ .../queries-reducer.with-request-key.js | 11 +- .../src/reducers/requests-keys-reducer.js | 2 +- .../reducers/requests-keys-reducer.spec.js | 51 ++--- .../src/reducers/requests-reducer.js | 13 +- .../src/reducers/requests-reducer.spec.js | 79 ++++--- .../src/reducers/requests-reset-reducer.js | 7 +- .../src/reducers/ssr-reducer.js | 2 +- .../src/reducers/ssr-reducer.spec.js | 28 +-- .../src/reducers/update-data.js | 8 +- .../redux-requests/src/requests-creators.js | 22 +- packages/redux-requests/types/index.d.spec.ts | 156 +++++-------- packages/redux-requests/types/index.d.ts | 202 ++++++++++------- yarn.lock | 31 +-- 53 files changed, 870 insertions(+), 1076 deletions(-) rename packages/redux-requests-react/src/{use-dispatch-request.js => use-dispatch.js} (100%) delete mode 100644 packages/redux-requests/src/middleware/create-requests-store.js diff --git a/.eslintrc b/.eslintrc index 835f1c944..838eefc52 100644 --- a/.eslintrc +++ b/.eslintrc @@ -62,13 +62,14 @@ "no-sequences": 2, "no-console": 1, + "import/no-unresolved": 2, "import/no-self-import": 2, "import/first": 2, "import/newline-after-import": 2, "import/no-named-default": 2, "import/dynamic-import-chunkname": 2, "import/no-unused-modules": [ - 2, + 0, { "missingExports": false, "unusedExports": true, @@ -76,7 +77,13 @@ } ], "import/no-extraneous-dependencies": 2, - "import/order": [2, { "newlines-between": "always" }], + "import/order": [ + 2, + { + "groups": ["builtin", "external", "parent", "sibling", "index"], + "newlines-between": "always" + } + ], "import/named": 2, "react/prop-types": 0, diff --git a/packages/redux-requests-axios/package.json b/packages/redux-requests-axios/package.json index 80098f109..094c7f02e 100644 --- a/packages/redux-requests-axios/package.json +++ b/packages/redux-requests-axios/package.json @@ -56,7 +56,7 @@ "nodemon": "2.0.6", "npm-run-all": "4.1.5", "rimraf": "3.0.2", - "typescript": "4.1.2", + "typescript": "4.2.4", "webpack": "5.9.0", "webpack-cli": "4.2.0" }, diff --git a/packages/redux-requests-fetch/package.json b/packages/redux-requests-fetch/package.json index 4937d3cbb..3e47e0add 100644 --- a/packages/redux-requests-fetch/package.json +++ b/packages/redux-requests-fetch/package.json @@ -58,7 +58,7 @@ "nodemon": "2.0.6", "npm-run-all": "4.1.5", "rimraf": "3.0.2", - "typescript": "4.1.2", + "typescript": "4.2.4", "webpack": "5.9.0", "webpack-cli": "4.2.0", "yet-another-abortcontroller-polyfill": "0.0.4" diff --git a/packages/redux-requests-graphql/package.json b/packages/redux-requests-graphql/package.json index aa7e2e567..8bac966fd 100644 --- a/packages/redux-requests-graphql/package.json +++ b/packages/redux-requests-graphql/package.json @@ -57,7 +57,7 @@ "nodemon": "2.0.6", "npm-run-all": "4.1.5", "rimraf": "3.0.2", - "typescript": "4.1.2", + "typescript": "4.2.4", "webpack": "5.9.0", "webpack-cli": "4.2.0" }, diff --git a/packages/redux-requests-mock/package.json b/packages/redux-requests-mock/package.json index 837a6f822..06d2398a8 100644 --- a/packages/redux-requests-mock/package.json +++ b/packages/redux-requests-mock/package.json @@ -55,7 +55,7 @@ "nodemon": "2.0.6", "npm-run-all": "4.1.5", "rimraf": "3.0.2", - "typescript": "4.1.2", + "typescript": "4.2.4", "webpack": "5.9.0", "webpack-cli": "4.2.0" }, diff --git a/packages/redux-requests-promise/package.json b/packages/redux-requests-promise/package.json index 0a0253e21..33d659876 100644 --- a/packages/redux-requests-promise/package.json +++ b/packages/redux-requests-promise/package.json @@ -54,7 +54,7 @@ "nodemon": "2.0.6", "npm-run-all": "4.1.5", "rimraf": "3.0.2", - "typescript": "4.1.2", + "typescript": "4.2.4", "webpack": "5.9.0", "webpack-cli": "4.2.0" }, diff --git a/packages/redux-requests-react/package.json b/packages/redux-requests-react/package.json index ab1fd2bff..140dfd6a4 100644 --- a/packages/redux-requests-react/package.json +++ b/packages/redux-requests-react/package.json @@ -65,10 +65,10 @@ "react-dom": "17.0.1", "react-redux": "7.2.2", "react-test-renderer": "17.0.1", - "redux": "4.0.5", + "redux": "4.1.0", "redux-mock-store": "1.5.4", "rimraf": "3.0.2", - "typescript": "4.1.2", + "typescript": "4.2.4", "webpack": "5.9.0", "webpack-cli": "4.2.0" }, diff --git a/packages/redux-requests-react/src/index.js b/packages/redux-requests-react/src/index.js index 4ac5a099b..680864b1e 100644 --- a/packages/redux-requests-react/src/index.js +++ b/packages/redux-requests-react/src/index.js @@ -1,6 +1,6 @@ export { default as useQuery } from './use-query'; export { default as useMutation } from './use-mutation'; export { default as useSubscription } from './use-subscription'; -export { default as useDispatchRequest } from './use-dispatch-request'; +export { default as useDispatch } from './use-dispatch'; export { default as RequestsProvider } from './requests-provider'; export { default as RequestErrorBoundary } from './request-error-boundary'; diff --git a/packages/redux-requests-react/src/requests-provider.jsx b/packages/redux-requests-react/src/requests-provider.jsx index 2800e7384..9fb8011f2 100644 --- a/packages/redux-requests-react/src/requests-provider.jsx +++ b/packages/redux-requests-react/src/requests-provider.jsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { createStore, applyMiddleware, combineReducers, compose } from 'redux'; import { Provider } from 'react-redux'; -import { handleRequests, createRequestsStore } from '@redux-requests/core'; +import { handleRequests } from '@redux-requests/core'; import RequestsContext from './requests-context'; @@ -35,7 +35,7 @@ const RequestsProvider = ({ const store = useMemo(() => { if (customStore) { - return createRequestsStore(customStore); + return customStore; } const { requestsReducer, requestsMiddleware } = handleRequests( @@ -52,12 +52,10 @@ const RequestsProvider = ({ window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; - return createRequestsStore( - createStore( - reducers, - initialState, - composeEnhancers(applyMiddleware(...getMiddleware(requestsMiddleware))), - ), + return createStore( + reducers, + initialState, + composeEnhancers(applyMiddleware(...getMiddleware(requestsMiddleware))), ); }, []); diff --git a/packages/redux-requests-react/src/use-dispatch-request.js b/packages/redux-requests-react/src/use-dispatch.js similarity index 100% rename from packages/redux-requests-react/src/use-dispatch-request.js rename to packages/redux-requests-react/src/use-dispatch.js diff --git a/packages/redux-requests-react/src/use-mutation.js b/packages/redux-requests-react/src/use-mutation.js index 5a010a207..267cdec21 100644 --- a/packages/redux-requests-react/src/use-mutation.js +++ b/packages/redux-requests-react/src/use-mutation.js @@ -8,7 +8,7 @@ import { joinRequest, } from '@redux-requests/core'; -import useDispatchRequest from './use-dispatch-request'; +import useDispatch from './use-dispatch'; import RequestsContext from './requests-context'; const emptyVariables = []; @@ -27,27 +27,25 @@ const useMutation = ({ throwError = throwError === undefined ? requestContext.throwError : throwError; - const dispatchRequest = useDispatchRequest(); + const dispatch = useDispatch(); const store = useStore(); const key = `${selectorProps.type}${selectorProps.requestKey || ''}`; const dispatchMutation = useCallback(() => { - return dispatchRequest( - (selectorProps.action || selectorProps.type)(...variables), - ); - }, [selectorProps.action, selectorProps.type, ...variables]); + return dispatch(selectorProps.type(...variables)); + }, [selectorProps.type, ...variables]); const mutation = useSelector(getMutationSelector(selectorProps)); useEffect(() => { - dispatchRequest(addWatcher(key)); + dispatch(addWatcher(key)); return () => { - dispatchRequest(removeWatcher(key)); + dispatch(removeWatcher(key)); if (autoReset && !store.getState().requests.watchers[key]) { - dispatchRequest( + dispatch( resetRequests( [ { @@ -63,7 +61,7 @@ const useMutation = ({ }, [selectorProps.type, selectorProps.requestKey]); if (suspense && mutation.loading) { - throw dispatchRequest(joinRequest(key)); + throw dispatch(joinRequest(key)); } if (throwError && mutation.error) { diff --git a/packages/redux-requests-react/src/use-query.js b/packages/redux-requests-react/src/use-query.js index f1a08304f..1e90e047b 100644 --- a/packages/redux-requests-react/src/use-query.js +++ b/packages/redux-requests-react/src/use-query.js @@ -9,7 +9,7 @@ import { joinRequest, } from '@redux-requests/core'; -import useDispatchRequest from './use-dispatch-request'; +import useDispatch from './use-dispatch'; import RequestsContext from './requests-context'; const emptyVariables = []; @@ -33,19 +33,17 @@ const useQuery = ({ throwError = throwError === undefined ? requestContext.throwError : throwError; - const dispatchRequest = useDispatchRequest(); + const dispatch = useDispatch(); const store = useStore(); const key = `${selectorProps.type}${selectorProps.requestKey || ''}`; const dispatchQuery = useCallback(() => { - return dispatchRequest( - (selectorProps.action || selectorProps.type)(...variables), - ); - }, [selectorProps.action, selectorProps.type, ...variables]); + return dispatch(selectorProps.type(...variables)); + }, [selectorProps.type, ...variables]); const dispatchStopPolling = useCallback(() => { - dispatchRequest( + dispatch( stopPolling([ { requestType: selectorProps.type, @@ -64,13 +62,13 @@ const useQuery = ({ const query = useSelector(getQuerySelector(selectorProps)); useEffect(() => { - dispatchRequest(addWatcher(key)); + dispatch(addWatcher(key)); return () => { - dispatchRequest(removeWatcher(key)); + dispatch(removeWatcher(key)); if (autoReset && !store.getState().requests.watchers[key]) { - dispatchRequest( + dispatch( resetRequests( [ { @@ -91,11 +89,11 @@ const useQuery = ({ throw dispatchQuery(); } - throw dispatchRequest(joinRequest(key, autoLoad)); + throw dispatch(joinRequest(key, autoLoad)); } if (suspense && !suspenseSsr && query.loading) { - throw dispatchRequest(joinRequest(key)); + throw dispatch(joinRequest(key)); } if (throwError && query.error) { diff --git a/packages/redux-requests-react/src/use-subscription.js b/packages/redux-requests-react/src/use-subscription.js index de91c0309..7fffca8eb 100644 --- a/packages/redux-requests-react/src/use-subscription.js +++ b/packages/redux-requests-react/src/use-subscription.js @@ -6,7 +6,7 @@ import { stopSubscriptions, } from '@redux-requests/core'; -import useDispatchRequest from './use-dispatch-request'; +import useDispatch from './use-dispatch'; const emptyVariables = []; @@ -14,28 +14,27 @@ const useSubscriptions = ({ variables = emptyVariables, type, requestKey, - action, active = true, }) => { - const dispatchRequest = useDispatchRequest(); + const dispatch = useDispatch(); const store = useStore(); const key = `${type}${requestKey || ''}`; useEffect(() => { if (active) { - dispatchRequest((action || type)(...variables)); + dispatch(type(...variables)); } - }, [active, action, type, ...variables]); + }, [active, type, ...variables]); useEffect(() => { - dispatchRequest(addWatcher(key)); + dispatch(addWatcher(key)); return () => { - dispatchRequest(removeWatcher(key)); + dispatch(removeWatcher(key)); if (!store.getState().requests.watchers[key]) { - dispatchRequest(stopSubscriptions([key])); + dispatch(stopSubscriptions([key])); } }; }, [type, requestKey]); diff --git a/packages/redux-requests/package.json b/packages/redux-requests/package.json index fa9349170..d2e8b3f79 100644 --- a/packages/redux-requests/package.json +++ b/packages/redux-requests/package.json @@ -57,11 +57,11 @@ "jest-date-mock": "1.0.8", "nodemon": "2.0.6", "npm-run-all": "4.1.5", - "redux": "4.0.5", + "redux": "4.1.0", "redux-mock-store": "1.5.4", "reselect": "4.0.0", "rimraf": "3.0.2", - "typescript": "4.1.2", + "typescript": "4.2.4", "webpack": "5.9.0", "webpack-cli": "4.2.0" }, diff --git a/packages/redux-requests/src/actions.js b/packages/redux-requests/src/actions.js index ab2bca3a4..13aa1f7c3 100644 --- a/packages/redux-requests/src/actions.js +++ b/packages/redux-requests/src/actions.js @@ -32,6 +32,7 @@ export const createSuccessAction = (action, response) => ({ response, meta: { ...action.meta, + requestType: undefined, requestAction: action, }, }); @@ -41,6 +42,7 @@ export const createErrorAction = (action, errorData) => ({ error: errorData, meta: { ...action.meta, + requestType: undefined, requestAction: action, }, }); @@ -49,23 +51,15 @@ export const createAbortAction = action => ({ type: abort(action.type), meta: { ...action.meta, + requestType: undefined, requestAction: action, }, }); export const isRequestAction = action => { return ( - !!action?.request && - !!( - Array.isArray(action.request) || - action.request.url || - action.request.query || - action.request.promise || - action.request.response || - action.request.error - ) && - !action.response && - !(action instanceof Error) + action?.meta?.requestType === 'QUERY' || + action?.meta?.requestType === 'MUTATION' ); }; @@ -82,21 +76,20 @@ export const isErrorAction = action => export const isAbortAction = action => isResponseAction(action) && action.type.endsWith(ABORT_SUFFIX); -const isRequestQuery = request => - (!request.query && - (!request.method || request.method.toLowerCase() === 'get')) || - (request.query && !request.query.trim().startsWith('mutation')); - export const isRequestActionQuery = action => { - const { request } = action; + return action?.meta?.requestType === 'QUERY'; +}; - if (action.meta?.asMutation !== undefined) { - return !action.meta.asMutation; - } +export const isRequestActionMutation = action => { + return action?.meta?.requestType === 'MUTATION'; +}; + +export const isRequestActionLocalMutation = action => { + return action?.meta?.requestType === 'LOCAL_MUTATION'; +}; - return !!(Array.isArray(request) - ? request.every(isRequestQuery) - : isRequestQuery(request)); +export const isRequestActionSubscription = action => { + return action?.meta?.requestType === 'SUBSCRIPTION'; }; export const clearRequestsCache = (requests = null) => ({ diff --git a/packages/redux-requests/src/actions.spec.js b/packages/redux-requests/src/actions.spec.js index cb49af8ef..9c2cb7d68 100644 --- a/packages/redux-requests/src/actions.spec.js +++ b/packages/redux-requests/src/actions.spec.js @@ -1,3 +1,4 @@ +import { createQuery } from './requests-creators'; import { success, error, @@ -36,10 +37,7 @@ describe('actions', () => { describe('createSuccessAction', () => { it('should correctly transform action payload', () => { - const requestAction = { - type: 'REQUEST', - request: { url: '/' }, - }; + const requestAction = createQuery('REQUEST', { url: '/' })(); expect(createSuccessAction(requestAction, { data: 'data' })).toEqual({ type: 'REQUEST_SUCCESS', @@ -142,27 +140,7 @@ describe('actions', () => { describe('isRequestAction', () => { it('recognizes request action', () => { - expect(isRequestAction({ type: 'ACTION', request: { url: '/' } })).toBe( - true, - ); - }); - - it('recognizes request action with multiple requests', () => { - expect( - isRequestAction({ - type: 'ACTION', - request: [{ url: '/' }, { url: '/path' }], - }), - ).toBe(true); - }); - - it('recognizes request action with graphql query', () => { - expect( - isRequestAction({ - type: 'ACTION', - request: { query: '{ x }' }, - }), - ).toBe(true); + expect(isRequestAction(createQuery('ACTION', { url: '/' })())).toBe(true); }); it('rejects actions without request object', () => { @@ -173,42 +151,11 @@ describe('actions', () => { }), ).toBe(false); }); - - it('rejects actions with request without url', () => { - expect( - isRequestAction({ - type: 'ACTION', - request: { headers: {} }, - }), - ).toBe(false); - }); - - it('rejects actions with response object', () => { - expect( - isRequestAction({ - type: 'ACTION', - request: { url: '/' }, - response: {}, - }), - ).toBe(false); - }); - - it('rejects actions with payload which is instance of error', () => { - const responseError = new Error(); - responseError.request = { request: { url: '/' } }; - expect( - isRequestAction({ - type: 'ACTION', - payload: responseError, - response: {}, - }), - ).toBe(false); - }); }); describe('getRequestActionFromResponse', () => { it('should return request action', () => { - const requestAction = { type: 'REQUEST', request: { url: '/' } }; + const requestAction = createQuery('REQUEST', { url: '/' })(); const responseAction = { type: success('REQUEST'), data: 'data', @@ -282,35 +229,8 @@ describe('actions', () => { describe('isRequestActionQuery', () => { it('treats request with GET method as queries', () => { - expect(isRequestActionQuery({ request: { url: '/books' } })).toBe(true); expect( - isRequestActionQuery({ request: { url: '/books', method: 'GET' } }), - ).toBe(true); - }); - - it('treats request with POST method as mutations', () => { - expect( - isRequestActionQuery({ request: { url: '/books', method: 'POST' } }), - ).toBe(false); - }); - - it('treats request with GET method as mutation when asMutation is true', () => { - expect(isRequestActionQuery({ request: { url: '/books' } })).toBe(true); - expect( - isRequestActionQuery({ - request: { url: '/books', method: 'GET' }, - meta: { asMutation: true }, - }), - ).toBe(false); - }); - - it('treats request with POST method as query when asMutation is false', () => { - expect(isRequestActionQuery({ request: { url: '/books' } })).toBe(true); - expect( - isRequestActionQuery({ - request: { url: '/books', method: 'POST' }, - meta: { asMutation: false }, - }), + isRequestActionQuery(createQuery('QUERY', { url: '/books' })()), ).toBe(true); }); }); diff --git a/packages/redux-requests/src/index.js b/packages/redux-requests/src/index.js index a220d5aca..c8e5edc71 100644 --- a/packages/redux-requests/src/index.js +++ b/packages/redux-requests/src/index.js @@ -19,6 +19,7 @@ export { export { createQuery, createMutation, + createLocalMutation, createSubscription, } from './requests-creators'; export { default as handleRequests } from './handle-requests'; @@ -29,4 +30,3 @@ export { getMutationSelector, getWebsocketState, } from './selectors'; -export { createRequestsStore } from './middleware'; diff --git a/packages/redux-requests/src/middleware/create-client-ssr-middleware.js b/packages/redux-requests/src/middleware/create-client-ssr-middleware.js index 43c6ab3c7..0c629cba8 100644 --- a/packages/redux-requests/src/middleware/create-client-ssr-middleware.js +++ b/packages/redux-requests/src/middleware/create-client-ssr-middleware.js @@ -9,7 +9,7 @@ export default (config = defaultConfig) => store => next => action => { const state = store.getState(); const actionsToIgnore = state.requests.ssr; const actionToIgnore = actionsToIgnore.find( - v => (v.requestType || v) === action.type + (action.meta?.requestKey || ''), + v => (v.requestType || v) === action.type + (action.meta.requestKey || ''), ); if (!actionToIgnore) { @@ -18,7 +18,7 @@ export default (config = defaultConfig) => store => next => action => { const query = getQuery(state, { type: action.type, - requestKey: action.meta?.requestKey, + requestKey: action.meta.requestKey, }); action.meta = actionToIgnore.duplicate diff --git a/packages/redux-requests/src/middleware/create-client-ssr-middleware.spec.js b/packages/redux-requests/src/middleware/create-client-ssr-middleware.spec.js index 7dcb8f2af..e54122e62 100644 --- a/packages/redux-requests/src/middleware/create-client-ssr-middleware.spec.js +++ b/packages/redux-requests/src/middleware/create-client-ssr-middleware.spec.js @@ -1,5 +1,7 @@ import configureStore from 'redux-mock-store'; +import { createQuery } from '../requests-creators'; + import { createClientSsrMiddleware } from '.'; describe('middleware', () => { @@ -28,10 +30,10 @@ describe('middleware', () => { ssr: ['REQUEST'], }, }); - const action = { type: 'REQUEST', request: { url: '/' } }; + const action = createQuery('REQUEST', { url: '/' })(); const actionWithSsrResponse = { ...action, - meta: { ssrResponse: { data: 'data' } }, + meta: { ...action.meta, ssrResponse: { data: 'data' } }, }; expect(store.dispatch(action)).toEqual(actionWithSsrResponse); expect(store.getActions()).toEqual([actionWithSsrResponse]); @@ -53,7 +55,7 @@ describe('middleware', () => { ssr: ['REQUEST'], }, }); - const action = { type: 'REQUEST2', request: { url: '/' } }; + const action = createQuery('REQUEST2', { url: '/' })(); expect(store.dispatch(action)).toEqual(action); expect(store.getActions()).toEqual([action]); }); @@ -74,11 +76,11 @@ describe('middleware', () => { ssr: ['REQUEST1'], }, }); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { requestKey: '1' }, - }; + const action = createQuery( + 'REQUEST', + { url: '/' }, + { requestKey: '1' }, + )(); const actionWithSsrResponse = { ...action, meta: { ...action.meta, ssrResponse: { data: 'data' } }, diff --git a/packages/redux-requests/src/middleware/create-polling-middleware.js b/packages/redux-requests/src/middleware/create-polling-middleware.js index 9ee1ddc46..0cebcca6d 100644 --- a/packages/redux-requests/src/middleware/create-polling-middleware.js +++ b/packages/redux-requests/src/middleware/create-polling-middleware.js @@ -1,7 +1,7 @@ import defaultConfig from '../default-config'; import { STOP_POLLING, RESET_REQUESTS } from '../constants'; -const getIntervalKey = action => action.type + (action.meta?.requestKey || ''); +const getIntervalKey = action => action.type + (action.meta.requestKey || ''); const getKeys = requests => requests.map(v => @@ -32,7 +32,7 @@ export default (config = defaultConfig) => { } else if ( config.isRequestAction(action) && config.isRequestActionQuery(action) && - !action.meta?.polled && + !action.meta.polled && intervals[getIntervalKey(action)] ) { const intervalsCopy = { ...intervals }; @@ -44,7 +44,7 @@ export default (config = defaultConfig) => { if ( config.isRequestAction(action) && config.isRequestActionQuery(action) && - action.meta?.poll && + action.meta.poll && !action.meta.polled ) { intervals[getIntervalKey(action)] = setInterval(() => { diff --git a/packages/redux-requests/src/middleware/create-polling-middleware.spec.js b/packages/redux-requests/src/middleware/create-polling-middleware.spec.js index 5bc6b5255..74d0a31e1 100644 --- a/packages/redux-requests/src/middleware/create-polling-middleware.spec.js +++ b/packages/redux-requests/src/middleware/create-polling-middleware.spec.js @@ -1,6 +1,7 @@ import configureStore from 'redux-mock-store'; import { stopPolling, resetRequests } from '../actions'; +import { createQuery } from '../requests-creators'; import createPollingMiddleware from './create-polling-middleware'; @@ -17,7 +18,7 @@ describe('middleware', () => { it('doesnt do anything for not polling requests', () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { type: 'REQUEST', request: { url: '/' } }; + const action = createQuery('REQUEST', { url: '/' })(); expect(store.dispatch(action)).toBe(action); expect(store.getActions()).toEqual([action]); }); @@ -25,11 +26,7 @@ describe('middleware', () => { it('repeats queries when meta poll defined', async () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { poll: 0.1 }, - }; + const action = createQuery('REQUEST', { url: '/' }, { poll: 0.1 })(); store.dispatch(action); await sleep(0.41); expect(store.getActions()).toEqual([ @@ -44,16 +41,8 @@ describe('middleware', () => { it('works with multiple queries types', async () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { poll: 0.1 }, - }; - const action2 = { - type: 'REQUEST2', - request: { url: '/' }, - meta: { poll: 0.2 }, - }; + const action = createQuery('REQUEST', { url: '/' }, { poll: 0.1 })(); + const action2 = createQuery('REQUEST2', { url: '/' }, { poll: 0.2 })(); store.dispatch(action); await sleep(0.01); store.dispatch(action2); @@ -73,11 +62,7 @@ describe('middleware', () => { it('clears interval when query of the same type is dispatched', async () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { poll: 0.1 }, - }; + const action = createQuery('REQUEST', { url: '/' }, { poll: 0.1 })(); const action2 = { ...action, meta: { ...action.meta, poll: undefined } }; store.dispatch(action); await sleep(0.11); @@ -93,11 +78,7 @@ describe('middleware', () => { it('clears all intervals on reset action', async () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { poll: 0.1 }, - }; + const action = createQuery('REQUEST', { url: '/' }, { poll: 0.1 })(); store.dispatch(action); await sleep(0.01); store.dispatch(resetRequests()); @@ -108,11 +89,7 @@ describe('middleware', () => { it('clears all intervals on stopPolling action', async () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { poll: 0.1 }, - }; + const action = createQuery('REQUEST', { url: '/' }, { poll: 0.1 })(); store.dispatch(action); await sleep(0.01); store.dispatch(stopPolling()); @@ -123,16 +100,13 @@ describe('middleware', () => { it('can clear specific intervals only on reset action', async () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { poll: 0.1 }, - }; - const action2 = { - type: 'REQUEST2', - request: { url: '/' }, - meta: { poll: 0.1, requestKey: '1' }, - }; + const action = createQuery('REQUEST', { url: '/' }, { poll: 0.1 })(); + const action2 = createQuery( + 'REQUEST2', + { url: '/' }, + { poll: 0.1, requestKey: '1' }, + )(); + store.dispatch(action); store.dispatch(action2); await sleep(0.01); @@ -151,16 +125,12 @@ describe('middleware', () => { it('can clear specific intervals only on stopPolling action', async () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { poll: 0.1 }, - }; - const action2 = { - type: 'REQUEST2', - request: { url: '/' }, - meta: { poll: 0.1, requestKey: '1' }, - }; + const action = createQuery('REQUEST', { url: '/' }, { poll: 0.1 })(); + const action2 = createQuery( + 'REQUEST2', + { url: '/' }, + { poll: 0.1, requestKey: '1' }, + )(); store.dispatch(action); store.dispatch(action2); await sleep(0.01); @@ -177,16 +147,12 @@ describe('middleware', () => { it('can clear specific intervals only on stopPolling action', async () => { const mockStore = configureStore([createPollingMiddleware()]); const store = mockStore({}); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { poll: 0.1 }, - }; - const action2 = { - type: 'REQUEST2', - request: { url: '/' }, - meta: { poll: 0.1, requestKey: '1' }, - }; + const action = createQuery('REQUEST', { url: '/' }, { poll: 0.1 })(); + const action2 = createQuery( + 'REQUEST2', + { url: '/' }, + { poll: 0.1, requestKey: '1' }, + )(); store.dispatch(action); store.dispatch(action2); await sleep(0.01); diff --git a/packages/redux-requests/src/middleware/create-requests-cache-middleware.js b/packages/redux-requests/src/middleware/create-requests-cache-middleware.js index 97c7f7662..6e90ba997 100644 --- a/packages/redux-requests/src/middleware/create-requests-cache-middleware.js +++ b/packages/redux-requests/src/middleware/create-requests-cache-middleware.js @@ -10,7 +10,7 @@ const getKey = action => action.type + (action.meta.requestKey || ''); export default (config = defaultConfig) => store => next => action => { if ( config.isRequestAction(action) && - action.meta?.cache && + action.meta.cache && !action.meta.ssrResponse ) { const key = getKey(action); @@ -20,7 +20,7 @@ export default (config = defaultConfig) => store => next => action => { if (cacheValue !== undefined && isCacheValid(cacheValue, action)) { const query = getQuery(state, { type: action.type, - requestKey: action.meta?.requestKey, + requestKey: action.meta.requestKey, }); return next({ diff --git a/packages/redux-requests/src/middleware/create-requests-cache-middleware.spec.js b/packages/redux-requests/src/middleware/create-requests-cache-middleware.spec.js index c7cbc2cb7..ab988c079 100644 --- a/packages/redux-requests/src/middleware/create-requests-cache-middleware.spec.js +++ b/packages/redux-requests/src/middleware/create-requests-cache-middleware.spec.js @@ -2,6 +2,7 @@ import configureStore from 'redux-mock-store'; import { advanceBy, advanceTo, clear } from 'jest-date-mock'; import { createSuccessAction } from '../actions'; +import { createQuery } from '../requests-creators'; import { createRequestsCacheMiddleware } from '.'; @@ -19,7 +20,7 @@ describe('middleware', () => { it('doesnt affect request actions with no meta cache', () => { const mockStore = configureStore([createRequestsCacheMiddleware()]); const store = mockStore({}); - const action = { type: 'REQUEST', request: { url: '/' } }; + const action = createQuery('REQUEST', { url: '/' })(); const responseAction = createSuccessAction(action, { data: null }); store.dispatch(action); store.dispatch(responseAction); @@ -36,18 +37,14 @@ describe('middleware', () => { uploadProgress: {}, }, }); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { cache: true }, - }; + const action = createQuery('REQUEST', { url: '/' }, { cache: true })(); store.dispatch(action); expect(store.getActions()).toEqual([ - { - type: 'REQUEST', - request: { url: '/' }, - meta: { cache: true, cacheResponse: { data: 'data' } }, - }, + createQuery( + 'REQUEST', + { url: '/' }, + { cache: true, cacheResponse: { data: 'data' } }, + )(), ]); }); @@ -63,18 +60,14 @@ describe('middleware', () => { uploadProgress: {}, }, }); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { cache: 1 }, - }; + const action = createQuery('REQUEST', { url: '/' }, { cache: 1 })(); store.dispatch(action); expect(store.getActions()).toEqual([ - { - type: 'REQUEST', - request: { url: '/' }, - meta: { cache: 1, cacheResponse: { data: 'data' } }, - }, + createQuery( + 'REQUEST', + { url: '/' }, + { cache: 1, cacheResponse: { data: 'data' } }, + )(), ]); } finally { clear(); @@ -91,11 +84,7 @@ describe('middleware', () => { cache: { REQUEST: { cacheKey: undefined, timeout: Date.now() } }, }, }); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { cache: 1 }, - }; + const action = createQuery('REQUEST', { url: '/' }, { cache: 1 })(); advanceBy(1); store.dispatch(action); expect(store.getActions()).toEqual([action]); @@ -112,19 +101,9 @@ describe('middleware', () => { cache: {}, }, }); - const action = { - type: 'REQUEST', - request: { url: '/' }, - meta: { cache: true }, - }; + const action = createQuery('REQUEST', { url: '/' }, { cache: true })(); store.dispatch(action); - expect(store.getActions()).toEqual([ - { - type: 'REQUEST', - request: { url: '/' }, - meta: { cache: true }, - }, - ]); + expect(store.getActions()).toEqual([action]); }); }); }); diff --git a/packages/redux-requests/src/middleware/create-requests-store.js b/packages/redux-requests/src/middleware/create-requests-store.js deleted file mode 100644 index c6f5ab548..000000000 --- a/packages/redux-requests/src/middleware/create-requests-store.js +++ /dev/null @@ -1,6 +0,0 @@ -export default store => { - return { - ...store, - dispatchRequest: store.dispatch, - }; -}; diff --git a/packages/redux-requests/src/middleware/create-send-requests-middleware.js b/packages/redux-requests/src/middleware/create-send-requests-middleware.js index c0609d686..7bafe6deb 100644 --- a/packages/redux-requests/src/middleware/create-send-requests-middleware.js +++ b/packages/redux-requests/src/middleware/create-send-requests-middleware.js @@ -8,31 +8,25 @@ import { import { ABORT_REQUESTS, RESET_REQUESTS, JOIN_REQUEST } from '../constants'; import { getQuery } from '../selectors'; -import createRequestsStore from './create-requests-store'; - -const getRequestTypeString = requestType => - typeof requestType === 'function' ? requestType.toString() : requestType; - const getKeys = requests => requests.map(v => typeof v === 'object' - ? getRequestTypeString(v.requestType) + (v.requestKey || '') - : getRequestTypeString(v), + ? v.requestType.toString() + (v.requestKey || '') + : v.toString(), ); const getDriver = (config, action) => - action.meta?.driver + action.meta.driver ? config.driver[action.meta.driver] : config.driver.default || config.driver; -const getLastActionKey = action => - action.type + (action.meta?.requestKey ? action.meta.requestKey : ''); +const getLastActionKey = action => action.type + (action.meta.requestKey || ''); const isActionRehydrated = action => !!( - action.meta?.cacheResponse || - action.meta?.ssrResponse || - action.meta?.ssrError + action.meta.cacheResponse || + action.meta.ssrResponse || + action.meta.ssrError ); // TODO: remove to more functional style, we need object maps and filters @@ -52,7 +46,7 @@ const abortPendingRequests = (action, pendingRequests) => { }; const isTakeLatest = (action, config) => - action.meta?.takeLatest !== undefined + action.meta.takeLatest !== undefined ? action.meta.takeLatest : typeof config.takeLatest === 'function' ? config.takeLatest(action) @@ -61,12 +55,12 @@ const isTakeLatest = (action, config) => const maybeCallOnRequestInterceptor = (action, config, store) => { if ( config.onRequest && - (!action.meta || - (action.meta.runOnRequest !== false && !action.meta.ssrDuplicate)) + action.meta.runOnRequest !== false && + !action.meta.ssrDuplicate ) { return { ...action, - request: config.onRequest(action.request, action, store), + payload: config.onRequest(action.payload, action, store), }; } @@ -74,10 +68,10 @@ const maybeCallOnRequestInterceptor = (action, config, store) => { }; const maybeCallOnRequestMeta = (action, store) => { - if (action.meta?.onRequest && !action.meta.ssrDuplicate) { + if (action.meta.onRequest && !action.meta.ssrDuplicate) { return { ...action, - request: action.meta.onRequest(action.request, action, store), + payload: action.meta.onRequest(action.payload, action, store), }; } @@ -85,7 +79,7 @@ const maybeCallOnRequestMeta = (action, store) => { }; const maybeDispatchRequestAction = (action, next) => { - if (!action.meta || !action.meta.silent) { + if (!action.meta.silent) { action = next(action); } @@ -95,21 +89,21 @@ const maybeDispatchRequestAction = (action, next) => { const getDriverActions = (action, store) => { const driverActions = {}; - if (action.meta?.measureDownloadProgress) { + if (action.meta.measureDownloadProgress) { driverActions.setDownloadProgress = progress => store.dispatch( setDownloadProgress( - action.type + (action.meta?.requestKey || ''), + action.type + (action.meta.requestKey || ''), progress, ), ); } - if (action.meta?.measureUploadProgress) { + if (action.meta.measureUploadProgress) { driverActions.setUploadProgress = progress => store.dispatch( setUploadProgress( - action.type + (action.meta?.requestKey || ''), + action.type + (action.meta.requestKey || ''), progress, ), ); @@ -132,13 +126,13 @@ const defer = () => { }; const getResponsePromises = (action, config, pendingRequests, store) => { - const isBatchedRequest = Array.isArray(action.request); + const isBatchedRequest = Array.isArray(action.payload); - if (action.meta?.cacheResponse) { + if (action.meta.cacheResponse) { return [Promise.resolve(action.meta.cacheResponse)]; - } else if (action.meta?.ssrResponse) { + } else if (action.meta.ssrResponse) { return [Promise.resolve(action.meta.ssrResponse)]; - } else if (action.meta?.ssrError) { + } else if (action.meta.ssrError) { return [Promise.reject(action.meta.ssrError)]; } @@ -152,8 +146,8 @@ const getResponsePromises = (action, config, pendingRequests, store) => { } const responsePromises = isBatchedRequest - ? action.request.map(r => driver(r, action, driverActions)) - : [driver(action.request, action, driverActions)]; + ? action.payload.map(r => driver(r, action, driverActions)) + : [driver(action.payload, action, driverActions)]; if (responsePromises[0].cancel) { pendingRequests[lastActionKey] = responsePromises; @@ -166,7 +160,7 @@ const maybeCallGetError = (action, error) => { if ( error !== 'REQUEST_ABORTED' && !isActionRehydrated(action) && - action.meta?.getError + action.meta.getError ) { throw action.meta.getError(error); } @@ -178,8 +172,8 @@ const maybeCallOnErrorInterceptor = (action, config, store, error) => { if ( error !== 'REQUEST_ABORTED' && config.onError && - (!action.meta || - (action.meta.runOnError !== false && !action.meta.ssrDuplicate)) + action.meta.runOnError !== false && + !action.meta.ssrDuplicate ) { return Promise.all([config.onError(error, action, store)]); } @@ -190,7 +184,7 @@ const maybeCallOnErrorInterceptor = (action, config, store, error) => { const maybeCallOnErrorMeta = (action, store, error) => { if ( error !== 'REQUEST_ABORTED' && - action.meta?.onError && + action.meta.onError && !action.meta.ssrDuplicate ) { return Promise.all([action.meta.onError(error, action, store)]); @@ -203,7 +197,7 @@ const maybeCallOnAbortInterceptor = (action, config, store, error) => { if ( error === 'REQUEST_ABORTED' && config.onAbort && - (!action.meta || action.meta.runOnAbort !== false) + action.meta.runOnAbort !== false ) { config.onAbort(action, store); } @@ -212,7 +206,7 @@ const maybeCallOnAbortInterceptor = (action, config, store, error) => { }; const maybeCallOnAbortMeta = (action, store, error) => { - if (error === 'REQUEST_ABORTED' && action.meta?.onAbort) { + if (error === 'REQUEST_ABORTED' && action.meta.onAbort) { action.meta.onAbort(action, store); } @@ -226,7 +220,7 @@ const getInitialBatchObject = responseKeys => }, {}); const maybeTransformBatchRequestResponse = (action, response) => { - const isBatchedRequest = Array.isArray(action.request); + const isBatchedRequest = Array.isArray(action.payload); const responseKeys = Object.keys(response[0]); return isBatchedRequest && !isActionRehydrated(action) @@ -240,10 +234,10 @@ const maybeTransformBatchRequestResponse = (action, response) => { }; const maybeCallGetData = (action, store, response) => { - if (!isActionRehydrated(action) && action.meta?.getData) { + if (!isActionRehydrated(action) && action.meta.getData) { const query = getQuery(store.getState(), { type: action.type, - requestKey: action.meta?.requestKey, + requestKey: action.meta.requestKey, }); return { @@ -258,8 +252,8 @@ const maybeCallGetData = (action, store, response) => { const maybeCallOnSuccessInterceptor = (action, config, store, response) => { if ( config.onSuccess && - (!action.meta || - (action.meta.runOnSuccess !== false && !action.meta.ssrDuplicate)) + action.meta.runOnSuccess !== false && + !action.meta.ssrDuplicate ) { const result = config.onSuccess(response, action, store); @@ -272,7 +266,7 @@ const maybeCallOnSuccessInterceptor = (action, config, store, response) => { }; const maybeCallOnSuccessMeta = (action, store, response) => { - if (action.meta?.onSuccess && !action.meta.ssrDuplicate) { + if (action.meta.onSuccess && !action.meta.ssrDuplicate) { const result = action.meta.onSuccess(response, action, store); if (!isActionRehydrated(action)) { @@ -291,8 +285,6 @@ const createSendRequestMiddleware = config => { const allPendingRequests = {}; // for joining return store => next => action => { - const requestsStore = createRequestsStore(store); - if (action.type === JOIN_REQUEST) { next(action); return allPendingRequests[action.requestType] || sleep(); @@ -310,8 +302,8 @@ const createSendRequestMiddleware = config => { const lastActionKey = getLastActionKey(action); allPendingRequests[lastActionKey] = defer(); - action = maybeCallOnRequestInterceptor(action, config, requestsStore); - action = maybeCallOnRequestMeta(action, requestsStore); + action = maybeCallOnRequestInterceptor(action, config, store); + action = maybeCallOnRequestMeta(action, store); action = maybeDispatchRequestAction(action, next); const responsePromises = getResponsePromises( @@ -324,18 +316,18 @@ const createSendRequestMiddleware = config => { return Promise.all(responsePromises) .catch(error => maybeCallGetError(action, error)) .catch(error => - maybeCallOnErrorInterceptor(action, config, requestsStore, error), + maybeCallOnErrorInterceptor(action, config, store, error), ) - .catch(error => maybeCallOnErrorMeta(action, requestsStore, error)) + .catch(error => maybeCallOnErrorMeta(action, store, error)) .catch(error => - maybeCallOnAbortInterceptor(action, config, requestsStore, error), + maybeCallOnAbortInterceptor(action, config, store, error), ) - .catch(error => maybeCallOnAbortMeta(action, requestsStore, error)) + .catch(error => maybeCallOnAbortMeta(action, store, error)) .catch(error => { if (error === 'REQUEST_ABORTED') { const abortAction = createAbortAction(action); - if (!action.meta || !action.meta.silent) { + if (!action.meta.silent) { store.dispatch(abortAction); } @@ -349,7 +341,7 @@ const createSendRequestMiddleware = config => { const errorAction = createErrorAction(action, error); - if (!action.meta || !action.meta.silent) { + if (!action.meta.silent) { store.dispatch(errorAction); } @@ -365,20 +357,13 @@ const createSendRequestMiddleware = config => { return maybeCallGetData(action, store, response); }) .then(response => - maybeCallOnSuccessInterceptor( - action, - config, - requestsStore, - response, - ), - ) - .then(response => - maybeCallOnSuccessMeta(action, requestsStore, response), + maybeCallOnSuccessInterceptor(action, config, store, response), ) + .then(response => maybeCallOnSuccessMeta(action, store, response)) .then(response => { const successAction = createSuccessAction(action, response); - if (!action.meta || !action.meta.silent) { + if (!action.meta.silent) { store.dispatch(successAction); } diff --git a/packages/redux-requests/src/middleware/create-send-requests-middleware.spec.js b/packages/redux-requests/src/middleware/create-send-requests-middleware.spec.js index feb609be6..f7007f7e7 100644 --- a/packages/redux-requests/src/middleware/create-send-requests-middleware.spec.js +++ b/packages/redux-requests/src/middleware/create-send-requests-middleware.spec.js @@ -7,6 +7,7 @@ import { createAbortAction, abortRequests, } from '../actions'; +import { createQuery } from '../requests-creators'; import { createSendRequestsMiddleware } from '.'; @@ -36,7 +37,6 @@ describe('middleware', () => { const testConfig = { ...defaultConfig, driver: dummyDriver, - isRequestAction: action => !!action.request, }; const mockStore = configureStore([createSendRequestsMiddleware(testConfig)]); @@ -50,26 +50,27 @@ describe('middleware', () => { }); it('dispatches requests and resolves on success', async () => { - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - }; + const requestAction = createQuery('REQUEST', { + response: { data: 'data' }, + })(); const { dispatch, getActions } = mockStore({}); const successAction = createSuccessAction(requestAction, { data: 'data', }); + const result = await dispatch(requestAction); + expect(result).toEqual({ action: successAction, data: 'data' }); expect(getActions()).toEqual([requestAction, successAction]); }); it('resolves on success but doesnt dispatch in silent mode', async () => { - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - meta: { silent: true }, - }; + const requestAction = createQuery( + 'REQUEST', + { response: { data: 'data' } }, + { silent: true }, + )(); const { dispatch, getActions } = mockStore({}); const successAction = createSuccessAction(requestAction, { @@ -81,13 +82,10 @@ describe('middleware', () => { }); it('dispatches requests and resolves on success for batch request', async () => { - const requestAction = { - type: 'REQUEST', - request: [ - { response: { data: 'data1' } }, - { response: { data: 'data2' } }, - ], - }; + const requestAction = createQuery('REQUEST', [ + { response: { data: 'data1' } }, + { response: { data: 'data2' } }, + ])(); const { dispatch, getActions } = mockStore({}); const successAction = createSuccessAction(requestAction, { @@ -102,11 +100,11 @@ describe('middleware', () => { }); it('dispatches requests and resolves on success for cache response', async () => { - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - meta: { cacheResponse: { data: 'data cached' } }, - }; + const requestAction = createQuery( + 'REQUEST', + { response: { data: 'data' } }, + { cacheResponse: { data: 'data cached' } }, + )(); const { dispatch, getActions } = mockStore({}); const successAction = createSuccessAction(requestAction, { @@ -118,11 +116,11 @@ describe('middleware', () => { }); it('dispatches requests and resolves on success for ssr response', async () => { - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - meta: { ssrResponse: { data: 'data ssr' } }, - }; + const requestAction = createQuery( + 'REQUEST', + { response: { data: 'data' } }, + { ssrResponse: { data: 'data ssr' } }, + )(); const { dispatch, getActions } = mockStore({}); const successAction = createSuccessAction(requestAction, { @@ -134,10 +132,7 @@ describe('middleware', () => { }); it('dispatches requests and resolves on error', async () => { - const requestAction = { - type: 'REQUEST', - request: { error: 'error' }, - }; + const requestAction = createQuery('REQUEST', { error: 'error' })(); const { dispatch, getActions } = mockStore({}); const errorAction = createErrorAction(requestAction, 'error'); @@ -150,11 +145,11 @@ describe('middleware', () => { }); it('resolves on error but doesnt dispatch in silent mode', async () => { - const requestAction = { - type: 'REQUEST', - request: { error: 'error' }, - meta: { silent: true }, - }; + const requestAction = createQuery( + 'REQUEST', + { error: 'error' }, + { silent: true }, + )(); const { dispatch, getActions } = mockStore({}); const errorAction = createErrorAction(requestAction, 'error'); @@ -167,11 +162,11 @@ describe('middleware', () => { }); it('dispatches requests and resolves on abort', async () => { - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - meta: { takeLatest: true }, - }; + const requestAction = createQuery( + 'REQUEST', + { response: { data: 'data' } }, + { takeLatest: true }, + )(); const { dispatch, getActions } = mockStore({}); const successAction = createSuccessAction(requestAction, { @@ -197,10 +192,9 @@ describe('middleware', () => { }); it('aborts all requests on abortRequests action', async () => { - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - }; + const requestAction = createQuery('REQUEST', { + response: { data: 'data' }, + })(); const { dispatch, getActions } = mockStore({}); const abortAction = createAbortAction(requestAction); @@ -225,19 +219,17 @@ describe('middleware', () => { }); it('aborts specific requests on abortRequests action', async () => { - const requestAction1 = { - type: 'REQUEST1', - request: { response: { data: 'data' } }, - }; - const requestAction2 = { - type: 'REQUEST2', - request: { response: { data: 'data' } }, - }; - const requestAction3 = { - type: 'REQUEST3', - request: { response: { data: 'data' } }, - meta: { requestKey: '1' }, - }; + const requestAction1 = createQuery('REQUEST1', { + response: { data: 'data' }, + })(); + const requestAction2 = createQuery('REQUEST2', { + response: { data: 'data' }, + })(); + const requestAction3 = createQuery( + 'REQUEST3', + { response: { data: 'data' } }, + { requestKey: '1' }, + )(); const { dispatch, getActions } = mockStore({}); const responseAction1 = createSuccessAction(requestAction1, { @@ -286,14 +278,12 @@ describe('middleware', () => { }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - }; - const requestActionUpdated = { - type: 'REQUEST', - request: { response: { data: 'dataUpdated' } }, - }; + const requestAction = createQuery('REQUEST', { + response: { data: 'data' }, + })(); + const requestActionUpdated = createQuery('REQUEST', { + response: { data: 'dataUpdated' }, + })(); const { dispatch, getActions } = onRequestMockStore({}); const successAction = createSuccessAction(requestActionUpdated, { @@ -318,11 +308,11 @@ describe('middleware', () => { }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - meta: { runOnRequest: false }, - }; + const requestAction = createQuery( + 'REQUEST', + { response: { data: 'data' } }, + { runOnRequest: false }, + )(); const { dispatch, getActions } = onRequestMockStore({}); const successAction = createSuccessAction(requestAction, { @@ -343,10 +333,9 @@ describe('middleware', () => { }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - }; + const requestAction = createQuery('REQUEST', { + response: { data: 'data' }, + })(); const { dispatch, getActions } = onSuccessMockStore({}); const successAction = createSuccessAction(requestAction, { data: 'dataUpdated', @@ -370,11 +359,11 @@ describe('middleware', () => { }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - meta: { runOnSuccess: false }, - }; + const requestAction = createQuery( + 'REQUEST', + { response: { data: 'data' } }, + { runOnSuccess: false }, + )(); const { dispatch, getActions } = onSuccessMockStore({}); const successAction = createSuccessAction(requestAction, { data: 'data', @@ -394,10 +383,7 @@ describe('middleware', () => { }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { error: 'error' }, - }; + const requestAction = createQuery('REQUEST', { error: 'error' })(); const { dispatch, getActions } = onErrorMockStore({}); const errorAction = createErrorAction(requestAction, 'errorUpdated'); const result = dispatch(requestAction); @@ -422,11 +408,11 @@ describe('middleware', () => { }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { error: 'error' }, - meta: { runOnError: false }, - }; + const requestAction = createQuery( + 'REQUEST', + { error: 'error' }, + { runOnError: false }, + )(); const { dispatch, getActions } = onErrorMockStore({}); const errorAction = createErrorAction(requestAction, 'error'); const result = dispatch(requestAction); @@ -442,20 +428,19 @@ describe('middleware', () => { createSendRequestsMiddleware({ ...testConfig, onError: async (error, action, store) => { - const { data } = await store.dispatch({ - type: 'REQUEST', - request: { response: { data: 'data' } }, - meta: { silent: true }, - }); + const { data } = await store.dispatch( + createQuery( + 'REQUEST', + { response: { data: 'data' } }, + { silent: true }, + )(), + ); return { data }; }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { error: 'error' }, - }; + const requestAction = createQuery('REQUEST', { error: 'error' })(); const { dispatch, getActions } = onErrorMockStore({}); const successAction = createSuccessAction(requestAction, { data: 'data', @@ -477,10 +462,7 @@ describe('middleware', () => { }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { error: 'error' }, - }; + const requestAction = createQuery('REQUEST', { error: 'error' })(); const { dispatch, getActions } = onErrorMockStore({}); const abortAction = createAbortAction(requestAction); const result = dispatch(requestAction); @@ -506,11 +488,11 @@ describe('middleware', () => { }, }), ]); - const requestAction = { - type: 'REQUEST', - request: { error: 'error' }, - meta: { runOnAbort: false }, - }; + const requestAction = createQuery( + 'REQUEST', + { error: 'error' }, + { runOnAbort: false }, + )(); const { dispatch, getActions } = onErrorMockStore({}); const abortAction = createAbortAction(requestAction); const result = dispatch(requestAction); @@ -527,13 +509,13 @@ describe('middleware', () => { }); it('dispatches requests and rejects on success but with getData syntax error', async () => { - const requestAction = { - type: 'REQUEST', - request: { response: { data: 'data' } }, - meta: { + const requestAction = createQuery( + 'REQUEST', + { response: { data: 'data' } }, + { getData: data => data.map(v => v), // error }, - }; + )(); const { dispatch, getActions } = mockStore({ requests: { queries: {}, mutations: {} }, diff --git a/packages/redux-requests/src/middleware/create-server-ssr-middleware.js b/packages/redux-requests/src/middleware/create-server-ssr-middleware.js index 23dccb224..780c8f5c6 100644 --- a/packages/redux-requests/src/middleware/create-server-ssr-middleware.js +++ b/packages/redux-requests/src/middleware/create-server-ssr-middleware.js @@ -9,7 +9,7 @@ export default (requestsPromise, config = defaultConfig) => { return () => next => action => { if (config.isRequestAction(action)) { index += - action.meta?.dependentRequestsNumber !== undefined + action.meta.dependentRequestsNumber !== undefined ? action.meta.dependentRequestsNumber + 1 : 1; } else if (isResponseAction(action)) { diff --git a/packages/redux-requests/src/middleware/create-server-ssr-middleware.spec.js b/packages/redux-requests/src/middleware/create-server-ssr-middleware.spec.js index ea364ac2f..7f0599db9 100644 --- a/packages/redux-requests/src/middleware/create-server-ssr-middleware.spec.js +++ b/packages/redux-requests/src/middleware/create-server-ssr-middleware.spec.js @@ -1,6 +1,7 @@ import configureStore from 'redux-mock-store'; import { createSuccessAction, createErrorAction } from '../actions'; +import { createQuery } from '../requests-creators'; import { createServerSsrMiddleware } from '.'; @@ -18,7 +19,7 @@ const defer = () => { describe('middleware', () => { describe('createServerSsrMiddleware', () => { - const requestAction = { type: 'REQUEST', request: { url: '/' } }; + const requestAction = createQuery('REQUEST', { url: '/' })(); const successAction = createSuccessAction(requestAction, 'data'); const errorAction = createErrorAction(requestAction, 'error'); @@ -72,21 +73,21 @@ describe('middleware', () => { createServerSsrMiddleware(requestsPromise), ]); const store = mockStore({}); - const firstRequestAction = { - type: 'REQUEST', - request: { url: '/' }, - meta: { dependentRequestsNumber: 2 }, - }; - const secondRequestAction = { - type: 'REQUEST2', - request: { url: '/' }, - meta: { isDependentRequest: true }, - }; - const thirdRequestAction = { - type: 'REQUEST3', - request: { url: '/' }, - meta: { isDependentRequest: true }, - }; + const firstRequestAction = createQuery( + 'REQUEST', + { url: '/' }, + { dependentRequestsNumber: 2 }, + )(); + const secondRequestAction = createQuery( + 'REQUEST2', + { url: '/' }, + { isDependentRequest: true }, + )(); + const thirdRequestAction = createQuery( + 'REQUEST3', + { url: '/' }, + { isDependentRequest: true }, + )(); const firsResponseAction = createSuccessAction(firstRequestAction); const secondResponseAction = createSuccessAction(secondRequestAction); const thirdResponseAction = createSuccessAction(thirdRequestAction); @@ -117,11 +118,11 @@ describe('middleware', () => { createServerSsrMiddleware(requestsPromise), ]); const store = mockStore({}); - const firstRequestAction = { - type: 'REQUEST', - request: { url: '/' }, - meta: { dependentRequestsNumber: 2 }, - }; + const firstRequestAction = createQuery( + 'REQUEST', + { url: '/' }, + { dependentRequestsNumber: 2 }, + )(); // const secondRequestAction = { // type: 'REQUEST2', // request: { url: '/' }, @@ -161,21 +162,21 @@ describe('middleware', () => { createServerSsrMiddleware(requestsPromise), ]); const store = mockStore({}); - const firstRequestAction = { - type: 'REQUEST', - request: { url: '/' }, - meta: { dependentRequestsNumber: 2 }, - }; - const secondRequestAction = { - type: 'REQUEST2', - request: { url: '/' }, - meta: { isDependentRequest: true }, - }; - const thirdRequestAction = { - type: 'REQUEST3', - request: { url: '/' }, - meta: { isDependentRequest: true }, - }; + const firstRequestAction = createQuery( + 'REQUEST', + { url: '/' }, + { dependentRequestsNumber: 2 }, + )(); + const secondRequestAction = createQuery( + 'REQUEST2', + { url: '/' }, + { isDependentRequest: true }, + )(); + const thirdRequestAction = createQuery( + 'REQUEST3', + { url: '/' }, + { isDependentRequest: true }, + )(); const firsResponseAction = createSuccessAction(firstRequestAction); const secondResponseAction = createSuccessAction(secondRequestAction); const thirdErrorAction = createErrorAction(thirdRequestAction); @@ -205,16 +206,16 @@ describe('middleware', () => { createServerSsrMiddleware(requestsPromise), ]); const store = mockStore({}); - const firstRequestAction = { - type: 'REQUEST', - request: { url: '/' }, - meta: { dependentRequestsNumber: 1 }, - }; - const secondRequestAction = { - type: 'REQUEST2', - request: { url: '/' }, - meta: { isDependentRequest: true, dependentRequestsNumber: 1 }, - }; + const firstRequestAction = createQuery( + 'REQUEST', + { url: '/' }, + { dependentRequestsNumber: 1 }, + )(); + const secondRequestAction = createQuery( + 'REQUEST2', + { url: '/' }, + { isDependentRequest: true, dependentRequestsNumber: 1 }, + )(); // const thirdRequestAction = { // type: 'REQUEST3', // request: { url: '/' }, diff --git a/packages/redux-requests/src/middleware/create-subscriptions-middleware.js b/packages/redux-requests/src/middleware/create-subscriptions-middleware.js index c10fd67ae..955f0f4e0 100644 --- a/packages/redux-requests/src/middleware/create-subscriptions-middleware.js +++ b/packages/redux-requests/src/middleware/create-subscriptions-middleware.js @@ -4,6 +4,7 @@ import { websocketClosed, openWebsocket, closeWebsocket, + isRequestActionSubscription, } from '../actions'; import { GET_WEBSOCKET, @@ -12,13 +13,10 @@ import { CLOSE_WEBSOCKET, WEBSOCKET_OPENED, } from '../constants'; - -import createRequestsStore from './create-requests-store'; +import { createLocalMutation } from '../requests-creators'; const shouldBeNormalized = (action, globalNormalize) => - action.meta?.normalize !== undefined - ? action.meta.normalize - : globalNormalize; + action.meta.normalize !== undefined ? action.meta.normalize : globalNormalize; const transformIntoLocalMutation = ( subscriptionAction, @@ -33,16 +31,13 @@ const transformIntoLocalMutation = ( } if (subscriptionAction.meta?.mutations) { - meta.mutations = mapObject(subscriptionAction.meta.mutations, (k, v) => ({ - local: true, - updateData: data => v(data, subscriptionData, message), - })); + meta.mutations = mapObject( + subscriptionAction.meta.mutations, + (k, v) => data => v(data, subscriptionData, message), + ); } - return { - type: `${subscriptionAction.type}_MUTATION`, - meta, - }; + return createLocalMutation(`${subscriptionAction.type}_MUTATION`, meta)(); }; /* @@ -177,8 +172,6 @@ export default ({ } if ((!ws && WS && url && !lazy) || action.type === OPEN_WEBSOCKET) { - const requestsStore = createRequestsStore(store); - clearLastReconnectTimeout(); clearLastHeartbeatTimeout(); @@ -196,7 +189,7 @@ export default ({ if (onOpen) { onOpen( - requestsStore, + store, ws, action.type === OPEN_WEBSOCKET ? action.props : null, ); @@ -213,7 +206,7 @@ export default ({ ws.addEventListener('error', e => { if (onError) { - onError(e, requestsStore, ws); + onError(e, store, ws); } }); @@ -224,7 +217,7 @@ export default ({ clearLastHeartbeatTimeout(); if (onClose) { - onClose(e, requestsStore, ws); + onClose(e, store, ws); } if (e.code !== 1000 && reconnectTimeout) { @@ -255,22 +248,22 @@ export default ({ } if (onMessage) { - onMessage(data, message, requestsStore); + onMessage(data, message, store); } const subscription = subscriptions[data.type]; if (subscription) { - if (subscription.meta?.getData) { + if (subscription.meta.getData) { data = subscription.meta.getData(data); } - if (subscription.meta?.onMessage) { - subscription.meta.onMessage(data, message, requestsStore); + if (subscription.meta.onMessage) { + subscription.meta.onMessage(data, message, store); } if ( - subscription.meta?.mutations || + subscription.meta.mutations || shouldBeNormalized(subscription, normalize) ) { store.dispatch( @@ -297,57 +290,52 @@ export default ({ ws.close(action.code); ws = null; return response; - } else if (action.type === WEBSOCKET_OPENED) { + } + + if (action.type === WEBSOCKET_OPENED) { Object.values(subscriptions).forEach(subscriptionAction => { - if (subscriptionAction.subscription) { + if (subscriptionAction.payload) { ws.send( JSON.stringify( onSend - ? onSend(subscriptionAction.subscription, subscriptionAction) - : subscriptionAction.subscription, + ? onSend(subscriptionAction.payload, subscriptionAction) + : subscriptionAction.payload, ), ); } }); } else if (action.type === STOP_SUBSCRIPTIONS) { - const requestsStore = createRequestsStore(store); - if (!action.subscriptions) { if (onStopSubscriptions) { - onStopSubscriptions( - Object.keys(subscriptions), - action, - ws, - requestsStore, - ); + onStopSubscriptions(Object.keys(subscriptions), action, ws, store); } subscriptions = {}; } else { if (onStopSubscriptions) { - onStopSubscriptions(action.subscriptions, action, ws, requestsStore); + onStopSubscriptions(action.subscriptions, action, ws, store); } subscriptions = mapObject(subscriptions, (k, v) => action.subscriptions.includes(k) ? undefined : v, ); } - } else if (action.subscription !== undefined) { + } else if (isRequestActionSubscription(action)) { if ( - action.meta?.onMessage || - action.meta?.mutations || + action.meta.onMessage || + action.meta.mutations || shouldBeNormalized(action, normalize) ) { subscriptions = { ...subscriptions, - [action.type + (action.meta?.requestKey || '')]: action, + [action.type + (action.meta.requestKey || '')]: action, }; } - if (action.subscription && ws && active) { + if (action.payload && ws && active) { ws.send( JSON.stringify( - onSend ? onSend(action.subscription, action) : action.subscription, + onSend ? onSend(action.payload, action) : action.payload, ), ); } diff --git a/packages/redux-requests/src/middleware/create-subscriptions-middleware.spec.js b/packages/redux-requests/src/middleware/create-subscriptions-middleware.spec.js index 9142895a2..8aabba55b 100644 --- a/packages/redux-requests/src/middleware/create-subscriptions-middleware.spec.js +++ b/packages/redux-requests/src/middleware/create-subscriptions-middleware.spec.js @@ -1,6 +1,7 @@ import configureStore from 'redux-mock-store'; import { websocketOpened, getWebsocket, websocketClosed } from '../actions'; +import { createQuery, createSubscription } from '../requests-creators'; import createSubscriptionsMiddleware from './create-subscriptions-middleware'; @@ -74,7 +75,7 @@ describe('middleware', () => { }), ]); const store = mockStore({}); - const action = { type: 'REQUEST', request: { url: '/' } }; + const action = createQuery('REQUEST', { url: '/' })(); expect(store.dispatch(action)).toBe(action); expect(store.getActions()).toEqual([websocketOpened(), action]); }); @@ -138,13 +139,9 @@ describe('middleware', () => { const store = mockStore({}); const ws = store.dispatch(getWebsocket()); - const subscription = { - type: 'SUBSCRIPTION2', - subscription: null, - meta: { - onMessage: jest.fn(), - }, - }; + const subscription = createSubscription('SUBSCRIPTION2', null, { + onMessage: jest.fn(), + })(); store.dispatch(subscription); ws.sendToClient({ type: 'SUBSCRIPTION' }); @@ -165,13 +162,9 @@ describe('middleware', () => { const store = mockStore({}); const ws = store.dispatch(getWebsocket()); - const subscription = { - type: 'SUBSCRIPTION', - subscription: null, - meta: { - onMessage: jest.fn(), - }, - }; + const subscription = createSubscription('SUBSCRIPTION', null, { + onMessage: jest.fn(), + })(); store.dispatch(subscription); ws.sendToClient({ type: 'SUBSCRIPTION' }); @@ -201,14 +194,10 @@ describe('middleware', () => { const store = mockStore({}); const ws = store.dispatch(getWebsocket()); - const subscription = { - type: 'SUBSCRIPTION', - subscription: null, - meta: { - getData: data => data.type, - onMessage: jest.fn(), - }, - }; + const subscription = createSubscription('SUBSCRIPTION', null, { + getData: data => data.type, + onMessage: jest.fn(), + })(); store.dispatch(subscription); ws.sendToClient({ type: 'SUBSCRIPTION' }); @@ -235,17 +224,13 @@ describe('middleware', () => { const store = mockStore({}); const ws = store.dispatch(getWebsocket()); - const onBookAdded = { - type: 'ON_BOOK_ADDED', - subscription: null, - meta: { - normalize: true, - mutations: { - FETCH_BOOK: (data, subscriptionData) => - data.concat(subscriptionData.newBook), - }, + const onBookAdded = createSubscription('ON_BOOK_ADDED', null, { + normalize: true, + mutations: { + FETCH_BOOK: (data, subscriptionData) => + data.concat(subscriptionData.newBook), }, - }; + })(); store.dispatch(onBookAdded); ws.sendToClient({ type: 'ON_BOOK_ADDED', newBook: 'New book' }); @@ -259,9 +244,9 @@ describe('middleware', () => { type: 'ON_BOOK_ADDED', newBook: 'New book', }); - expect(dispatchedActions[2].meta.mutations.FETCH_BOOK.local).toBe(true); + expect(dispatchedActions[2].meta.requestType).toBe('LOCAL_MUTATION'); expect( - dispatchedActions[2].meta.mutations.FETCH_BOOK.updateData(['Old book']), + dispatchedActions[2].meta.mutations.FETCH_BOOK(['Old book']), ).toEqual(['Old book', 'New book']); }); @@ -277,10 +262,9 @@ describe('middleware', () => { const store = mockStore({}); const ws = store.dispatch(getWebsocket()); - const subscription = { + const subscription = createSubscription('SUBSCRIPTION', { type: 'SUBSCRIPTION', - subscription: { type: 'SUBSCRIPTION' }, - }; + })(); store.dispatch(subscription); ws.sendToClient({ type: 'SUBSCRIPTION' }); diff --git a/packages/redux-requests/src/middleware/index.js b/packages/redux-requests/src/middleware/index.js index 7db814cda..5104426d8 100644 --- a/packages/redux-requests/src/middleware/index.js +++ b/packages/redux-requests/src/middleware/index.js @@ -4,4 +4,3 @@ export { default as createServerSsrMiddleware } from './create-server-ssr-middle export { default as createSendRequestsMiddleware } from './create-send-requests-middleware'; export { default as createPollingMiddleware } from './create-polling-middleware'; export { default as createSubscriptionsMiddleware } from './create-subscriptions-middleware'; -export { default as createRequestsStore } from './create-requests-store'; diff --git a/packages/redux-requests/src/reducers/cache-reducer.js b/packages/redux-requests/src/reducers/cache-reducer.js index b169c61da..b6cae72b0 100644 --- a/packages/redux-requests/src/reducers/cache-reducer.js +++ b/packages/redux-requests/src/reducers/cache-reducer.js @@ -6,14 +6,11 @@ const getNewCacheTimeout = cache => const getRequestKey = action => action.type + (action.meta.requestKey || ''); -const getRequestTypeString = requestType => - typeof requestType === 'function' ? requestType.toString() : requestType; - const getRequestKeys = requests => requests.map(v => typeof v === 'object' - ? getRequestTypeString(v.requestType) + v.requestKey - : getRequestTypeString(v), + ? v.requestType.toString() + v.requestKey + : v.toString(), ); export default (state, action) => { diff --git a/packages/redux-requests/src/reducers/cache-reducer.spec.js b/packages/redux-requests/src/reducers/cache-reducer.spec.js index 5f0fecbf3..d289563d5 100644 --- a/packages/redux-requests/src/reducers/cache-reducer.spec.js +++ b/packages/redux-requests/src/reducers/cache-reducer.spec.js @@ -1,6 +1,7 @@ import { advanceTo, clear } from 'jest-date-mock'; import { clearRequestsCache, createSuccessAction } from '../actions'; +import { createQuery } from '../requests-creators'; import cacheReducer from './cache-reducer'; @@ -34,7 +35,7 @@ describe('reducers', () => { it('doesnt do anything for request action', () => { expect( - cacheReducer(defaultState, { type: 'REQUEST', request: { url: '/' } }), + cacheReducer(defaultState, createQuery('REQUEST', { url: '/' })()), ).toBe(defaultState); }); @@ -42,10 +43,9 @@ describe('reducers', () => { expect( cacheReducer( defaultState, - createSuccessAction( - { type: 'QUERY4', request: { url: '/' } }, - { data: 'data' }, - ), + createSuccessAction(createQuery('QUERY4', { url: '/' })(), { + data: 'data', + }), ), ).toBe(defaultState); }); @@ -55,11 +55,11 @@ describe('reducers', () => { cacheReducer( defaultState, createSuccessAction( - { - type: 'QUERY4', - request: { url: '/' }, - meta: { cache: true, cacheResponse: { data: 'data' } }, - }, + createQuery( + 'QUERY4', + { url: '/' }, + { cache: true, cacheResponse: { data: 'data' } }, + )(), { data: 'data' }, ), ), @@ -71,7 +71,7 @@ describe('reducers', () => { cacheReducer( defaultState, createSuccessAction( - { type: 'QUERY4', request: { url: '/' }, meta: { cache: true } }, + createQuery('QUERY4', { url: '/' }, { cache: true })(), { data: 'data' }, ), ), @@ -88,7 +88,7 @@ describe('reducers', () => { cacheReducer( defaultState, createSuccessAction( - { type: 'QUERY4', request: { url: '/' }, meta: { cache: 1 } }, + createQuery('QUERY4', { url: '/' }, { cache: 1 })(), { data: 'data' }, ), ), diff --git a/packages/redux-requests/src/reducers/mutations-reducer.js b/packages/redux-requests/src/reducers/mutations-reducer.js index 45ad5d551..81b31720f 100644 --- a/packages/redux-requests/src/reducers/mutations-reducer.js +++ b/packages/redux-requests/src/reducers/mutations-reducer.js @@ -3,12 +3,12 @@ import { isAbortAction, isResponseAction, getRequestActionFromResponse, + isRequestActionMutation, } from '../actions'; -export default (state, action) => { - if (!isResponseAction(action)) { - const mutationType = - action.type + (action.meta?.requestKey ? action.meta.requestKey : ''); +export default (state, action, config) => { + if (config.isRequestAction(action) && isRequestActionMutation(action)) { + const mutationType = action.type + (action.meta.requestKey || ''); return { ...state, @@ -20,36 +20,41 @@ export default (state, action) => { }; } - const requestAction = getRequestActionFromResponse(action); - const mutationType = - requestAction.type + - (action.meta?.requestKey ? action.meta.requestKey : ''); + if ( + isResponseAction(action) && + isRequestActionMutation(getRequestActionFromResponse(action)) + ) { + const requestAction = getRequestActionFromResponse(action); + const mutationType = requestAction.type + (action.meta.requestKey || ''); + + if (isErrorAction(action)) { + return { + ...state, + [mutationType]: { + error: action.error, + pending: state[mutationType].pending - 1, + ref: state[mutationType].ref, + }, + }; + } + + if ( + isAbortAction(action) && + state[mutationType].pending === 1 && + state[mutationType].error === null + ) { + return state; + } - if (isErrorAction(action)) { return { ...state, [mutationType]: { - error: action.error, + error: null, pending: state[mutationType].pending - 1, ref: state[mutationType].ref, }, }; } - if ( - isAbortAction(action) && - state[mutationType].pending === 1 && - state[mutationType].error === null - ) { - return state; - } - - return { - ...state, - [mutationType]: { - error: null, - pending: state[mutationType].pending - 1, - ref: state[mutationType].ref, - }, - }; + return state; }; diff --git a/packages/redux-requests/src/reducers/progress-reducer.js b/packages/redux-requests/src/reducers/progress-reducer.js index 1c260bae9..02a2cd351 100644 --- a/packages/redux-requests/src/reducers/progress-reducer.js +++ b/packages/redux-requests/src/reducers/progress-reducer.js @@ -21,22 +21,22 @@ export default (state, action, config) => { }; } - if (config.isRequestAction(action) && action.meta?.measureDownloadProgress) { + if (config.isRequestAction(action) && action.meta.measureDownloadProgress) { return { ...state, downloadProgress: { ...state.downloadProgress, - [action.type + (action.meta?.requestKey || '')]: 0, + [action.type + (action.meta.requestKey || '')]: 0, }, }; } - if (config.isRequestAction(action) && action.meta?.measureUploadProgress) { + if (config.isRequestAction(action) && action.meta.measureUploadProgress) { return { ...state, uploadProgress: { ...state.uploadProgress, - [action.type + (action.meta?.requestKey || '')]: 0, + [action.type + (action.meta.requestKey || '')]: 0, }, }; } diff --git a/packages/redux-requests/src/reducers/queries-reducer.js b/packages/redux-requests/src/reducers/queries-reducer.js index e21f89829..22bfeacab 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.js +++ b/packages/redux-requests/src/reducers/queries-reducer.js @@ -7,6 +7,7 @@ import { getRequestActionFromResponse, isErrorAction, isSuccessAction, + isRequestActionLocalMutation, } from '../actions'; import { getQuery } from '../selectors'; import { normalize, mergeData, getDependentKeys } from '../normalizers'; @@ -26,7 +27,7 @@ const getInitialQuery = normalized => ({ }); const shouldBeNormalized = (action, config) => - action.meta?.normalize !== undefined + action.meta.normalize !== undefined ? action.meta.normalize : config.normalize; @@ -162,7 +163,7 @@ const maybeGetQueryActionType = (action, config) => { }; const maybeGetMutationData = (action, config) => { - if (config.isRequestAction(action) && action.meta?.optimisticData) { + if (config.isRequestAction(action) && action.meta.optimisticData) { return action.meta.optimisticData; } @@ -183,7 +184,7 @@ const maybeGetMutationData = (action, config) => { return action.response.data; } - if (action.meta?.localData) { + if (isRequestActionLocalMutation(action) && action.meta.localData) { return action.meta.localData; } diff --git a/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js b/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js index 827478b88..11c93ba63 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js @@ -4,6 +4,7 @@ import { createErrorAction, createAbortAction, } from '../actions'; +import { createQuery } from '../requests-creators'; import queriesReducer from './queries-reducer'; @@ -20,10 +21,7 @@ describe('reducers', () => { usedKeys: null, dependencies: null, }; - const requestAction = { - type: 'FETCH_BOOK', - request: { url: '/ ' }, - }; + const requestAction = createQuery('FETCH_BOOK', { url: '/ ' })(); it('returns the same state for not handled action', () => { const state = { queries: {}, normalizedData: {} }; diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js index 7baaf29d6..b9cbbf1c6 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js @@ -1,5 +1,6 @@ import defaultConfig from '../default-config'; import { createSuccessAction } from '../actions'; +import { createMutation, createLocalMutation } from '../requests-creators'; import queriesReducer from './queries-reducer'; @@ -32,17 +33,17 @@ describe('reducers', () => { expect( queriesReducer( initialState, - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { mutations: { FETCH_BOOK1: { updateDataOptimistic: data => `${data} suffix`, }, }, }, - }, + )(), defaultConfig, ), ).toEqual({ @@ -71,10 +72,10 @@ describe('reducers', () => { queriesReducer( initialState, createSuccessAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { mutations: { FETCH_BOOK1: { updateData: (data, mutationData) => @@ -82,7 +83,7 @@ describe('reducers', () => { }, }, }, - }, + )(), { data: { nested: 'suffix' } }, ), defaultConfig, @@ -112,17 +113,11 @@ describe('reducers', () => { expect( queriesReducer( initialState, - { - type: 'LOCAL_MUTATION_ACTION', - meta: { - mutations: { - FETCH_BOOK1: { - local: true, - updateData: data => `${data} suffix`, - }, - }, + createLocalMutation('LOCAL_MUTATION_ACTION', { + mutations: { + FETCH_BOOK1: data => `${data} suffix`, }, - }, + })(), defaultConfig, ), ).toEqual({ diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js index 3cdf7dd5d..8b3402ff8 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js @@ -4,6 +4,7 @@ import { createErrorAction, createAbortAction, } from '../actions'; +import { createMutation, createLocalMutation } from '../requests-creators'; import queriesReducer from './queries-reducer'; @@ -29,17 +30,17 @@ describe('reducers', () => { expect( queriesReducer( initialState, - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { mutations: { FETCH_BOOK: { updateDataOptimistic: data => `${data} suffix`, }, }, }, - }, + )(), defaultConfig, ), ).toEqual({ @@ -61,17 +62,17 @@ describe('reducers', () => { queriesReducer( initialState, createSuccessAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { mutations: { FETCH_BOOK: { updateDataOptimistic: data => `${data} suffix`, }, }, }, - }, + )(), { data: 'updated data' }, ), defaultConfig, @@ -84,16 +85,16 @@ describe('reducers', () => { queriesReducer( initialState, createSuccessAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { mutations: { FETCH_BOOK: (data, mutationData) => data + mutationData.nested, }, }, - }, + )(), { data: { nested: 'suffix' } }, ), defaultConfig, @@ -117,10 +118,10 @@ describe('reducers', () => { queriesReducer( initialState, createSuccessAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { mutations: { FETCH_BOOK: { updateData: (data, mutationData) => @@ -128,7 +129,7 @@ describe('reducers', () => { }, }, }, - }, + )(), { data: { nested: 'suffix' } }, ), defaultConfig, @@ -152,10 +153,10 @@ describe('reducers', () => { queriesReducer( initialState, createErrorAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { mutations: { FETCH_BOOK: { updateDataOptimistic: () => 'data2', @@ -163,7 +164,7 @@ describe('reducers', () => { }, }, }, - }, + )(), 'error', ), defaultConfig, @@ -187,17 +188,17 @@ describe('reducers', () => { queriesReducer( initialState, createErrorAction( - { - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { mutations: { FETCH_BOOK: { updateDataOptimistic: () => 'data2', }, }, }, - }, + )(), 'error', ), defaultConfig, @@ -220,18 +221,20 @@ describe('reducers', () => { expect( queriesReducer( initialState, - createAbortAction({ - type: MUTATION_ACTION, - request: { url: '/books', method: 'post' }, - meta: { - mutations: { - FETCH_BOOK: { - updateDataOptimistic: () => 'data2', - revertData: data => `${data} reverted`, + createAbortAction( + createMutation( + MUTATION_ACTION, + { url: '/books', method: 'post' }, + { + mutations: { + FETCH_BOOK: { + updateDataOptimistic: () => 'data2', + revertData: data => `${data} reverted`, + }, }, }, - }, - }), + )(), + ), defaultConfig, ), ).toEqual({ @@ -252,17 +255,11 @@ describe('reducers', () => { expect( queriesReducer( initialState, - { - type: 'LOCAL_MUTATION_ACTION', - meta: { - mutations: { - FETCH_BOOK: { - local: true, - updateData: data => `${data} suffix`, - }, - }, + createLocalMutation('LOCAL_MUTATION_ACTION', { + mutations: { + FETCH_BOOK: data => `${data} suffix`, }, - }, + })(), defaultConfig, ), ).toEqual({ diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js index 98cd6a96e..1cfb2036c 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js +++ b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js @@ -1,5 +1,10 @@ import defaultConfig from '../default-config'; import { createSuccessAction, createErrorAction } from '../actions'; +import { + createQuery, + createMutation, + createLocalMutation, +} from '../requests-creators'; import queriesReducer from './queries-reducer'; @@ -165,11 +170,11 @@ describe('reducers', () => { }; it('handles initial books fetch', () => { - const fetchBooks = { - type: 'FETCH_BOOKS', - request: { url: '/books' }, - meta: { normalize: true }, - }; + const fetchBooks = createQuery( + 'FETCH_BOOKS', + { url: '/books' }, + { normalize: true }, + )(); expect( queriesReducer( @@ -225,11 +230,11 @@ describe('reducers', () => { }); it('handles book detail fetch', () => { - const fetchBook = { - type: 'FETCH_BOOK', - request: { url: '/book/1' }, - meta: { normalize: true }, - }; + const fetchBook = createQuery( + 'FETCH_BOOK', + { url: '/book/1' }, + { normalize: true }, + )(); expect( queriesReducer( @@ -307,11 +312,11 @@ describe('reducers', () => { }); it('handles book update', () => { - const updateBook = { - type: 'UPDATE_BOOK', - request: { url: '/book/1', method: 'put' }, - meta: { normalize: true }, - }; + const updateBook = createMutation( + 'UPDATE_BOOK', + { url: '/book/1', method: 'put' }, + { normalize: true }, + )(); expect( queriesReducer( @@ -395,11 +400,11 @@ describe('reducers', () => { }); it('handles book author change and orphan object', () => { - const updateBookAuthor = { - type: 'UPDATE_BOOK_AUTHOR', - request: { url: '/book/1/author', method: 'put' }, - meta: { normalize: true }, - }; + const updateBookAuthor = createMutation( + 'UPDATE_BOOK_AUTHOR', + { url: '/book/1/author', method: 'put' }, + { normalize: true }, + )(); expect( queriesReducer( @@ -485,11 +490,11 @@ describe('reducers', () => { }); it('handles added liker', () => { - const addBookLiker = { - type: 'ADD_BOOK_LIKER', - request: { url: '/book/1/liker', method: 'put' }, - meta: { normalize: true }, - }; + const addBookLiker = createMutation( + 'ADD_BOOK_LIKER', + { url: '/book/1/liker', method: 'put' }, + { normalize: true }, + )(); expect( queriesReducer( @@ -590,13 +595,13 @@ describe('reducers', () => { dependencies: [], ref: {}, }; - const requestAction = { - type: 'FETCH_BOOK', - request: { url: '/ ' }, - meta: { + const requestAction = createQuery( + 'FETCH_BOOK', + { url: '/ ' }, + { normalize: true, }, - }; + )(); it('should normalize data on query success', () => { expect( @@ -747,13 +752,13 @@ describe('reducers', () => { }, }, createSuccessAction( - { - type: 'UPDATE_BOOK', - request: { url: '/', method: 'put' }, - meta: { + createMutation( + 'UPDATE_BOOK', + { url: '/', method: 'put' }, + { normalize: true, }, - }, + )(), { data: { id: '1', a: 'd', c: 'c' }, }, @@ -800,16 +805,16 @@ describe('reducers', () => { dependentQueries: { '@@1': ['FETCH_BOOK'] }, }, createSuccessAction( - { - type: 'ADD_BOOK', - request: { url: '/', method: 'put' }, - meta: { + createMutation( + 'ADD_BOOK', + { url: '/', method: 'put' }, + { normalize: true, mutations: { FETCH_BOOK: updateData, }, }, - }, + )(), { data: { id: '2', x: 2 }, }, @@ -865,18 +870,14 @@ describe('reducers', () => { normalizedData: { '@@1': { id: '1', x: 1 } }, dependentQueries: { '@@1': ['FETCH_BOOK'] }, }, - { - type: 'ADD_BOOK_LOCALLY', - meta: { - // normalize: true, - mutations: { - FETCH_BOOK: { - updateData: data => [...data, { id: '2', x: 2 }], - local: true, - }, + createLocalMutation('ADD_BOOK_LOCALLY', { + mutations: { + FETCH_BOOK: { + updateData: data => [...data, { id: '2', x: 2 }], + local: true, }, }, - }, + })(), defaultConfig, ), ).toEqual({ @@ -924,12 +925,9 @@ describe('reducers', () => { '@@1': ['FETCH_BOOK'], }, }, - { - type: 'UPDATE_BOOK_LOCALLY', - meta: { - localData: { id: '1', x: 2 }, - }, - }, + createLocalMutation('UPDATE_BOOK_LOCALLY', { + localData: { id: '1', x: 2 }, + })(), defaultConfig, ), ).toEqual({ @@ -977,13 +975,13 @@ describe('reducers', () => { '@@1': ['FETCH_BOOK'], }, }, - { - type: 'UPDATE_BOOK', - request: { url: '/books', method: 'post' }, - meta: { + createMutation( + 'UPDATE_BOOK', + { url: '/books', method: 'post' }, + { optimisticData: { id: '1', x: 2 }, }, - }, + )(), defaultConfig, ), ).toEqual({ @@ -1031,14 +1029,16 @@ describe('reducers', () => { '@@1': ['FETCH_BOOK'], }, }, - createErrorAction({ - type: 'UPDATE_BOOK', - request: { url: '/books', method: 'post' }, - meta: { - optimisticData: { id: '1', x: 2 }, - revertedData: { id: '1', x: 1 }, - }, - }), + createErrorAction( + createMutation( + 'UPDATE_BOOK', + { url: '/books', method: 'post' }, + { + optimisticData: { id: '1', x: 2 }, + revertedData: { id: '1', x: 1 }, + }, + )(), + ), defaultConfig, ), ).toEqual({ diff --git a/packages/redux-requests/src/reducers/queries-reducer.with-request-key.js b/packages/redux-requests/src/reducers/queries-reducer.with-request-key.js index d512cbf27..bfbd2c5a1 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.with-request-key.js +++ b/packages/redux-requests/src/reducers/queries-reducer.with-request-key.js @@ -4,6 +4,7 @@ import { createErrorAction, createAbortAction, } from '../actions'; +import { createQuery } from '../requests-creators'; import queriesReducer from './queries-reducer'; @@ -19,13 +20,13 @@ describe('reducers', () => { ref: {}, usedKeys: null, }; - const requestAction = { - type: 'FETCH_BOOK', - request: { url: '/ ' }, - meta: { + const requestAction = createQuery( + 'FETCH_BOOK', + { url: '/ ' }, + { requestKey: 1, }, - }; + )(); it('handles request query action', () => { expect( diff --git a/packages/redux-requests/src/reducers/requests-keys-reducer.js b/packages/redux-requests/src/reducers/requests-keys-reducer.js index e6b588910..9a0e047fa 100644 --- a/packages/redux-requests/src/reducers/requests-keys-reducer.js +++ b/packages/redux-requests/src/reducers/requests-keys-reducer.js @@ -2,7 +2,7 @@ import defaultConfig from '../default-config'; // TODO: this should be rewritten to more functional style, we need things like filter object helpers export default (state, action, config = defaultConfig) => { - if (config.isRequestAction(action) && action.meta?.requestKey !== undefined) { + if (config.isRequestAction(action) && action.meta.requestKey) { let { queries, mutations, cache, requestsKeys } = state; if (!requestsKeys[action.type]) { diff --git a/packages/redux-requests/src/reducers/requests-keys-reducer.spec.js b/packages/redux-requests/src/reducers/requests-keys-reducer.spec.js index 4a7480731..1d1655a62 100644 --- a/packages/redux-requests/src/reducers/requests-keys-reducer.spec.js +++ b/packages/redux-requests/src/reducers/requests-keys-reducer.spec.js @@ -1,3 +1,5 @@ +import { createQuery, createMutation } from '../requests-creators'; + import requestsKeysReducer from './requests-keys-reducer'; describe('reducers', () => { @@ -17,11 +19,10 @@ describe('reducers', () => { it('appends requestKeys for request actions', () => { expect( - requestsKeysReducer(defaultState, { - type: 'REQUEST', - request: { url: '/' }, - meta: { requestKey: '1' }, - }), + requestsKeysReducer( + defaultState, + createQuery('REQUEST', { url: '/' }, { requestKey: '1' })(), + ), ).toEqual({ ...defaultState, requestsKeys: { REQUEST: ['1'] } }); }); @@ -36,11 +37,11 @@ describe('reducers', () => { REQUEST2: { pending: 1, data: 'data', error: null }, }, }, - { - type: 'REQUEST', - request: { url: '/' }, - meta: { requestKey: '2', requestsCapacity: 1 }, - }, + createQuery( + 'REQUEST', + { url: '/' }, + { requestKey: '2', requestsCapacity: 1 }, + )(), ), ).toEqual({ ...defaultState, @@ -60,11 +61,11 @@ describe('reducers', () => { REQUEST2: { pending: 1, error: null }, }, }, - { - type: 'REQUEST', - request: { url: '/' }, - meta: { requestKey: '2', requestsCapacity: 1, asMutation: true }, - }, + createMutation( + 'REQUEST', + { url: '/' }, + { requestKey: '2', requestsCapacity: 1, asMutation: true }, + )(), ), ).toEqual({ ...defaultState, @@ -86,11 +87,11 @@ describe('reducers', () => { REQUEST2: { pending: 0, data: 'data', error: null }, }, }, - { - type: 'REQUEST', - request: { url: '/' }, - meta: { requestKey: '2', requestsCapacity: 2 }, - }, + createQuery( + 'REQUEST', + { url: '/' }, + { requestKey: '2', requestsCapacity: 2 }, + )(), ), ).toEqual({ ...defaultState, @@ -113,11 +114,11 @@ describe('reducers', () => { REQUEST2: { pending: 0, data: 'data', error: null }, }, }, - { - type: 'REQUEST', - request: { url: '/' }, - meta: { requestKey: '2', requestsCapacity: 1 }, - }, + createQuery( + 'REQUEST', + { url: '/' }, + { requestKey: '2', requestsCapacity: 1 }, + )(), ), ).toEqual({ ...defaultState, diff --git a/packages/redux-requests/src/reducers/requests-reducer.js b/packages/redux-requests/src/reducers/requests-reducer.js index 302ae252d..9a4829a22 100644 --- a/packages/redux-requests/src/reducers/requests-reducer.js +++ b/packages/redux-requests/src/reducers/requests-reducer.js @@ -1,5 +1,4 @@ import defaultConfig from '../default-config'; -import { isResponseAction, getRequestActionFromResponse } from '../actions'; import queriesReducer from './queries-reducer'; import mutationsReducer from './mutations-reducer'; @@ -30,23 +29,13 @@ export default (config = defaultConfig) => (state = defaultState, action) => { config, ); - let { mutations } = state; - - if ( - (config.isRequestAction(action) && !config.isRequestActionQuery(action)) || - (isResponseAction(action) && - !config.isRequestActionQuery(getRequestActionFromResponse(action))) - ) { - mutations = mutationsReducer(mutations, action); - } - return { ...requestKeysReducer( { ...requestsResetReducer( { queries, - mutations, + mutations: mutationsReducer(state.mutations, action, config), cache: cacheReducer(state.cache, action), ...progressReducer( { diff --git a/packages/redux-requests/src/reducers/requests-reducer.spec.js b/packages/redux-requests/src/reducers/requests-reducer.spec.js index 032677ec2..02d28face 100644 --- a/packages/redux-requests/src/reducers/requests-reducer.spec.js +++ b/packages/redux-requests/src/reducers/requests-reducer.spec.js @@ -1,4 +1,9 @@ import { createSuccessAction, createErrorAction } from '../actions'; +import { + createQuery, + createMutation, + createLocalMutation, +} from '../requests-creators'; import { requestsReducer } from '.'; @@ -48,8 +53,8 @@ describe('reducers', () => { it('handles read only requests', () => { const reducer = requestsReducer(); - const firstRequest = { type: 'REQUEST', request: { url: '/' } }; - const secondRequest = { type: 'REQUEST_2', request: { url: '/' } }; + const firstRequest = createQuery('REQUEST', { url: '/' })(); + const secondRequest = createQuery('REQUEST_2', { url: '/' })(); let state = reducer( { @@ -193,7 +198,7 @@ describe('reducers', () => { requestsKeys: {}, ssr: null, }, - { type: 'REQUEST', request: { url: '/' } }, + createQuery('REQUEST', { url: '/' })(), ); let state = { @@ -216,17 +221,14 @@ describe('reducers', () => { ssr: null, }; - state = reducer(state, { - type: 'LOCAL_MUTATION', - meta: { + state = reducer( + state, + createLocalMutation('LOCAL_MUTATION', { mutations: { - REQUEST: { - local: true, - updateData: () => 'data', - }, + REQUEST: () => 'data', }, - }, - }); + })(), + ); expect(state).toEqual({ queries: { @@ -248,10 +250,10 @@ describe('reducers', () => { ssr: null, }); - const mutationWithoutConfig = { - type: 'MUTATION_WITHOUT_CONFIG', - request: { url: '/', method: 'post' }, - }; + const mutationWithoutConfig = createMutation('MUTATION_WITHOUT_CONFIG', { + url: '/', + method: 'post', + })(); state = reducer(state, mutationWithoutConfig); @@ -368,15 +370,15 @@ describe('reducers', () => { ssr: null, }); - const mutationWithConfig = { - type: 'MUTATION_WITH_CONFIG', - request: { url: '/', method: 'post' }, - meta: { + const mutationWithConfig = createMutation( + 'MUTATION_WITH_CONFIG', + { url: '/', method: 'post' }, + { mutations: { REQUEST: (_, data) => data, }, }, - }; + )(); state = reducer(state, mutationWithConfig); @@ -447,16 +449,16 @@ describe('reducers', () => { ssr: null, }); - const mutationWithConfigWithRequestKey = { - type: 'MUTATION_WITH_CONFIG_WITH_REQUEST_KEY', - request: { url: '/', method: 'post' }, - meta: { + const mutationWithConfigWithRequestKey = createMutation( + 'MUTATION_WITH_CONFIG_WITH_REQUEST_KEY', + { url: '/', method: 'post' }, + { requestKey: '1', mutations: { REQUEST: (_, data) => data, }, }, - }; + )(); state = reducer(state, mutationWithConfigWithRequestKey); state = reducer(state, mutationWithConfigWithRequestKey); @@ -589,10 +591,10 @@ describe('reducers', () => { ssr: null, }); - const mutationWithOptimisticUpdate = { - type: 'MUTATION_WITH_OPTIMISTIC_UPDATE', - request: { url: '/', method: 'post' }, - meta: { + const mutationWithOptimisticUpdate = createMutation( + 'MUTATION_WITH_OPTIMISTIC_UPDATE', + { url: '/', method: 'post' }, + { mutations: { REQUEST: { updateData: (data, mutationData) => mutationData, @@ -601,7 +603,7 @@ describe('reducers', () => { }, }, }, - }; + )(); state = reducer(state, mutationWithOptimisticUpdate); @@ -772,17 +774,14 @@ describe('reducers', () => { let state = reducer(initialState, {}); expect(state).toEqual(initialState); - state = reducer(state, { - type: 'LOCAL_MUTATION', - meta: { + state = reducer( + state, + createLocalMutation('LOCAL_MUTATION', { mutations: { - QUERY: { - updateData: data => [...data, 'data2'], - local: true, - }, + QUERY: data => [...data, 'data2'], }, - }, - }); + })(), + ); expect(state).toEqual({ queries: { QUERY: { diff --git a/packages/redux-requests/src/reducers/requests-reset-reducer.js b/packages/redux-requests/src/reducers/requests-reset-reducer.js index 5d7543cc7..6e29bc300 100644 --- a/packages/redux-requests/src/reducers/requests-reset-reducer.js +++ b/packages/redux-requests/src/reducers/requests-reset-reducer.js @@ -1,14 +1,11 @@ import { RESET_REQUESTS } from '../constants'; import { mapObject } from '../helpers'; -const getRequestTypeString = requestType => - typeof requestType === 'function' ? requestType.toString() : requestType; - const getKeys = requests => requests.map(v => typeof v === 'object' - ? getRequestTypeString(v.requestType) + (v.requestKey || '') - : getRequestTypeString(v), + ? v.requestType.toString() + (v.requestKey || '') + : v.toString(), ); const resetQuery = query => diff --git a/packages/redux-requests/src/reducers/ssr-reducer.js b/packages/redux-requests/src/reducers/ssr-reducer.js index bee181e8e..ec99f9a1b 100644 --- a/packages/redux-requests/src/reducers/ssr-reducer.js +++ b/packages/redux-requests/src/reducers/ssr-reducer.js @@ -22,7 +22,7 @@ export default (state = [], action, config = defaultConfig) => { if ( config.ssr === 'client' && config.isRequestAction(action) && - (action.meta?.ssrResponse || action.meta?.ssrError) + (action.meta.ssrResponse || action.meta.ssrError) ) { const indexToRemove = state.findIndex( v => diff --git a/packages/redux-requests/src/reducers/ssr-reducer.spec.js b/packages/redux-requests/src/reducers/ssr-reducer.spec.js index a8100e310..578799be0 100644 --- a/packages/redux-requests/src/reducers/ssr-reducer.spec.js +++ b/packages/redux-requests/src/reducers/ssr-reducer.spec.js @@ -1,5 +1,6 @@ import { createSuccessAction } from '../actions'; import defaultConfig from '../default-config'; +import { createQuery } from '../requests-creators'; import ssrReducer from './ssr-reducer'; @@ -18,10 +19,7 @@ describe('reducers', () => { expect( ssrReducer( ['REQUEST'], - createSuccessAction( - { type: 'REQUEST', request: { url: '/' } }, - 'data', - ), + createSuccessAction(createQuery('REQUEST', { url: '/' })(), 'data'), { ...defaultConfig, ssr: 'server' }, ), ).toEqual(['REQUEST', 'REQUEST']); @@ -31,12 +29,11 @@ describe('reducers', () => { expect( ssrReducer( ['REQUEST', 'REQUEST'], - - { - type: 'REQUEST', - request: { url: '/' }, - meta: { ssrResponse: { data: 'data' } }, - }, + createQuery( + 'REQUEST', + { url: '/' }, + { ssrResponse: { data: 'data' } }, + )(), { ...defaultConfig, ssr: 'client' }, ), ).toEqual(['REQUEST']); @@ -46,12 +43,11 @@ describe('reducers', () => { expect( ssrReducer( ['REQUEST', 'REQUEST'], - - { - type: 'REQUEST2', - request: { url: '/' }, - meta: { ssrResponse: { data: 'data' } }, - }, + createQuery( + 'REQUEST2', + { url: '/' }, + { ssrResponse: { data: 'data' } }, + )(), { ...defaultConfig, ssr: 'client' }, ), ).toEqual(['REQUEST', 'REQUEST']); diff --git a/packages/redux-requests/src/reducers/update-data.js b/packages/redux-requests/src/reducers/update-data.js index 95fee30e2..7373d7c39 100644 --- a/packages/redux-requests/src/reducers/update-data.js +++ b/packages/redux-requests/src/reducers/update-data.js @@ -1,4 +1,8 @@ -import { isSuccessAction, isResponseAction } from '../actions'; +import { + isSuccessAction, + isResponseAction, + isRequestActionLocalMutation, +} from '../actions'; const getDataUpdater = mutationConfig => { if (typeof mutationConfig === 'function') { @@ -31,7 +35,7 @@ export default (data, action, mutationConfig) => { return mutationConfig.updateDataOptimistic(data); } - if (mutationConfig.local) { + if (isRequestActionLocalMutation(action)) { return getDataUpdater(mutationConfig)(data); } diff --git a/packages/redux-requests/src/requests-creators.js b/packages/redux-requests/src/requests-creators.js index 59fd54eca..5eb00f3b0 100644 --- a/packages/redux-requests/src/requests-creators.js +++ b/packages/redux-requests/src/requests-creators.js @@ -1,6 +1,6 @@ -const createRequest = requestType => (name, requestConfig, metaConfig) => { +const createRequest = requestType => (type, requestConfig, metaConfig) => { const actionCreator = (...params) => ({ - type: name, + type, payload: typeof requestConfig === 'function' ? requestConfig(...params) @@ -12,10 +12,26 @@ const createRequest = requestType => (name, requestConfig, metaConfig) => { requestType, }, }); - actionCreator.toString = () => name; + actionCreator.toString = () => type; return actionCreator; }; export const createQuery = createRequest('QUERY'); + export const createMutation = createRequest('MUTATION'); + export const createSubscription = createRequest('SUBSCRIPTION'); + +export const createLocalMutation = (type, metaConfig) => { + const actionCreator = (...params) => ({ + type, + meta: { + ...(typeof metaConfig === 'function' + ? metaConfig(...params) + : metaConfig), + requestType: 'LOCAL_MUTATION', + }, + }); + actionCreator.toString = () => type; + return actionCreator; +}; diff --git a/packages/redux-requests/types/index.d.spec.ts b/packages/redux-requests/types/index.d.spec.ts index cff60f65b..af664f0c2 100644 --- a/packages/redux-requests/types/index.d.spec.ts +++ b/packages/redux-requests/types/index.d.spec.ts @@ -7,8 +7,6 @@ import { clearRequestsCache, resetRequests, abortRequests, - RequestAction, - LocalMutationAction, ResponseData, handleRequests, getQuery, @@ -18,64 +16,62 @@ import { isRequestAction, isRequestActionQuery, isResponseAction, - createRequestsStore, + createQuery, } from './index'; success('type'); error('type'); abort('type'); -const requestAction: RequestAction = { - type: 'FETCH', - request: { url: '/' }, - meta: { - driver: 'default', - takeLatest: false, - cache: 1, - cacheKey: 'key', - cacheSize: 2, - dependentRequestsNumber: 1, - isDependentRequest: true, - customKey: 'customValue', - requestKey: '1', - asMutation: true, - mutations: { - FETCH: { - updateData: () => 'data', - revertData: () => 'data', - }, - }, +const fetchBook = createQuery( + 'fetchBook', + (id: number) => ({ url: `/books/${id}` }), + { + getData: (data: string) => ({ + title: 'title', + nested: { value: 1, data }, + }), + normalize: true, + requestType: 'QUERY', }, -}; - -const accessRequestActionProps = (requestAction: RequestAction) => { - if (requestAction.request !== undefined) { - // this request action has an existing `request` key - } else if (requestAction.payload !== undefined) { - // this request action has an existing `payload` key - } -} - -const fetchBook: ( - id: string, -) => RequestAction<{ id: string; title: string }> = () => { - return { - type: 'FETCH_BOOK', - request: { - url: '/book', - }, - }; -}; - -const dummyDriver: Driver = ({}, requestAction, {}) => - new Promise((resolve) => { resolve() }); +); + +// const fetchBook: ( +// id: string, +// ) => RequestAction<{ id: string; title: string }> = () => { +// return { +// type: 'FETCH_BOOK', +// request: { +// url: '/book', +// }, +// }; +// }; + +// const dummyDriver: Driver = ({}, requestAction, {}) => +// new Promise(resolve => { +// resolve(); +// }); +const x = fetchBook(1); + +const booksQuery = getQuery({}, { type: fetchBook }); +booksQuery.data.nested.value; + +const booksSelector = getQuerySelector({ type: fetchBook }); +booksSelector({}).data.title; + +let dummyDriver: Driver; +dummyDriver({}, fetchBook, {}) + .then(v => v) + .catch(e => { + throw e; + }); handleRequests({ driver: dummyDriver }); handleRequests({ driver: { default: dummyDriver, anotherDriver: dummyDriver }, onRequest: (request, action) => request, onSuccess: async (response, action, store) => { - const r = await store.dispatchRequest(fetchBook('1')); + const r = await store.dispatch(fetchBook(1)); return response; }, onError: (error, action) => ({ error }), @@ -84,8 +80,19 @@ handleRequests({ isRequestActionQuery: () => true, }); -const requestsStore = createRequestsStore(createStore(combineReducers({}))); -const response = requestsStore.dispatchRequest(fetchBook('1')); +const reducer = (state = 0, action) => { + if (action.type === 'KSKS') { + return 1; + } + + return state; +}; + +const requestsStore = createStore(combineReducers({ x: reducer })); + +const ff = fetchBook(1); +const response = requestsStore.dispatch(fetchBook(1)); +const response2 = requestsStore.dispatch({ type: 'LALA' }); clearRequestsCache(); clearRequestsCache(['TYPE']); @@ -99,57 +106,6 @@ resetRequests(); resetRequests(['TYPE']); resetRequests(['TYPE', { requestType: 'ANOTHER_TYPE', requestKey: '1' }]); -getQuery({}, { type: 'Mutation', requestKey: '1' }); - -const querySelector = getQuerySelector({ type: 'Query' }); -querySelector({}); - -const query = getQuery<{ key: string }>({}, { type: 'Query' }); -query.data.key = '1'; - -const querySelector2 = getQuerySelector<{ key: string }>({ type: 'Query' }); -const query2 = querySelector2({}); -query2.data.key = '1'; - -getMutation({}, { type: 'Mutation', requestKey: '1' }); -const mutationSelector = getMutationSelector({ type: 'Mutation' }); -mutationSelector({}); - isRequestAction({ type: 'ACTION' }) === true; isRequestActionQuery({ type: 'ACTION', request: { url: '/' } }) === true; isResponseAction({ type: 'ACTION', request: { url: '/' } }) === true; - -const fetchBooks: () => RequestAction< - { raw: boolean }, - { parsed: boolean } -> = () => { - return { - type: 'FETCH_BOOKS', - request: { - url: '/books', - }, - meta: { - getData: data => ({ parsed: data.raw }), - }, - }; -}; - -const booksQuery = getQuery({}, { type: fetchBooks }); - -const booksSelector = getQuerySelector({ type: fetchBooks }); -booksSelector({}).data.parsed; - -type BooksData = ResponseData; - -const localMutation: () => LocalMutationAction = () => ({ - type: 'LOCAL_MUTATION', - meta: { - localData: { id: '1', title: 'title' }, - mutations: { - FETCH_BOOKS: { - updateData: (data: BooksData) => ({ parsed: data.parsed }), - local: true, - }, - }, - }, -}); diff --git a/packages/redux-requests/types/index.d.ts b/packages/redux-requests/types/index.d.ts index 5ad609238..270c6557e 100644 --- a/packages/redux-requests/types/index.d.ts +++ b/packages/redux-requests/types/index.d.ts @@ -1,14 +1,7 @@ import { AnyAction, Reducer, Middleware, Store } from 'redux'; -export interface DispatchRequest { - ( - requestAction: RequestAction, - ): Promise<{ - data?: QueryStateData; - error?: any; - isAborted?: true; - action: any; - }>; +interface Config { + [key: string]: any; } interface FilterActions { @@ -19,14 +12,15 @@ interface ModifyData { (data: any, mutationData: any): any; } -interface RequestsStore extends Store { - dispatchRequest: DispatchRequest; -} +type ActionTypeModifier = (actionType: string) => string; -export const createRequestsStore: (store: Store) => RequestsStore; +export const success: ActionTypeModifier; -interface RequestActionMeta { - asMutation?: boolean; +export const error: ActionTypeModifier; + +export const abort: ActionTypeModifier; + +interface RequestMeta { driver?: string; takeLatest?: boolean; getData?: (data: Data, currentData: TransformedData) => TransformedData; @@ -34,24 +28,6 @@ interface RequestActionMeta { requestKey?: string; requestsCapacity?: number; normalize?: boolean; - mutations?: { - [actionType: string]: - | ModifyData - | { - updateData?: ModifyData; - updateDataOptimistic?: (data: any) => any; - revertData?: (data: any) => any; - local?: boolean; - }; - }; - optimisticData?: any; - revertedData?: any; - localData?: any; - cache?: boolean | number; - cacheKey?: string; - poll?: number; - dependentRequestsNumber?: number; - isDependentRequest?: boolean; silent?: boolean; onRequest?: ( request: any, @@ -71,30 +47,44 @@ interface RequestActionMeta { runOnAbort?: boolean; measureDownloadProgress?: boolean; measureUploadProgress?: boolean; - [extraProperty: string]: any; } -export type RequestAction = { - type?: string; - request: any | any[]; - meta?: RequestActionMeta; -}; +interface QueryMeta + extends RequestMeta { + requestType: 'QUERY'; + cache?: boolean | number; + cacheKey?: string; + poll?: number; + dependentRequestsNumber?: number; + isDependentRequest?: boolean; +} -export type LocalMutationAction = { - type?: string; - meta: { - mutations?: { - [actionType: string]: { - updateData: ModifyData; - local: true; - }; - }; - localData?: any; - [extraProperty: string]: any; +interface MutationMeta + extends RequestMeta { + requestType: 'MUTATION'; + mutations?: { + [actionType: string]: + | ModifyData + | { + updateData?: ModifyData; + updateDataOptimistic?: (data: any) => any; + revertData?: (data: any) => any; + }; }; -}; + optimisticData?: any; + revertedData?: any; +} -interface SubscriptionActionMeta { +interface LocalMutationMeta { + requestType: 'LOCAL_MUTATION'; + mutations?: { + [actionType: string]: ModifyData; + }; + localData?: any; +} + +interface SubscriptionMeta { + requestType: 'SUBSCRIPTION'; requestKey?: string; normalize?: boolean; mutations?: { @@ -106,26 +96,86 @@ interface SubscriptionActionMeta { }; getData?: (data: any) => any; onMessage?: (data: any, message: any, store: RequestsStore) => void; - [extraProperty: string]: any; } -export type SubscriptionAction = { - type?: string; - subscription: any; - meta?: SubscriptionActionMeta; -}; +export interface Query { + type: string; + payload: any | any[]; + meta?: QueryMeta; +} -type ResponseData< - Request extends (...args: any[]) => RequestAction -> = ReturnType['meta']>['getData']>; +export interface Mutation { + type: string; + payload: any | any[]; + meta?: MutationMeta; +} -type ActionTypeModifier = (actionType: string) => string; +export interface LocalMutation { + type: string; + meta: LocalMutationMeta; +} -export const success: ActionTypeModifier; +export interface Subscription { + type: string; + payload: any; + meta?: SubscriptionMeta; +} -export const error: ActionTypeModifier; +export interface Dispatch { + (action: Action): Action extends + | Query + | Mutation + ? Promise<{ + data?: Data; + error?: any; + isAborted?: true; + action: any; + }> + : Action; +} -export const abort: ActionTypeModifier; +interface RequestsStore extends Store { + dispatch: Dispatch; +} + +export function createQuery< + Data = any, + TransformedData = Data, + Variables extends any[] = any[] +>( + type: string, + requestConfig: Config | ((...params: Variables) => Config), + metaConfig?: + | QueryMeta + | ((...params: Variables) => QueryMeta), +): (...params: Variables) => Query; + +export function createMutation< + Data = any, + TransformedData = Data, + Variables extends any[] = any[] +>( + type: string, + requestConfig: Config | ((...params: Variables) => Config), + metaConfig?: + | MutationMeta + | ((...params: Variables) => MutationMeta), +): (...params: Variables) => Mutation; + +export function createLocalMutation( + type: string, + metaConfig: LocalMutationMeta | ((...params: Variables) => LocalMutationMeta), +): (...params: Variables) => LocalMutation; + +export function createSubscription( + type: string, + requestConfig: Config | ((...params: Variables) => Config) | null, + metaConfig?: SubscriptionMeta | ((...params: Variables) => SubscriptionMeta), +): (...params: Variables) => Subscription; + +type ResponseData< + Request extends (...args: any[]) => Query | Mutation +> = ReturnType['meta']['getData']>; interface DriverActions { setDownloadProgress?: (downloadProgress: number) => void; @@ -274,8 +324,8 @@ export const joinRequest: ( rehydrate: boolean; }; -export interface QueryState { - data: QueryStateData; +export interface QueryState { + data: Data; error: any; pending: number; loading: boolean; @@ -284,20 +334,18 @@ export interface QueryState { downloadProgress: number | null; } -export function getQuery( +export function getQuery( state: any, props: { - type: string | ((...params: any[]) => RequestAction); - action?: (...params: any[]) => RequestAction; + type: (...params: any[]) => Query; requestKey?: string; }, -): QueryState; +): QueryState; -export function getQuerySelector(props: { - type: string | ((...params: any[]) => RequestAction); - action?: (...params: any[]) => RequestAction; +export function getQuerySelector(props: { + type: (...params: any[]) => Query; requestKey?: string; -}): (state: any) => QueryState; +}): (state: any) => QueryState; export interface MutationState { pending: number; @@ -310,13 +358,13 @@ export interface MutationState { export function getMutation( state: any, props: { - type: string | ((...params: any[]) => RequestAction); + type: (...params: any[]) => Mutation; requestKey?: string; }, ): MutationState; export function getMutationSelector(props: { - type: string | ((...params: any[]) => RequestAction); + type: (...params: any[]) => Mutation; requestKey?: string; }): (state: any) => MutationState; diff --git a/yarn.lock b/yarn.lock index b8fe6342b..a5c0a40a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1214,6 +1214,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.9.2": + version "7.13.17" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.17.tgz#8966d1fc9593bf848602f0662d6b4d0069e3a7ec" + integrity sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.1.0", "@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -8977,13 +8984,12 @@ redux-mock-store@1.5.4: dependencies: lodash.isplainobject "^4.0.6" -redux@4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" - integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== +redux@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4" + integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g== dependencies: - loose-envify "^1.4.0" - symbol-observable "^1.2.0" + "@babel/runtime" "^7.9.2" regenerate-unicode-properties@^8.1.0: version "8.1.0" @@ -10013,11 +10019,6 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" -symbol-observable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -10421,10 +10422,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" - integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== +typescript@4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== typical@^5.0.0, typical@^5.2.0: version "5.2.0" From 9a10a446a93696396cf8427c9f56931bbfaaa4a8 Mon Sep 17 00:00:00 2001 From: klis87 Date: Wed, 15 Dec 2021 01:19:10 +0100 Subject: [PATCH 14/14] Remove isRequestAction and isRequestActionQuery from handleRequests config --- packages/redux-requests/src/default-config.js | 4 +--- packages/redux-requests/src/handle-requests.js | 8 ++++---- .../src/middleware/create-client-ssr-middleware.js | 6 +++--- .../src/middleware/create-polling-middleware.js | 12 ++++++------ .../middleware/create-requests-cache-middleware.js | 6 +++--- .../middleware/create-send-requests-middleware.js | 3 ++- .../src/middleware/create-server-ssr-middleware.js | 7 +++---- .../src/reducers/mutations-reducer.js | 5 +++-- .../src/reducers/progress-reducer.js | 7 ++++--- .../redux-requests/src/reducers/queries-reducer.js | 14 ++++++++------ .../src/reducers/requests-keys-reducer.js | 8 ++++---- .../src/reducers/requests-reducer.js | 4 +--- .../redux-requests/src/reducers/ssr-reducer.js | 8 ++++++-- packages/redux-requests/types/index.d.spec.ts | 1 - packages/redux-requests/types/index.d.ts | 2 -- 15 files changed, 48 insertions(+), 47 deletions(-) diff --git a/packages/redux-requests/src/default-config.js b/packages/redux-requests/src/default-config.js index 8a817659c..787c7e850 100644 --- a/packages/redux-requests/src/default-config.js +++ b/packages/redux-requests/src/default-config.js @@ -1,4 +1,4 @@ -import { isRequestActionQuery, isRequestAction } from './actions'; +import { isRequestActionQuery } from './actions'; export default { driver: null, @@ -8,8 +8,6 @@ export default { onAbort: null, ssr: null, disableRequestsPromise: false, - isRequestAction, - isRequestActionQuery, takeLatest: isRequestActionQuery, normalize: false, getNormalisationObjectKey: obj => obj.id, diff --git a/packages/redux-requests/src/handle-requests.js b/packages/redux-requests/src/handle-requests.js index aac4a842a..a12322231 100644 --- a/packages/redux-requests/src/handle-requests.js +++ b/packages/redux-requests/src/handle-requests.js @@ -32,12 +32,12 @@ const handleRequests = userConfig => { config.ssr !== 'server' && config.subscriber && createSubscriptionsMiddleware(config), - config.ssr !== 'server' && createPollingMiddleware(config), + config.ssr !== 'server' && createPollingMiddleware(), config.ssr === 'server' && !config.disableRequestsPromise && - createServerSsrMiddleware(requestsPromise, config), - config.ssr === 'client' && createClientSsrMiddleware(config), - createRequestsCacheMiddleware(config), + createServerSsrMiddleware(requestsPromise), + config.ssr === 'client' && createClientSsrMiddleware(), + createRequestsCacheMiddleware(), createSendRequestsMiddleware(config), ].filter(Boolean), requestsPromise, diff --git a/packages/redux-requests/src/middleware/create-client-ssr-middleware.js b/packages/redux-requests/src/middleware/create-client-ssr-middleware.js index 0c629cba8..6ee4ae852 100644 --- a/packages/redux-requests/src/middleware/create-client-ssr-middleware.js +++ b/packages/redux-requests/src/middleware/create-client-ssr-middleware.js @@ -1,8 +1,8 @@ -import defaultConfig from '../default-config'; import { getQuery } from '../selectors'; +import { isRequestAction } from '../actions'; -export default (config = defaultConfig) => store => next => action => { - if (!config.isRequestAction(action)) { +export default () => store => next => action => { + if (!isRequestAction(action)) { return next(action); } diff --git a/packages/redux-requests/src/middleware/create-polling-middleware.js b/packages/redux-requests/src/middleware/create-polling-middleware.js index 0cebcca6d..d997add81 100644 --- a/packages/redux-requests/src/middleware/create-polling-middleware.js +++ b/packages/redux-requests/src/middleware/create-polling-middleware.js @@ -1,5 +1,5 @@ -import defaultConfig from '../default-config'; import { STOP_POLLING, RESET_REQUESTS } from '../constants'; +import { isRequestAction, isRequestActionQuery } from '../actions'; const getIntervalKey = action => action.type + (action.meta.requestKey || ''); @@ -10,7 +10,7 @@ const getKeys = requests => : v.toString(), ); -export default (config = defaultConfig) => { +export default () => { let intervals = {}; return store => next => action => { @@ -30,8 +30,8 @@ export default (config = defaultConfig) => { intervals = intervalsCopy; } } else if ( - config.isRequestAction(action) && - config.isRequestActionQuery(action) && + isRequestAction(action) && + isRequestActionQuery(action) && !action.meta.polled && intervals[getIntervalKey(action)] ) { @@ -42,8 +42,8 @@ export default (config = defaultConfig) => { } if ( - config.isRequestAction(action) && - config.isRequestActionQuery(action) && + isRequestAction(action) && + isRequestActionQuery(action) && action.meta.poll && !action.meta.polled ) { diff --git a/packages/redux-requests/src/middleware/create-requests-cache-middleware.js b/packages/redux-requests/src/middleware/create-requests-cache-middleware.js index 6e90ba997..9d636eb76 100644 --- a/packages/redux-requests/src/middleware/create-requests-cache-middleware.js +++ b/packages/redux-requests/src/middleware/create-requests-cache-middleware.js @@ -1,5 +1,5 @@ -import defaultConfig from '../default-config'; import { getQuery } from '../selectors'; +import { isRequestAction } from '../actions'; const isCacheValid = (cache, action) => cache.cacheKey === action.meta.cacheKey && @@ -7,9 +7,9 @@ const isCacheValid = (cache, action) => const getKey = action => action.type + (action.meta.requestKey || ''); -export default (config = defaultConfig) => store => next => action => { +export default () => store => next => action => { if ( - config.isRequestAction(action) && + isRequestAction(action) && action.meta.cache && !action.meta.ssrResponse ) { diff --git a/packages/redux-requests/src/middleware/create-send-requests-middleware.js b/packages/redux-requests/src/middleware/create-send-requests-middleware.js index 7bafe6deb..ee45b2e86 100644 --- a/packages/redux-requests/src/middleware/create-send-requests-middleware.js +++ b/packages/redux-requests/src/middleware/create-send-requests-middleware.js @@ -4,6 +4,7 @@ import { createAbortAction, setDownloadProgress, setUploadProgress, + isRequestAction, } from '../actions'; import { ABORT_REQUESTS, RESET_REQUESTS, JOIN_REQUEST } from '../constants'; import { getQuery } from '../selectors'; @@ -298,7 +299,7 @@ const createSendRequestMiddleware = config => { return next(action); } - if (config.isRequestAction(action)) { + if (isRequestAction(action)) { const lastActionKey = getLastActionKey(action); allPendingRequests[lastActionKey] = defer(); diff --git a/packages/redux-requests/src/middleware/create-server-ssr-middleware.js b/packages/redux-requests/src/middleware/create-server-ssr-middleware.js index 780c8f5c6..446f2ebea 100644 --- a/packages/redux-requests/src/middleware/create-server-ssr-middleware.js +++ b/packages/redux-requests/src/middleware/create-server-ssr-middleware.js @@ -1,13 +1,12 @@ -import defaultConfig from '../default-config'; -import { isResponseAction, isSuccessAction } from '../actions'; +import { isResponseAction, isSuccessAction, isRequestAction } from '../actions'; -export default (requestsPromise, config = defaultConfig) => { +export default requestsPromise => { let index = 0; const successActions = []; const errorActions = []; return () => next => action => { - if (config.isRequestAction(action)) { + if (isRequestAction(action)) { index += action.meta.dependentRequestsNumber !== undefined ? action.meta.dependentRequestsNumber + 1 diff --git a/packages/redux-requests/src/reducers/mutations-reducer.js b/packages/redux-requests/src/reducers/mutations-reducer.js index 81b31720f..975b21b9a 100644 --- a/packages/redux-requests/src/reducers/mutations-reducer.js +++ b/packages/redux-requests/src/reducers/mutations-reducer.js @@ -3,11 +3,12 @@ import { isAbortAction, isResponseAction, getRequestActionFromResponse, + isRequestAction, isRequestActionMutation, } from '../actions'; -export default (state, action, config) => { - if (config.isRequestAction(action) && isRequestActionMutation(action)) { +export default (state, action) => { + if (isRequestAction(action) && isRequestActionMutation(action)) { const mutationType = action.type + (action.meta.requestKey || ''); return { diff --git a/packages/redux-requests/src/reducers/progress-reducer.js b/packages/redux-requests/src/reducers/progress-reducer.js index 02a2cd351..dee9b8e53 100644 --- a/packages/redux-requests/src/reducers/progress-reducer.js +++ b/packages/redux-requests/src/reducers/progress-reducer.js @@ -1,6 +1,7 @@ import { SET_DOWNLOAD_PROGRESS, SET_UPLOAD_PROGRESS } from '../constants'; +import { isRequestAction } from '../actions'; -export default (state, action, config) => { +export default (state, action) => { if (action.type === SET_DOWNLOAD_PROGRESS) { return { ...state, @@ -21,7 +22,7 @@ export default (state, action, config) => { }; } - if (config.isRequestAction(action) && action.meta.measureDownloadProgress) { + if (isRequestAction(action) && action.meta.measureDownloadProgress) { return { ...state, downloadProgress: { @@ -31,7 +32,7 @@ export default (state, action, config) => { }; } - if (config.isRequestAction(action) && action.meta.measureUploadProgress) { + if (isRequestAction(action) && action.meta.measureUploadProgress) { return { ...state, uploadProgress: { diff --git a/packages/redux-requests/src/reducers/queries-reducer.js b/packages/redux-requests/src/reducers/queries-reducer.js index 22bfeacab..29a11f4fc 100644 --- a/packages/redux-requests/src/reducers/queries-reducer.js +++ b/packages/redux-requests/src/reducers/queries-reducer.js @@ -7,6 +7,8 @@ import { getRequestActionFromResponse, isErrorAction, isSuccessAction, + isRequestAction, + isRequestActionQuery, isRequestActionLocalMutation, } from '../actions'; import { getQuery } from '../selectors'; @@ -147,14 +149,14 @@ const queryReducer = (state, action, actionType, config, normalizedData) => { } }; -const maybeGetQueryActionType = (action, config) => { - if (config.isRequestAction(action) && config.isRequestActionQuery(action)) { +const maybeGetQueryActionType = action => { + if (isRequestAction(action) && isRequestActionQuery(action)) { return action.type; } if ( isResponseAction(action) && - config.isRequestActionQuery(getRequestActionFromResponse(action)) + isRequestActionQuery(getRequestActionFromResponse(action)) ) { return getRequestActionFromResponse(action).type; } @@ -163,7 +165,7 @@ const maybeGetQueryActionType = (action, config) => { }; const maybeGetMutationData = (action, config) => { - if (config.isRequestAction(action) && action.meta.optimisticData) { + if (isRequestAction(action) && action.meta.optimisticData) { return action.meta.optimisticData; } @@ -179,7 +181,7 @@ const maybeGetMutationData = (action, config) => { isResponseAction(action) && isSuccessAction(action) && shouldBeNormalized(action, config) && - !config.isRequestActionQuery(getRequestActionFromResponse(action)) + !isRequestActionQuery(getRequestActionFromResponse(action)) ) { return action.response.data; } @@ -332,7 +334,7 @@ export default (state, action, config = defaultConfig) => { }; } - const queryActionType = maybeGetQueryActionType(action, config); + const queryActionType = maybeGetQueryActionType(action); if (queryActionType) { const queryType = diff --git a/packages/redux-requests/src/reducers/requests-keys-reducer.js b/packages/redux-requests/src/reducers/requests-keys-reducer.js index 9a0e047fa..45fb66c7b 100644 --- a/packages/redux-requests/src/reducers/requests-keys-reducer.js +++ b/packages/redux-requests/src/reducers/requests-keys-reducer.js @@ -1,8 +1,8 @@ -import defaultConfig from '../default-config'; +import { isRequestAction, isRequestActionQuery } from '../actions'; // TODO: this should be rewritten to more functional style, we need things like filter object helpers -export default (state, action, config = defaultConfig) => { - if (config.isRequestAction(action) && action.meta.requestKey) { +export default (state, action) => { + if (isRequestAction(action) && action.meta.requestKey) { let { queries, mutations, cache, requestsKeys } = state; if (!requestsKeys[action.type]) { @@ -26,7 +26,7 @@ export default (state, action, config = defaultConfig) => { action.meta.requestsCapacity && requestsKeys[action.type].length > action.meta.requestsCapacity ) { - const isQuery = config.isRequestActionQuery(action); + const isQuery = isRequestActionQuery(action); const requestsStorage = isQuery ? queries : mutations; const numberOfExceedingRequests = diff --git a/packages/redux-requests/src/reducers/requests-reducer.js b/packages/redux-requests/src/reducers/requests-reducer.js index 9a4829a22..73412b995 100644 --- a/packages/redux-requests/src/reducers/requests-reducer.js +++ b/packages/redux-requests/src/reducers/requests-reducer.js @@ -35,7 +35,7 @@ export default (config = defaultConfig) => (state = defaultState, action) => { ...requestsResetReducer( { queries, - mutations: mutationsReducer(state.mutations, action, config), + mutations: mutationsReducer(state.mutations, action), cache: cacheReducer(state.cache, action), ...progressReducer( { @@ -43,7 +43,6 @@ export default (config = defaultConfig) => (state = defaultState, action) => { uploadProgress: state.uploadProgress, }, action, - config, ), }, action, @@ -51,7 +50,6 @@ export default (config = defaultConfig) => (state = defaultState, action) => { requestsKeys: state.requestsKeys, }, action, - config, ), normalizedData, ssr: config.ssr ? ssrReducer(state.ssr, action, config) : null, diff --git a/packages/redux-requests/src/reducers/ssr-reducer.js b/packages/redux-requests/src/reducers/ssr-reducer.js index ec99f9a1b..d0d589356 100644 --- a/packages/redux-requests/src/reducers/ssr-reducer.js +++ b/packages/redux-requests/src/reducers/ssr-reducer.js @@ -1,5 +1,9 @@ import defaultConfig from '../default-config'; -import { getRequestActionFromResponse, isResponseAction } from '../actions'; +import { + getRequestActionFromResponse, + isResponseAction, + isRequestAction, +} from '../actions'; import { JOIN_REQUEST } from '../constants'; export default (state = [], action, config = defaultConfig) => { @@ -21,7 +25,7 @@ export default (state = [], action, config = defaultConfig) => { if ( config.ssr === 'client' && - config.isRequestAction(action) && + isRequestAction(action) && (action.meta.ssrResponse || action.meta.ssrError) ) { const indexToRemove = state.findIndex( diff --git a/packages/redux-requests/types/index.d.spec.ts b/packages/redux-requests/types/index.d.spec.ts index af664f0c2..97cb14400 100644 --- a/packages/redux-requests/types/index.d.spec.ts +++ b/packages/redux-requests/types/index.d.spec.ts @@ -77,7 +77,6 @@ handleRequests({ onError: (error, action) => ({ error }), onAbort: action => {}, takeLatest: true, - isRequestActionQuery: () => true, }); const reducer = (state = 0, action) => { diff --git a/packages/redux-requests/types/index.d.ts b/packages/redux-requests/types/index.d.ts index 270c6557e..3f7fb2c95 100644 --- a/packages/redux-requests/types/index.d.ts +++ b/packages/redux-requests/types/index.d.ts @@ -229,8 +229,6 @@ export interface HandleRequestConfig { onAbort?: (action: RequestAction, store: RequestsStore) => void; ssr?: null | 'client' | 'server'; disableRequestsPromise?: boolean; - isRequestAction?: (action: AnyAction) => boolean; - isRequestActionQuery?: (requestAction: RequestAction) => boolean; takeLatest?: boolean | FilterActions; normalize?: boolean; getNormalisationObjectKey?: (obj: any) => string;