Skip to content

Commit 040c74d

Browse files
authored
Merge pull request #2893 from asauber/remove-auto-refetching
Remove automatic refetching logic and state invalidation from action creator generators
2 parents b3c93cb + 8aa5b9f commit 040c74d

File tree

5 files changed

+14
-170
lines changed

5 files changed

+14
-170
lines changed

src/api/ad-hoc/linodes.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ export function rebuildLinode(id, config = null) {
4848
return async (dispatch) => {
4949
// Add this manually so StatusDropdown will start polling.
5050
dispatch(actions.one({ status: 'rebuilding' }, id));
51-
await dispatch(actions.disks.invalidate([id], false));
5251
await dispatch(actions.disks.many(makeNormalResponse(rsp, 'disks'), id));
53-
await dispatch(actions.configs.invalidate([id], false));
5452
await dispatch(actions.configs.many(makeNormalResponse(rsp, 'configs'), id));
5553
};
5654
}

src/api/external.js

Lines changed: 11 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { fetch } from './fetch';
22
import {
3-
ONE, MANY, DELETE, POST, PUT, generateDefaultStateFull, isPlural,
3+
ONE, MANY, DELETE, POST, PUT,
44
} from './internal';
55

66

@@ -10,60 +10,6 @@ export function fullyLoadedObject(object) {
1010
return object && !!Object.keys(object).filter(key => !key.startsWith('_')).length;
1111
}
1212

13-
/*
14-
* This function applies the ids to the config to try to find
15-
* the particular object we are talking about.
16-
*
17-
* Examples:
18-
* getStateOfSpecificResource(require('~/api/generic/linodes').config.generic,
19-
* store.api, ['1234'])
20-
* returns store.api.linodes.linodes['1234']._generic.generic
21-
*
22-
* getStateOfSpecificResource(require('~/api/generic/linodes').config.generic,
23-
* store.api, ['1234', '1'])
24-
* returns store.api.linodes.linodes['1234']._generic.generic['1']
25-
*/
26-
export function getStateOfSpecificResource(config, state, ids) {
27-
const path = [];
28-
let root = config;
29-
const match = (sub, parent) => {
30-
if (parent.subresources[sub] === root) {
31-
path.push(sub);
32-
}
33-
};
34-
while (root.parent) {
35-
const parent = root.parent;
36-
Object.keys(parent.subresources).forEach(s => match(s, parent));
37-
root = parent;
38-
}
39-
let refined = state.api[root.name];
40-
const _ids = [...ids];
41-
let current = root;
42-
let name = null;
43-
44-
while (current !== config) {
45-
name = path.pop();
46-
if (isPlural(current)) {
47-
refined = refined[current.name][_ids.shift()][name];
48-
} else {
49-
// Not totally sure how things would work with a plural inside a singular
50-
refined = refined[current.name][name];
51-
}
52-
current = current.subresources[name];
53-
}
54-
55-
if (_ids.length) {
56-
// Should only be a plural one anyway, but just in case.
57-
const objects = refined[current.name];
58-
if (!isPlural(current)) {
59-
return objects;
60-
}
61-
62-
return objects[_ids[_ids.length - 1]];
63-
}
64-
return refined;
65-
}
66-
6713
/*
6814
* Apply a filter to all returned objects so only selected fields (or none)
6915
* will be updated.
@@ -109,119 +55,46 @@ function genThunkOne(config, actions) {
10955
* The results are returned.
11056
*/
11157
function genThunkPage(config, actions) {
112-
function fetchPage(page = 0, ids = [], resourceFilter, storeInState = true,
113-
fetchBeganAt, headers) {
114-
return async (dispatch, getState) => {
58+
function fetchPage(page = 0, ids = [], resourceFilter, headers) {
59+
return async (dispatch) => {
11560
const endpoint = `${config.endpoint(...ids, '')}?page=${page + 1}`;
116-
11761
const resources = await dispatch(fetch.get(endpoint, undefined, headers));
11862
resources[config.name] = resources.data || [];
119-
120-
const now = fetchBeganAt || new Date();
121-
122-
// The filterResources function must acknowledge that it may not be getting
123-
// the most up-to-date results.
12463
const filteredResources = filterResources(config, resources, resourceFilter);
125-
126-
// Refetch any existing results that have been updated since fetchBeganAt.
127-
const fetchOne = genThunkOne(config, actions);
128-
const objects = await Promise.all(filteredResources[config.name].map(async (resource) => {
129-
const existingResourceState = getStateOfSpecificResource(
130-
config, getState(), [...ids, resource.id]);
131-
if (existingResourceState) {
132-
const hasActualState = fullyLoadedObject(existingResourceState);
133-
134-
const updatedAt = hasActualState && existingResourceState.__updatedAt || fetchBeganAt;
135-
if (updatedAt > now) {
136-
try {
137-
return await dispatch(fetchOne([...ids, resource.id]));
138-
} catch (e) {
139-
// There's some case where fetching will 404 because there's some item in internal
140-
// state that is not returned from the API (or was because the API is returning
141-
// deleted items?). Catch that and don't blow up; log for good measure.
142-
// eslint-disable-next-line
143-
console.trace(e);
144-
return Promise.resolve();
145-
}
146-
}
147-
}
148-
return resource;
149-
}));
150-
151-
const updatedResources = {
152-
...filteredResources,
153-
[config.name]: objects,
154-
};
155-
156-
if (storeInState) {
157-
await dispatch(actions.many(updatedResources, ...ids));
158-
}
159-
160-
return updatedResources;
64+
await dispatch(actions.many(filteredResources, ...ids));
65+
return filteredResources;
16166
};
16267
}
16368

16469
return fetchPage;
16570
}
16671

16772
/*
168-
* This function fetches all pages. If the final page indicates the total results
169-
* were not fetched, it restarts the process. If it is fixing partial invalidation,
170-
* it waits to invalidate and store the new data into the store until all the
171-
* pages have been fetched.
73+
* This function fetches all pages, stores them in Redux and returns the result
17274
*/
17375
function genThunkAll(config, actions, fetchPage) {
17476
function fetchAll(ids = [], resourceFilter, options) {
175-
return async (dispatch, getState) => {
176-
let state = getStateOfSpecificResource(config, getState(), ids) ||
177-
generateDefaultStateFull(config);
178-
179-
const fetchBeganAt = new Date();
180-
77+
return async (dispatch) => {
18178
// Grab first page so we know how many there are.
182-
const storeInState = !state.invalid; // Store the fetched results later
18379
const resource = await dispatch(
184-
fetchPage(0, ids, resourceFilter, storeInState, fetchBeganAt, options));
80+
fetchPage(0, ids, resourceFilter, options));
18581
const resources = [resource];
186-
state = getStateOfSpecificResource(config, getState(), ids);
18782

188-
// Grab all pages we know about. If state.invalid, don't save the result
189-
// in the redux store until we've got all the results.
83+
// Grab all pages we know about and store them in Redux.
19084
const requests = [];
19185
for (let i = 1; i < resources[0].pages; i += 1) {
192-
requests.push(fetchPage(i, ids, resourceFilter, !state.invalid, fetchBeganAt, options));
86+
requests.push(fetchPage(i, ids, resourceFilter, options));
19387
}
19488

89+
// Gather all the results for for return to the caller
19590
const allPages = await Promise.all(requests.map(r => dispatch(r)));
19691
allPages.forEach(function (response) {
19792
resources.push(response);
19893
});
199-
200-
// If the number of total results returned by the last page is different
201-
// than the total number of results we have, restart.
202-
const numFetchedResources = resources.map(
203-
resource => resource[config.name].length
204-
).reduce((a, b) => a + b);
205-
const numExpectedResources = resources[resources.length - 1].results;
206-
if (numFetchedResources !== numExpectedResources) {
207-
return await dispatch(fetchAll(ids, resourceFilter));
208-
}
209-
210-
// Waits until all things have been fetched so we don't have UI flashes
211-
// while we wait for requests to be made and the results saved in the redux store.
212-
if (state.invalid) {
213-
dispatch(actions.invalidate());
214-
215-
await Promise.all(resources.map(page =>
216-
dispatch(actions.many(page, ...ids))));
217-
}
218-
219-
// The resulting object will look like this, return it so anyone can use it immediately.
22094
const res = {
22195
...resources[resources.length - 1],
22296
[config.name]: resources.reduce((a, b) => [...a, ...b[config.name]], []),
22397
};
224-
22598
return res;
22699
};
227100
}

src/api/internal.js

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ export function genActions(config) {
8080
actions[fns[feature]] = actionGenerators[feature](config);
8181
}
8282
});
83-
actions.invalidate = (ids = [], partial = false) =>
84-
({ type: `GEN@${fullyQualified(config)}/INVALIDATE`, ids, partial });
8583
if (config.subresources) {
8684
Object.keys(config.subresources).forEach((key) => {
8785
const subresource = config.subresources[key];
@@ -192,29 +190,6 @@ export class ReducerGenerator {
192190
};
193191
}
194192

195-
static invalidate(config, state, action) {
196-
let newState = { ...state };
197-
if (action.partial) {
198-
// Keep data but mark as invalid to be overwritten
199-
// when new data is available by thunks.all.
200-
if (action.ids.length) {
201-
// action.ids should only ever be just 1 id
202-
newState[config.name][action.ids[0]].invalid = true;
203-
} else {
204-
newState.invalid = true;
205-
}
206-
} else {
207-
if (action.ids.length) {
208-
// action.ids should only ever be just 1 id
209-
delete newState[config.name][action.ids[0]];
210-
} else {
211-
newState = generateDefaultStateFull(config);
212-
}
213-
}
214-
215-
return { ...newState, __updatedAt: new Date() };
216-
}
217-
218193
static subresource(config, state, action) {
219194
let path = action.type.substr(action.type.indexOf('@') + 1);
220195
path = path.substr(0, path.indexOf('/'));
@@ -266,8 +241,6 @@ export class ReducerGenerator {
266241
return ReducerGenerator.many(config, state, action);
267242
case `GEN@${fullyQualified(config)}/DELETE`:
268243
return ReducerGenerator.del(config, state, action);
269-
case `GEN@${fullyQualified(config)}/INVALIDATE`:
270-
return ReducerGenerator.invalidate(config, state, action);
271244
// eslint-disable-next-line no-case-declarations
272245
default:
273246
if (action.type && action.type.split('/')[0].indexOf(subTypeMatch) === 0) {

src/components/PollingWrapper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const mapStateToProps = (state) => ({
104104
const mapDispatchToProps = (dispatch) => ({
105105
dispatch,
106106
fetchEventsPage(headers = null) {
107-
return dispatch(api.events.page(0, [], null, true, null, headers));
107+
return dispatch(api.events.page(0, [], null, headers));
108108
},
109109
});
110110
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PollingWrapper));

src/components/notifications/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class Notifications extends Component {
123123

124124
fetchEventsPage(headers = null) {
125125
const { dispatch } = this.props;
126-
return dispatch(api.events.page(0, [], null, true, null, headers));
126+
return dispatch(api.events.page(0, [], null, headers));
127127
}
128128

129129
render() {
@@ -188,7 +188,7 @@ const mapDispatchToProps = (dispatch) => ({
188188
}
189189
},
190190
fetchEventsPage(headers = null) {
191-
return dispatch(api.events.page(0, [], null, true, null, headers));
191+
return dispatch(api.events.page(0, [], null, headers));
192192
},
193193
});
194194

0 commit comments

Comments
 (0)