Skip to content
This repository was archived by the owner on Sep 8, 2021. It is now read-only.

Commit a9cbafc

Browse files
committed
Merge pull request #9 from GoThinkster/gh-5
New approach for handling race condition in #5
2 parents 7eff738 + 1079730 commit a9cbafc

File tree

11 files changed

+386
-233
lines changed

11 files changed

+386
-233
lines changed

bin/main.js

Lines changed: 267 additions & 186 deletions
Large diffs are not rendered by default.

src/components/ProfileFavorites.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@ const store = require('../store');
99
class ProfileFavorites extends Profile {
1010
componentWillMount() {
1111
store.dispatch({
12-
type: 'PROFILE_PAGE_LOADED',
12+
type: 'PROFILE_FAVORITES_PAGE_LOADED',
1313
payload: Promise.all([
1414
agent.Profile.get(this.props.params.username),
1515
agent.Articles.favoritedBy(this.props.params.username)
1616
])
1717
});
1818
}
1919

20+
componentWillUnmount() {
21+
store.dispatch({ type: 'PROFILE_FAVORITES_PAGE_UNLOADED' });
22+
}
23+
2024
renderTabs() {
2125
return (
2226
<ul className="nav nav-pills outline-active">

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ class App extends React.Component {
4141
store.dispatch({
4242
type: 'APP_LOAD',
4343
token: token,
44-
payload: token ? agent.Auth.current() : null
44+
payload: token ? agent.Auth.current() : null,
45+
skipTracking: true
4546
});
4647
}
4748

src/middleware.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,41 @@ const agent = require('./agent');
44

55
exports.promiseMiddleware = store => next => action => {
66
if (isPromise(action.payload)) {
7-
store.dispatch({ type: 'ASYNC_START', subtype: action.type });
7+
let cancelled = false;
8+
if (!action.skipTracking) {
9+
store.dispatch({
10+
type: 'ASYNC_START',
11+
subtype: action.type,
12+
promise: action.payload,
13+
cancel: () => { cancelled = true; }
14+
});
15+
}
816
action.payload.then(
917
res => {
18+
if (cancelled) {
19+
return;
20+
}
1021
console.log('RESULT', res);
1122
action.payload = res;
23+
if (!action.skipTracking) {
24+
store.dispatch({ type: 'ASYNC_END', promise: action.payload });
25+
}
1226
store.dispatch(action);
1327
},
1428
error => {
29+
if (cancelled) {
30+
return;
31+
}
1532
console.log('ERROR', error);
1633
action.error = true;
1734
action.payload = error.response.body;
35+
if (!action.skipTracking) {
36+
store.dispatch({ type: 'ASYNC_END', promise: action.payload });
37+
}
1838
store.dispatch(action);
1939
}
2040
);
2141

22-
store.dispatch({ type: 'LOADING' });
23-
2442
return;
2543
}
2644

src/reducer.js

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ const auth = require('./reducers/auth');
55
const editor = require('./reducers/editor');
66
const home = require('./reducers/home');
77
const profile = require('./reducers/profile');
8+
const profileFavorites = require('./reducers/profileFavorites');
89
const settings = require('./reducers/settings');
910

1011
const defaultState = {
1112
appName: 'Conduit',
12-
token: null
13+
token: null,
14+
viewChangeCounter: 0
1315
};
1416

1517
module.exports = (state = defaultState, action) => {
@@ -18,6 +20,7 @@ module.exports = (state = defaultState, action) => {
1820
state = editor(state, action);
1921
state = home(state, action);
2022
state = profile(state, action);
23+
state = profileFavorites(state, action);
2124
state = settings(state, action);
2225
switch (action.type) {
2326
case 'APP_LOAD':
@@ -46,11 +49,15 @@ module.exports = (state = defaultState, action) => {
4649
state = Object.assign({}, state, { redirectTo: '/' });
4750
break;
4851
case 'ARTICLE_PAGE_UNLOADED':
49-
state = Object.assign({}, state);
50-
delete state.article;
51-
delete state.comments;
52-
delete state.commentErrors;
53-
break;
52+
if (state.outstandingActions) {
53+
state.outstandingActions.forEach(promise => promise.cancel());
54+
}
55+
return Object.assign({}, state, {
56+
article: null,
57+
comments: null,
58+
commentErrors: null,
59+
outstandingActions: null
60+
});
5461
case 'ADD_TAG':
5562
state = Object.assign({}, state);
5663
state.tagList.push(state.tagInput);
@@ -77,6 +84,18 @@ module.exports = (state = defaultState, action) => {
7784
const filter = comment => comment.id !== action.commentId;
7885
state.comments = _.filter(state.comments, filter);
7986
break;
87+
case 'ASYNC_START':
88+
const promise = Object.assign(action.promise, { cancel: action.cancel })
89+
return Object.assign({}, state, {
90+
outstandingActions: (state.outstandingActions || []).concat([promise])
91+
});
92+
case 'ASYNC_END':
93+
if (state.outstandingActions) {
94+
const filter = p => p !== action.promise;
95+
return Object.assign({}, state, {
96+
outstandingActions: state.outstandingActions.filter(filter)
97+
});
98+
}
8099
}
81100

82101
return state;

src/reducers/auth.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ module.exports = (state, action) => {
1616
break;
1717
case 'LOGIN_PAGE_UNLOADED':
1818
case 'REGISTER_PAGE_UNLOADED':
19-
state = Object.assign({}, state);
20-
const props = ['errors', 'username', 'email', 'password', 'inProgress']
21-
for (const key of props) {
22-
delete state[key];
19+
if (state.outstandingActions) {
20+
state.outstandingActions.forEach(promise => promise.cancel());
2321
}
24-
break;
22+
return Object.assign({}, state, {
23+
errors: null,
24+
username: null,
25+
email: null,
26+
password: null,
27+
inProgress: null,
28+
outstandingActions: null
29+
});
2530
case 'ASYNC_START':
2631
if (action.subtype === 'LOGIN' || action.subtype === 'REGISTER') {
2732
state = Object.assign({}, state);

src/reducers/editor.js

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,20 @@ module.exports = (state, action) => {
2323
}
2424
break;
2525
case 'EDITOR_PAGE_UNLOADED':
26-
state = Object.assign({}, state);
27-
const keys = [
28-
'title',
29-
'description',
30-
'body',
31-
'tagInput',
32-
'tagList',
33-
'errors',
34-
'articleSlug',
35-
'inProgress'
36-
];
37-
for (const key of keys) {
38-
delete state[key];
26+
if (state.outstandingActions) {
27+
state.outstandingActions.forEach(promise => promise.cancel());
3928
}
40-
break;
29+
return Object.assign({}, state, {
30+
title: null,
31+
description: null,
32+
body: null,
33+
tagInput: null,
34+
tagList: null,
35+
errors: null,
36+
articleSlug: null,
37+
inProgress: null,
38+
outstandingActions: null
39+
});
4140
case 'ARTICLE_SUBMITTED':
4241
state = Object.assign({}, state);
4342
state.inProgress = null;

src/reducers/home.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ module.exports = (state, action) => {
1212
});
1313
break;
1414
case 'HOME_PAGE_UNLOADED':
15-
state = Object.assign({}, state);
16-
delete state.articles;
17-
delete state.tags;
18-
delete state.tab;
19-
delete state.articlesCount;
20-
delete state.currentPage;
21-
break;
15+
if (state.outstandingActions) {
16+
state.outstandingActions.forEach(promise => promise.cancel());
17+
}
18+
return Object.assign({}, state, {
19+
articles: null,
20+
tags: null,
21+
tab: null,
22+
articlesCount: null,
23+
currentPage: null,
24+
outstandingActions: null
25+
});
2226
case 'CHANGE_TAB':
2327
state = Object.assign({}, state, {
2428
articles: action.payload.articles,

src/reducers/profile.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ module.exports = (state = defaultState, action) => {
1111
});
1212
break;
1313
case 'PROFILE_PAGE_UNLOADED':
14-
state = Object.assign({}, state);
15-
delete state.profile;
16-
delete state.articles;
17-
delete state.articlesCount;
18-
delete state.currentPage;
19-
break;
14+
if (state.outstandingActions) {
15+
state.outstandingActions.forEach(promise => promise.cancel());
16+
}
17+
return Object.assign({}, state, {
18+
profile: null,
19+
articles: null,
20+
articlesCount: null,
21+
currentPage: null,
22+
outstandingActions: null
23+
});
2024
case 'FOLLOW_USER':
2125
case 'UNFOLLOW_USER':
2226
state = Object.assign({}, state, {

src/reducers/profileFavorites.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
module.exports = (state = defaultState, action) => {
4+
switch (action.type) {
5+
case 'PROFILE_FAVORITES_PAGE_UNLOADED':
6+
if (state.outstandingActions) {
7+
state.outstandingActions.forEach(promise => promise.cancel());
8+
}
9+
return Object.assign({}, state, {
10+
outstandingActions: null
11+
});
12+
}
13+
14+
return state;
15+
};

src/reducers/settings.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ module.exports = (state, action) => {
1313
}
1414
break;
1515
case 'SETTINGS_PAGE_UNLOADED':
16-
state = Object.assign({}, state);
17-
for (const key of ['errors', 'inProgress']) {
18-
delete state[key];
16+
if (state.outstandingActions) {
17+
state.outstandingActions.forEach(promise => promise.cancel());
1918
}
20-
break;
19+
return Object.assign({}, state, {
20+
errors: null,
21+
inProgress: null,
22+
outstandingActions: null
23+
});
2124
case 'ASYNC_START':
2225
if (action.subtype === 'SETTINGS_SAVED') {
2326
state = Object.assign({}, state, { inProgress: true });

0 commit comments

Comments
 (0)