diff --git a/.eslintrc b/.eslintrc
index db431f3ab..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,14 @@
}
],
"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,
"react/no-array-index-key": 0,
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-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 430b19dea..140dfd6a4 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",
@@ -66,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/__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..680864b1e 100644
--- a/packages/redux-requests-react/src/index.js
+++ b/packages/redux-requests-react/src/index.js
@@ -1,8 +1,6 @@
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 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/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/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-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/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 8f8fabaa6..13aa1f7c3 100644
--- a/packages/redux-requests/src/actions.js
+++ b/packages/redux-requests/src/actions.js
@@ -27,29 +27,22 @@ 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,
+ requestType: undefined,
requestAction: action,
},
});
export const createErrorAction = (action, errorData) => ({
type: error(action.type),
- ...(isFSA(action)
- ? {
- payload: errorData,
- error: true,
- }
- : {
- error: errorData,
- }),
+ error: errorData,
meta: {
...action.meta,
+ requestType: undefined,
requestAction: action,
},
});
@@ -58,32 +51,15 @@ export const createAbortAction = action => ({
type: abort(action.type),
meta: {
...action.meta,
+ requestType: undefined,
requestAction: 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 &&
- !!(
- Array.isArray(actionPayload.request) ||
- actionPayload.request.url ||
- actionPayload.request.query ||
- actionPayload.request.promise ||
- actionPayload.request.response ||
- actionPayload.request.error
- ) &&
- !actionPayload.response &&
- !(actionPayload instanceof Error)
+ action?.meta?.requestType === 'QUERY' ||
+ action?.meta?.requestType === 'MUTATION'
);
};
@@ -100,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 } = getActionPayload(action);
+ return action?.meta?.requestType === 'QUERY';
+};
+
+export const isRequestActionMutation = action => {
+ return action?.meta?.requestType === 'MUTATION';
+};
- if (action.meta?.asMutation !== undefined) {
- return !action.meta.asMutation;
- }
+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 93bc0ce37..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,
@@ -5,7 +6,6 @@ import {
createSuccessAction,
createErrorAction,
createAbortAction,
- getActionPayload,
isRequestAction,
getRequestActionFromResponse,
isSuccessAction,
@@ -37,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',
@@ -51,25 +48,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 +84,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,50 +138,9 @@ 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(
- true,
- );
- });
-
- it('recognizes request FSA action', () => {
- expect(
- isRequestAction({
- type: 'ACTION',
- payload: { 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', () => {
@@ -232,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',
@@ -341,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/default-config.js b/packages/redux-requests/src/default-config.js
index 3f9ed2c4d..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,
@@ -6,11 +6,8 @@ export default {
onSuccess: null,
onError: null,
onAbort: null,
- cache: false,
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 aca69a734..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),
- config.cache && createRequestsCacheMiddleware(config),
+ createServerSsrMiddleware(requestsPromise),
+ config.ssr === 'client' && createClientSsrMiddleware(),
+ createRequestsCacheMiddleware(),
createSendRequestsMiddleware(config),
].filter(Boolean),
requestsPromise,
diff --git a/packages/redux-requests/src/index.js b/packages/redux-requests/src/index.js
index 813f0c438..c8e5edc71 100644
--- a/packages/redux-requests/src/index.js
+++ b/packages/redux-requests/src/index.js
@@ -16,6 +16,12 @@ export {
openWebsocket,
closeWebsocket,
} from './actions';
+export {
+ createQuery,
+ createMutation,
+ createLocalMutation,
+ createSubscription,
+} from './requests-creators';
export { default as handleRequests } from './handle-requests';
export {
getQuery,
@@ -24,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..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,15 +1,15 @@
-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);
}
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..d997add81 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';
+import { isRequestAction, isRequestActionQuery } from '../actions';
-const getIntervalKey = action => action.type + (action.meta?.requestKey || '');
+const getIntervalKey = action => action.type + (action.meta.requestKey || '');
const getKeys = requests =>
requests.map(v =>
@@ -10,7 +10,7 @@ const getKeys = requests =>
: v.toString(),
);
-export default (config = defaultConfig) => {
+export default () => {
let intervals = {};
return store => next => action => {
@@ -30,9 +30,9 @@ export default (config = defaultConfig) => {
intervals = intervalsCopy;
}
} else if (
- config.isRequestAction(action) &&
- config.isRequestActionQuery(action) &&
- !action.meta?.polled &&
+ isRequestAction(action) &&
+ isRequestActionQuery(action) &&
+ !action.meta.polled &&
intervals[getIntervalKey(action)]
) {
const intervalsCopy = { ...intervals };
@@ -42,9 +42,9 @@ export default (config = defaultConfig) => {
}
if (
- config.isRequestAction(action) &&
- config.isRequestActionQuery(action) &&
- action.meta?.poll &&
+ isRequestAction(action) &&
+ isRequestActionQuery(action) &&
+ 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..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,10 +7,10 @@ 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) &&
- action.meta?.cache &&
+ isRequestAction(action) &&
+ 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 ae996b388..ee45b2e86 100644
--- a/packages/redux-requests/src/middleware/create-send-requests-middleware.js
+++ b/packages/redux-requests/src/middleware/create-send-requests-middleware.js
@@ -1,48 +1,41 @@
import {
- getActionPayload,
createSuccessAction,
createErrorAction,
createAbortAction,
setDownloadProgress,
setUploadProgress,
+ isRequestAction,
} from '../actions';
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
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()),
);
@@ -54,33 +47,21 @@ 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)
: config.takeLatest;
const maybeCallOnRequestInterceptor = (action, config, store) => {
- const payload = getActionPayload(action);
-
if (
config.onRequest &&
- (!action.meta ||
- (action.meta.runOnRequest !== false && !action.meta.ssrDuplicate))
+ 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),
- },
+ payload: config.onRequest(action.payload, action, store),
};
}
@@ -88,22 +69,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),
- };
- }
-
+ if (action.meta.onRequest && !action.meta.ssrDuplicate) {
return {
...action,
- payload: {
- ...action.payload,
- request: action.meta.onRequest(payload.request, action, store),
- },
+ payload: action.meta.onRequest(action.payload, action, store),
};
}
@@ -111,7 +80,7 @@ const maybeCallOnRequestMeta = (action, store) => {
};
const maybeDispatchRequestAction = (action, next) => {
- if (!action.meta || !action.meta.silent) {
+ if (!action.meta.silent) {
action = next(action);
}
@@ -121,21 +90,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,
),
);
@@ -158,14 +127,13 @@ const defer = () => {
};
const getResponsePromises = (action, config, pendingRequests, store) => {
- const actionPayload = getActionPayload(action);
- const isBatchedRequest = Array.isArray(actionPayload.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)];
}
@@ -179,8 +147,8 @@ const getResponsePromises = (action, config, pendingRequests, store) => {
}
const responsePromises = isBatchedRequest
- ? actionPayload.request.map(r => driver(r, action, driverActions))
- : [driver(actionPayload.request, action, driverActions)];
+ ? action.payload.map(r => driver(r, action, driverActions))
+ : [driver(action.payload, action, driverActions)];
if (responsePromises[0].cancel) {
pendingRequests[lastActionKey] = responsePromises;
@@ -193,7 +161,7 @@ const maybeCallGetError = (action, error) => {
if (
error !== 'REQUEST_ABORTED' &&
!isActionRehydrated(action) &&
- action.meta?.getError
+ action.meta.getError
) {
throw action.meta.getError(error);
}
@@ -205,8 +173,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)]);
}
@@ -217,7 +185,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)]);
@@ -230,7 +198,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);
}
@@ -239,7 +207,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);
}
@@ -253,8 +221,7 @@ const getInitialBatchObject = responseKeys =>
}, {});
const maybeTransformBatchRequestResponse = (action, response) => {
- const actionPayload = getActionPayload(action);
- const isBatchedRequest = Array.isArray(actionPayload.request);
+ const isBatchedRequest = Array.isArray(action.payload);
const responseKeys = Object.keys(response[0]);
return isBatchedRequest && !isActionRehydrated(action)
@@ -268,10 +235,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 {
@@ -286,8 +253,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);
@@ -300,7 +267,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)) {
@@ -319,9 +286,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) {
next(action);
return allPendingRequests[action.requestType] || sleep();
@@ -329,18 +293,18 @@ 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);
}
- if (config.isRequestAction(action)) {
+ if (isRequestAction(action)) {
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(
@@ -353,18 +317,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);
}
@@ -378,7 +342,7 @@ const createSendRequestMiddleware = config => {
const errorAction = createErrorAction(action, error);
- if (!action.meta || !action.meta.silent) {
+ if (!action.meta.silent) {
store.dispatch(errorAction);
}
@@ -394,20 +358,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..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,15 +1,14 @@
-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 !== 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 06a3e4a2d..955f0f4e0 100644
--- a/packages/redux-requests/src/middleware/create-subscriptions-middleware.js
+++ b/packages/redux-requests/src/middleware/create-subscriptions-middleware.js
@@ -4,7 +4,7 @@ import {
websocketClosed,
openWebsocket,
closeWebsocket,
- getActionPayload,
+ isRequestActionSubscription,
} from '../actions';
import {
GET_WEBSOCKET,
@@ -13,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,
@@ -34,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)();
};
/*
@@ -178,8 +172,6 @@ export default ({
}
if ((!ws && WS && url && !lazy) || action.type === OPEN_WEBSOCKET) {
- const requestsStore = createRequestsStore(store);
-
clearLastReconnectTimeout();
clearLastHeartbeatTimeout();
@@ -197,7 +189,7 @@ export default ({
if (onOpen) {
onOpen(
- requestsStore,
+ store,
ws,
action.type === OPEN_WEBSOCKET ? action.props : null,
);
@@ -214,7 +206,7 @@ export default ({
ws.addEventListener('error', e => {
if (onError) {
- onError(e, requestsStore, ws);
+ onError(e, store, ws);
}
});
@@ -225,7 +217,7 @@ export default ({
clearLastHeartbeatTimeout();
if (onClose) {
- onClose(e, requestsStore, ws);
+ onClose(e, store, ws);
}
if (e.code !== 1000 && reconnectTimeout) {
@@ -256,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(
@@ -298,66 +290,52 @@ export default ({
ws.close(action.code);
ws = null;
return response;
- } else if (action.type === WEBSOCKET_OPENED) {
- Object.values(subscriptions).forEach(subscriptionAction => {
- const actionPayload = getActionPayload(subscriptionAction);
+ }
- if (actionPayload.subscription) {
+ if (action.type === WEBSOCKET_OPENED) {
+ Object.values(subscriptions).forEach(subscriptionAction => {
+ if (subscriptionAction.payload) {
ws.send(
JSON.stringify(
onSend
- ? onSend(actionPayload.subscription, subscriptionAction)
- : actionPayload.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 ||
- action.payload?.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,
};
}
- const actionPayload = getActionPayload(action);
-
- if (actionPayload.subscription && ws && active) {
+ if (action.payload && ws && active) {
ws.send(
JSON.stringify(
- onSend
- ? onSend(actionPayload.subscription, action)
- : actionPayload.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 fcbd767b0..975b21b9a 100644
--- a/packages/redux-requests/src/reducers/mutations-reducer.js
+++ b/packages/redux-requests/src/reducers/mutations-reducer.js
@@ -3,12 +3,13 @@ import {
isAbortAction,
isResponseAction,
getRequestActionFromResponse,
+ isRequestAction,
+ isRequestActionMutation,
} from '../actions';
export default (state, action) => {
- if (!isResponseAction(action)) {
- const mutationType =
- action.type + (action.meta?.requestKey ? action.meta.requestKey : '');
+ if (isRequestAction(action) && isRequestActionMutation(action)) {
+ const mutationType = action.type + (action.meta.requestKey || '');
return {
...state,
@@ -20,36 +21,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.payload ? action.payload : 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..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,22 +22,22 @@ export default (state, action, config) => {
};
}
- if (config.isRequestAction(action) && action.meta?.measureDownloadProgress) {
+ if (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 (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 c5a359a16..29a11f4fc 100644
--- a/packages/redux-requests/src/reducers/queries-reducer.js
+++ b/packages/redux-requests/src/reducers/queries-reducer.js
@@ -7,9 +7,13 @@ import {
getRequestActionFromResponse,
isErrorAction,
isSuccessAction,
+ isRequestAction,
+ isRequestActionQuery,
+ isRequestActionLocalMutation,
} from '../actions';
import { getQuery } from '../selectors';
-import { normalize, mergeData } from '../normalizers';
+import { normalize, mergeData, getDependentKeys } from '../normalizers';
+import { mapObject } from '../helpers';
import updateData from './update-data';
@@ -21,16 +25,59 @@ const getInitialQuery = normalized => ({
ref: {},
normalized,
usedKeys: normalized ? {} : null,
+ dependencies: normalized ? [] : null,
});
-const getDataFromResponseAction = action =>
- action.payload ? action.payload.data : action.response.data;
-
const shouldBeNormalized = (action, config) =>
- action.meta?.normalize !== undefined
+ action.meta.normalize !== undefined
? action.meta.normalize
: config.normalize;
+const addQueryAsDependency = (dependentQueries, dependencies, queryType) => {
+ dependencies.forEach(dependency => {
+ if (!dependentQueries[dependency]) {
+ dependentQueries = { ...dependentQueries, [dependency]: [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;
+};
+
+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) {
state = getInitialQuery(shouldBeNormalized(action, config));
@@ -64,7 +111,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 +123,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) {
@@ -102,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;
}
@@ -117,10 +164,9 @@ const maybeGetQueryActionType = (action, config) => {
return null;
};
-const updateNormalizedData = (normalizedData, action, config) => {
- if (config.isRequestAction(action) && action.meta?.optimisticData) {
- const [, newNormalizedData] = normalize(action.meta.optimisticData, config);
- return mergeData(normalizedData, newNormalizedData);
+const maybeGetMutationData = (action, config) => {
+ if (isRequestAction(action) && action.meta.optimisticData) {
+ return action.meta.optimisticData;
}
if (
@@ -128,47 +174,125 @@ 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 (
isResponseAction(action) &&
isSuccessAction(action) &&
shouldBeNormalized(action, config) &&
- !config.isRequestActionQuery(getRequestActionFromResponse(action))
+ !isRequestActionQuery(getRequestActionFromResponse(action))
) {
- const [, newNormalizedData] = normalize(
- getDataFromResponseAction(action),
- config,
- );
- return mergeData(normalizedData, newNormalizedData);
+ return action.response.data;
}
- if (action.meta?.localData) {
- const [, newNormalizedData] = normalize(action.meta.localData, config);
- return mergeData(normalizedData, newNormalizedData);
+ if (isRequestActionLocalMutation(action) && action.meta.localData) {
+ 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 (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 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,
@@ -177,14 +301,28 @@ 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,
config,
);
+ const dependencies = [
+ ...getDependentKeys(newdata, newNormalizedData, usedKeys),
+ ];
normalizedData = mergeData(normalizedData, newNormalizedData);
- prev[actionType] = { ...updatedQuery, data: newdata, usedKeys };
+ prev[actionType] = {
+ ...updatedQuery,
+ data: newdata,
+ dependencies,
+ usedKeys,
+ };
+
+ dependentQueries = addQueryAsDependency(
+ dependentQueries,
+ dependencies,
+ actionType,
+ );
} else {
prev[actionType] = updatedQuery;
}
@@ -192,10 +330,11 @@ export default (state, action, config = defaultConfig) => {
}, {}),
},
normalizedData,
+ dependentQueries,
};
}
- const queryActionType = maybeGetQueryActionType(action, config);
+ const queryActionType = maybeGetQueryActionType(action);
if (queryActionType) {
const queryType =
@@ -203,7 +342,7 @@ export default (state, action, config = defaultConfig) => {
? queryActionType + action.meta.requestKey
: queryActionType;
const updatedQuery = queryReducer(
- state.queries[queryType],
+ queries[queryType],
action,
queryActionType,
config,
@@ -211,37 +350,51 @@ 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,
};
}
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,
config,
);
+ const dependencies = [
+ ...getDependentKeys(data, newNormalizedData, usedKeys),
+ ];
+
return {
queries: {
- ...state.queries,
- [queryType]: { ...updatedQuery, data, usedKeys },
+ ...queries,
+ [queryType]: {
+ ...updatedQuery,
+ data,
+ usedKeys,
+ dependencies,
+ },
},
normalizedData: mergeData(normalizedData, newNormalizedData),
+ dependentQueries: addQueryAsDependency(
+ dependentQueries,
+ dependencies,
+ queryType,
+ ),
};
}
return {
queries: {
- ...state.queries,
+ ...queries,
[queryType]: updatedQuery,
},
normalizedData,
@@ -253,5 +406,7 @@ export default (state, action, config = defaultConfig) => {
: {
...state,
normalizedData,
+ queries,
+ dependentQueries,
};
};
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..11c93ba63
--- /dev/null
+++ b/packages/redux-requests/src/reducers/queries-reducer.simple.spec.js
@@ -0,0 +1,109 @@
+import defaultConfig from '../default-config';
+import {
+ createSuccessAction,
+ createErrorAction,
+ createAbortAction,
+} from '../actions';
+import { createQuery } from '../requests-creators';
+
+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,
+ dependencies: null,
+ };
+ const requestAction = createQuery('FETCH_BOOK', { 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 2e8c7fd3c..000000000
--- a/packages/redux-requests/src/reducers/queries-reducer.spec.js
+++ /dev/null
@@ -1,1114 +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: {},
- });
- });
-
- 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', () => {
- 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 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(
- 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..b9cbbf1c6
--- /dev/null
+++ b/packages/redux-requests/src/reducers/queries-reducer.with-mutations-with-query-request-key.spec.js
@@ -0,0 +1,145 @@
+import defaultConfig from '../default-config';
+import { createSuccessAction } from '../actions';
+import { createMutation, createLocalMutation } from '../requests-creators';
+
+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,
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ 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(
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ 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,
+ createLocalMutation('LOCAL_MUTATION_ACTION', {
+ mutations: {
+ FETCH_BOOK1: 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..8b3402ff8
--- /dev/null
+++ b/packages/redux-requests/src/reducers/queries-reducer.with-mutations.spec.js
@@ -0,0 +1,280 @@
+import defaultConfig from '../default-config';
+import {
+ createSuccessAction,
+ createErrorAction,
+ createAbortAction,
+} from '../actions';
+import { createMutation, createLocalMutation } from '../requests-creators';
+
+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,
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ 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(
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ mutations: {
+ FETCH_BOOK: {
+ updateDataOptimistic: data => `${data} suffix`,
+ },
+ },
+ },
+ )(),
+ { data: 'updated data' },
+ ),
+ defaultConfig,
+ ),
+ ).toEqual(initialState);
+ });
+
+ it('handles updateData customized per mutation', () => {
+ expect(
+ queriesReducer(
+ initialState,
+ createSuccessAction(
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ 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(
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ 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(
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ 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(
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ 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(
+ createMutation(
+ MUTATION_ACTION,
+ { url: '/books', method: 'post' },
+ {
+ 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,
+ createLocalMutation('LOCAL_MUTATION_ACTION', {
+ mutations: {
+ FETCH_BOOK: 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..1cfb2036c
--- /dev/null
+++ b/packages/redux-requests/src/reducers/queries-reducer.with-normalisation.spec.js
@@ -0,0 +1,1098 @@
+import defaultConfig from '../default-config';
+import { createSuccessAction, createErrorAction } from '../actions';
+import {
+ createQuery,
+ createMutation,
+ createLocalMutation,
+} from '../requests-creators';
+
+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('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 = createQuery(
+ 'FETCH_BOOKS',
+ { url: '/books' },
+ { 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 = createQuery(
+ 'FETCH_BOOK',
+ { url: '/book/1' },
+ { 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 = createMutation(
+ 'UPDATE_BOOK',
+ { url: '/book/1', method: 'put' },
+ { 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 = createMutation(
+ 'UPDATE_BOOK_AUTHOR',
+ { url: '/book/1/author', method: 'put' },
+ { 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 = createMutation(
+ 'ADD_BOOK_LIKER',
+ { url: '/book/1/liker', method: 'put' },
+ { 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,
+ error: null,
+ pending: 0,
+ pristine: true,
+ normalized: true,
+ usedKeys: [],
+ dependencies: [],
+ ref: {},
+ };
+ const requestAction = createQuery(
+ 'FETCH_BOOK',
+ { url: '/ ' },
+ {
+ normalize: true,
+ },
+ )();
+
+ it('should normalize data on query success', () => {
+ expect(
+ queriesReducer(
+ { queries: {}, normalizedData: {}, dependentQueries: {} },
+ createSuccessAction(requestAction, {
+ data: { id: '1', name: 'name' },
+ }),
+ defaultConfig,
+ ),
+ ).toEqual({
+ queries: {
+ FETCH_BOOK: {
+ ...defaultState,
+ pending: -1,
+ data: '@@1',
+ usedKeys: { '': ['id', 'name'] },
+ dependencies: ['@@1'],
+ },
+ },
+ normalizedData: { '@@1': { id: '1', name: 'name' } },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ });
+ });
+
+ 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'] },
+ dependencies: [],
+ ref: {},
+ },
+ },
+ normalizedData: {},
+ dependentQueries: {},
+ };
+ 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: {}, dependentQueries: {} },
+ 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'],
+ },
+ dependencies: ['@@1', '@@2', '@@3'],
+ },
+ },
+ normalizedData: {
+ '@@1': {
+ id: '1',
+ name: 'name',
+ nested: ['@@2', '@@3'],
+ },
+ '@@2': { id: '2', v: 2 },
+ '@@3': { id: '3', v: 3 },
+ },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ '@@2': ['FETCH_BOOK'],
+ '@@3': ['FETCH_BOOK'],
+ },
+ });
+ });
+
+ it('should merge normalized data on query success', () => {
+ expect(
+ queriesReducer(
+ {
+ queries: {},
+ normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } },
+ dependentQueries: {},
+ },
+ createSuccessAction(requestAction, {
+ data: { id: '1', a: 'd', c: 'c' },
+ }),
+ defaultConfig,
+ ),
+ ).toEqual({
+ queries: {
+ FETCH_BOOK: {
+ ...defaultState,
+ pending: -1,
+ data: '@@1',
+ usedKeys: { '': ['id', 'a', 'c'] },
+ dependencies: ['@@1'],
+ },
+ },
+ normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ });
+ });
+
+ it('should update normalized data on mutation success', () => {
+ expect(
+ queriesReducer(
+ {
+ queries: {
+ FETCH_BOOK: {
+ ...defaultState,
+ data: '@@1',
+ usedKeys: { '': ['id', 'a', 'b'] },
+ dependencies: ['@@1'],
+ },
+ },
+ normalizedData: { '@@1': { id: '1', a: 'a', b: 'b' } },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ },
+ createSuccessAction(
+ createMutation(
+ 'UPDATE_BOOK',
+ { url: '/', method: 'put' },
+ {
+ normalize: true,
+ },
+ )(),
+ {
+ data: { id: '1', a: 'd', c: 'c' },
+ },
+ ),
+ defaultConfig,
+ ),
+ ).toEqual({
+ queries: {
+ FETCH_BOOK: {
+ ...defaultState,
+ data: '@@1',
+ usedKeys: { '': ['id', 'a', 'b'] },
+ dependencies: ['@@1'],
+ },
+ },
+ normalizedData: { '@@1': { id: '1', a: 'd', b: 'b', c: 'c' } },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ });
+ });
+
+ 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'] },
+ dependencies: ['@@1'],
+ },
+ },
+ normalizedData: { '@@1': { id: '1', x: 1 } },
+ dependentQueries: { '@@1': ['FETCH_BOOK'] },
+ },
+ createSuccessAction(
+ createMutation(
+ 'ADD_BOOK',
+ { url: '/', method: 'put' },
+ {
+ 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'] },
+ dependencies: ['@@1', '@@2', '@@3'],
+ ref: {},
+ },
+ },
+ normalizedData: {
+ '@@1': { id: '1', x: 1 },
+ '@@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',
+ 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'],
+ },
+ dependencies: ['@@1'],
+ ref: {},
+ },
+ },
+ normalizedData: { '@@1': { id: '1', x: 1 } },
+ dependentQueries: { '@@1': ['FETCH_BOOK'] },
+ },
+ createLocalMutation('ADD_BOOK_LOCALLY', {
+ 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'] },
+ dependencies: ['@@1', '@@2'],
+ ref: {},
+ },
+ },
+ normalizedData: {
+ '@@1': { id: '1', x: 1 },
+ '@@2': { id: '2', x: 2 },
+ },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ '@@2': ['FETCH_BOOK'],
+ },
+ });
+ });
+
+ it('should update normalized query data with localData', () => {
+ expect(
+ queriesReducer(
+ {
+ queries: {
+ FETCH_BOOK: {
+ data: ['@@1'],
+ pending: 0,
+ error: null,
+ normalized: true,
+ dependencies: ['@@1'],
+ ref: {},
+ usedKeys: {
+ '': ['id', 'x'],
+ },
+ },
+ },
+ normalizedData: { '@@1': { id: '1', x: 1 } },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ },
+ createLocalMutation('UPDATE_BOOK_LOCALLY', {
+ localData: { id: '1', x: 2 },
+ })(),
+ defaultConfig,
+ ),
+ ).toEqual({
+ queries: {
+ FETCH_BOOK: {
+ data: ['@@1'],
+ pending: 0,
+ error: null,
+ normalized: true,
+ dependencies: ['@@1'],
+ ref: {},
+ usedKeys: {
+ '': ['id', 'x'],
+ },
+ },
+ },
+ normalizedData: {
+ '@@1': { id: '1', x: 2 },
+ },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ });
+ });
+
+ it('should update normalized query data with optimisticData', () => {
+ expect(
+ queriesReducer(
+ {
+ queries: {
+ FETCH_BOOK: {
+ data: ['@@1'],
+ pending: 0,
+ error: null,
+ normalized: true,
+ dependencies: ['@@1'],
+ ref: {},
+ usedKeys: {
+ '': ['id', 'x'],
+ },
+ },
+ },
+ normalizedData: { '@@1': { id: '1', x: 1 } },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ },
+ createMutation(
+ 'UPDATE_BOOK',
+ { url: '/books', method: 'post' },
+ {
+ optimisticData: { id: '1', x: 2 },
+ },
+ )(),
+ defaultConfig,
+ ),
+ ).toEqual({
+ queries: {
+ FETCH_BOOK: {
+ data: ['@@1'],
+ pending: 0,
+ error: null,
+ normalized: true,
+ dependencies: ['@@1'],
+ ref: {},
+ usedKeys: {
+ '': ['id', 'x'],
+ },
+ },
+ },
+ normalizedData: {
+ '@@1': { id: '1', x: 2 },
+ },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ });
+ });
+
+ it('should update normalized query data with revertedData on response error', () => {
+ expect(
+ queriesReducer(
+ {
+ queries: {
+ FETCH_BOOK: {
+ data: ['@@1'],
+ pending: 0,
+ error: null,
+ normalized: true,
+ dependencies: ['@@1'],
+ ref: {},
+ usedKeys: {
+ '': ['id', 'x'],
+ },
+ },
+ },
+ normalizedData: { '@@1': { id: '1', x: 2 } },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ },
+ createErrorAction(
+ createMutation(
+ 'UPDATE_BOOK',
+ { url: '/books', method: 'post' },
+ {
+ optimisticData: { id: '1', x: 2 },
+ revertedData: { id: '1', x: 1 },
+ },
+ )(),
+ ),
+ defaultConfig,
+ ),
+ ).toEqual({
+ queries: {
+ FETCH_BOOK: {
+ data: ['@@1'],
+ pending: 0,
+ error: null,
+ normalized: true,
+ dependencies: ['@@1'],
+ ref: {},
+ usedKeys: {
+ '': ['id', 'x'],
+ },
+ },
+ },
+ normalizedData: {
+ '@@1': { id: '1', x: 1 },
+ },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ });
+ });
+
+ it('should allow custom shouldObjectBeNormalized and getNormalisationObjectKey', () => {
+ expect(
+ queriesReducer(
+ { queries: {}, normalizedData: {}, dependentQueries: {} },
+ 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',
+ dependencies: ['@@1'],
+ usedKeys: { '': ['_id', 'name'] },
+ },
+ },
+ normalizedData: { '@@1': { _id: '1', name: 'name' } },
+ dependentQueries: {
+ '@@1': ['FETCH_BOOK'],
+ },
+ });
+ });
+ });
+ });
+});
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..bfbd2c5a1
--- /dev/null
+++ b/packages/redux-requests/src/reducers/queries-reducer.with-request-key.js
@@ -0,0 +1,107 @@
+import defaultConfig from '../default-config';
+import {
+ createSuccessAction,
+ createErrorAction,
+ createAbortAction,
+} from '../actions';
+import { createQuery } from '../requests-creators';
+
+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 = createQuery(
+ 'FETCH_BOOK',
+ { url: '/ ' },
+ {
+ 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: {},
+ });
+ });
+ });
+ });
+});
diff --git a/packages/redux-requests/src/reducers/requests-keys-reducer.js b/packages/redux-requests/src/reducers/requests-keys-reducer.js
index e6b588910..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 !== undefined) {
+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-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..73412b995 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),
cache: cacheReducer(state.cache, action),
...progressReducer(
{
@@ -54,7 +43,6 @@ export default (config = defaultConfig) => (state = defaultState, action) => {
uploadProgress: state.uploadProgress,
},
action,
- config,
),
},
action,
@@ -62,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/requests-reducer.spec.js b/packages/redux-requests/src/reducers/requests-reducer.spec.js
index 9c78e17eb..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(
{
@@ -72,6 +77,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
});
@@ -85,6 +91,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
REQUEST_2: {
@@ -94,6 +101,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
});
@@ -110,6 +118,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
REQUEST_2: {
@@ -119,6 +128,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
});
@@ -132,6 +142,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
REQUEST_2: {
@@ -141,6 +152,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
});
@@ -186,7 +198,7 @@ describe('reducers', () => {
requestsKeys: {},
ssr: null,
},
- { type: 'REQUEST', request: { url: '/' } },
+ createQuery('REQUEST', { url: '/' })(),
);
let state = {
@@ -198,6 +210,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -208,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: {
@@ -229,6 +239,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -239,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);
@@ -255,6 +266,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -282,6 +294,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -309,6 +322,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -339,6 +353,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -355,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);
@@ -376,6 +391,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -411,6 +427,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -432,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);
@@ -455,6 +472,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -499,6 +517,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -543,6 +562,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -571,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,
@@ -583,7 +603,7 @@ describe('reducers', () => {
},
},
},
- };
+ )();
state = reducer(state, mutationWithOptimisticUpdate);
@@ -596,6 +616,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -643,6 +664,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -692,6 +714,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -737,6 +760,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
@@ -750,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: {
@@ -770,6 +791,7 @@ describe('reducers', () => {
pristine: false,
normalized: false,
usedKeys: null,
+ dependencies: null,
ref: {},
},
},
diff --git a/packages/redux-requests/src/reducers/requests-reset-reducer.js b/packages/redux-requests/src/reducers/requests-reset-reducer.js
index 67b4cfc75..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 =>
@@ -20,6 +17,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: {},
diff --git a/packages/redux-requests/src/reducers/ssr-reducer.js b/packages/redux-requests/src/reducers/ssr-reducer.js
index bee181e8e..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,8 +25,8 @@ export default (state = [], action, config = defaultConfig) => {
if (
config.ssr === 'client' &&
- config.isRequestAction(action) &&
- (action.meta?.ssrResponse || action.meta?.ssrError)
+ isRequestAction(action) &&
+ (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
new file mode 100644
index 000000000..5eb00f3b0
--- /dev/null
+++ b/packages/redux-requests/src/requests-creators.js
@@ -0,0 +1,37 @@
+const createRequest = requestType => (type, requestConfig, metaConfig) => {
+ const actionCreator = (...params) => ({
+ type,
+ payload:
+ typeof requestConfig === 'function'
+ ? requestConfig(...params)
+ : requestConfig,
+ meta: {
+ ...(typeof metaConfig === 'function'
+ ? metaConfig(...params)
+ : metaConfig),
+ requestType,
+ },
+ });
+ 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/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' },
+ });
+ });
+ });
+});
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.spec.ts b/packages/redux-requests/types/index.d.spec.ts
index cff60f65b..97cb14400 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,74 +16,82 @@ 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 }),
onAbort: action => {},
takeLatest: true,
- 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 +105,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 19c5e1db1..3f7fb2c95 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,40 +47,44 @@ interface RequestActionMeta {
runOnAbort?: boolean;
measureDownloadProgress?: boolean;
measureUploadProgress?: boolean;
- [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 LocalMutationAction = {
- type?: string;
- meta: {
- mutations?: {
- [actionType: string]: {
- updateData: ModifyData;
- local: true;
- };
- };
- localData?: any;
- [extraProperty: string]: any;
+interface QueryMeta
+ extends RequestMeta {
+ requestType: 'QUERY';
+ cache?: boolean | number;
+ cacheKey?: string;
+ poll?: number;
+ dependentRequestsNumber?: number;
+ isDependentRequest?: boolean;
+}
+
+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 LocalMutationMeta {
+ requestType: 'LOCAL_MUTATION';
+ mutations?: {
+ [actionType: string]: ModifyData;
};
-};
+ localData?: any;
+}
-interface SubscriptionActionMeta {
+interface SubscriptionMeta {
+ requestType: 'SUBSCRIPTION';
requestKey?: string;
normalize?: boolean;
mutations?: {
@@ -116,34 +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;
- }
- | {
- type?: string;
- payload: {
- subscription: any;
- };
- meta?: SubscriptionActionMeta;
- };
+export interface Query {
+ type: string;
+ payload: any | any[];
+ meta?: QueryMeta;
+}
+
+export interface Mutation {
+ type: string;
+ payload: any | any[];
+ meta?: MutationMeta;
+}
-type ResponseData<
- Request extends (...args: any[]) => RequestAction
-> = ReturnType['meta']>['getData']>;
+export interface LocalMutation {
+ type: string;
+ meta: LocalMutationMeta;
+}
-type ActionTypeModifier = (actionType: string) => string;
+export interface Subscription {
+ type: string;
+ payload: any;
+ meta?: SubscriptionMeta;
+}
-export const success: ActionTypeModifier;
+export interface Dispatch {
+ (action: Action): Action extends
+ | Query
+ | Mutation
+ ? Promise<{
+ data?: Data;
+ error?: any;
+ isAborted?: true;
+ action: any;
+ }>
+ : Action;
+}
-export const error: ActionTypeModifier;
+interface RequestsStore extends Store {
+ dispatch: Dispatch;
+}
-export const abort: ActionTypeModifier;
+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;
@@ -195,11 +227,8 @@ 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;
- isRequestActionQuery?: (requestAction: RequestAction) => boolean;
takeLatest?: boolean | FilterActions;
normalize?: boolean;
getNormalisationObjectKey?: (obj: any) => string;
@@ -293,8 +322,8 @@ export const joinRequest: (
rehydrate: boolean;
};
-export interface QueryState {
- data: QueryStateData;
+export interface QueryState {
+ data: Data;
error: any;
pending: number;
loading: boolean;
@@ -303,24 +332,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;
- multiple?: boolean;
- defaultData?: any;
},
-): 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;
- multiple?: boolean;
- defaultData?: any;
-}): (state: any) => QueryState;
+}): (state: any) => QueryState;
export interface MutationState {
pending: number;
@@ -333,13 +356,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 c30dd5410..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"
@@ -8722,7 +8729,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==
@@ -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"