Skip to content

Commit 2b40c9d

Browse files
authored
Use state in processes in real world app (#404)
* Use state in real world app processes * Update TypeScript version for real world app and use optional chaining
1 parent 30258c0 commit 2b40c9d

13 files changed

+1727
-1307
lines changed

realworld/package-lock.json

+1,498-1,070
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

realworld/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@
3232
"eslint-plugin-import": "2.20.2",
3333
"npm-run-all": "4.1.5",
3434
"sinon": "^9.0.1",
35-
"typescript": "~3.4.5"
35+
"typescript": "~3.9.5"
3636
}
3737
}

realworld/src/interfaces.d.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ export interface Settings extends User, ResourceBased {
5353
}
5454

5555
export interface Article extends ResourceBased {
56-
item: ArticleItem;
56+
item?: ArticleItem;
5757
comments: Comment[];
5858
newComment: string;
5959
}
6060

6161
export interface Feed extends ResourceBased {
6262
category: string;
63-
tagName: string;
63+
tagName?: string;
6464
filter: string;
65-
items: ArticleItem[];
65+
items?: ArticleItem[];
6666
offset: number;
6767
page: number;
6868
total: number;
@@ -95,22 +95,22 @@ export interface Editor extends ResourceBased {
9595
}
9696

9797
export interface Profile {
98-
user: AuthorProfile & ResourceBased;
99-
feed: Feed;
98+
user: Partial<AuthorProfile & ResourceBased>;
99+
feed: Partial<Feed>;
100100
}
101101

102102
interface State {
103-
settings: Settings;
103+
settings: Partial<Settings>;
104104
article: {
105105
[index: string]: Article;
106106
};
107-
feed: Feed;
108-
session: Session;
109-
profile: Profile;
107+
feed: Partial<Feed>;
108+
session?: Session;
109+
profile: Partial<Profile>;
110110
routing: Routing;
111111
tags: string[];
112-
errors: Errors;
112+
errors?: Errors;
113113
login: ResourceBased;
114114
register: ResourceBased;
115-
editor: Editor;
115+
editor: Partial<Editor>;
116116
}

realworld/src/processes/articleProcesses.ts

+37-43
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createProcess } from '@dojo/framework/stores/process';
2-
import { remove, replace } from '@dojo/framework/stores/state/operations';
32
import { getHeaders, commandFactory } from './utils';
43
import { baseUrl } from '../config';
54
import {
@@ -11,66 +10,64 @@ import {
1110
NewCommentPayload
1211
} from './interfaces';
1312

14-
const startLoadingArticleCommand = commandFactory(({ path, payload: { slug } }) => {
15-
return [
16-
replace(path('article', slug, 'item'), undefined),
17-
replace(path('article', slug, 'comments'), []),
18-
replace(path('article', slug, 'isLoading'), true),
19-
replace(path('article', slug, 'isLoaded'), false)
20-
];
13+
const startLoadingArticleCommand = commandFactory(({ state, payload: { slug } }) => {
14+
state.article[slug] = {
15+
item: undefined,
16+
comments: [],
17+
newComment: '',
18+
isLoading: true,
19+
isLoaded: false
20+
};
2121
});
2222

23-
const loadArticleCommand = commandFactory<SlugPayload>(async ({ get, path, payload: { slug } }) => {
24-
const token = get(path('session', 'token'));
23+
const loadArticleCommand = commandFactory<SlugPayload>(async ({ state, payload: { slug } }) => {
24+
const token = state.session?.token;
2525
const response = await fetch(`${baseUrl}/articles/${slug}`, {
2626
headers: getHeaders(token)
2727
});
2828
const json = await response.json();
2929

30-
return [
31-
replace(path('article', slug, 'item'), json.article),
32-
replace(path('article', slug, 'isLoading'), false),
33-
replace(path('article', slug, 'isLoaded'), true)
34-
];
30+
state.article[slug].item = json.article;
31+
state.article[slug].isLoading = false;
32+
state.article[slug].isLoaded = true;
3533
});
3634

3735
const favoriteArticleCommand = commandFactory<FavoriteArticlePayload>(
38-
async ({ get, path, payload: { slug, favorited } }) => {
39-
const token = get(path('session', 'token'));
36+
async ({ state, payload: { slug, favorited } }) => {
37+
const token = state.session?.token;
4038
const response = await fetch(`${baseUrl}/articles/${slug}/favorite`, {
4139
method: favorited ? 'delete' : 'post',
4240
headers: getHeaders(token)
4341
});
4442
const json = await response.json();
45-
return [replace(path('article', slug, 'item'), json.article)];
43+
state.article[slug].item = json.article;
4644
}
4745
);
4846

4947
const followUserCommand = commandFactory<Required<FollowUserPayload>>(
50-
async ({ get, path, payload: { slug, username, following } }) => {
51-
const token = get(path('session', 'token'));
48+
async ({ state,payload: { slug, username, following } }) => {
49+
const token = state.session?.token;
5250
const response = await fetch(`${baseUrl}/profiles/${username}/follow`, {
5351
method: following ? 'delete' : 'post',
5452
headers: getHeaders(token)
5553
});
5654
const json = await response.json();
57-
const article = get(path('article', slug, 'item'));
58-
return [replace(path('article', slug, 'item'), { ...article, author: json.profile })];
55+
const article = state.article[slug]?.item;
56+
state.article[slug].item = article && { ...article, author: json.profile };
5957
}
6058
);
6159

62-
const loadCommentsCommand = commandFactory<SlugPayload>(async ({ get, path, payload: { slug } }) => {
63-
const token = get(path('session', 'token'));
60+
const loadCommentsCommand = commandFactory<SlugPayload>(async ({ state, payload: { slug } }) => {
61+
const token = state.session?.token;
6462
const response = await fetch(`${baseUrl}/articles/${slug}/comments`, {
6563
headers: getHeaders(token)
6664
});
6765
const json = await response.json();
68-
69-
return [replace(path('article', slug, 'comments'), json.comments)];
66+
state.article[slug].comments = json.comments;
7067
});
7168

72-
const addCommentCommand = commandFactory<AddCommentPayload>(async ({ get, path, payload: { slug, newComment } }) => {
73-
const token = get(path('session', 'token'));
69+
const addCommentCommand = commandFactory<AddCommentPayload>(async ({ state, payload: { slug, newComment } }) => {
70+
const token = state.session?.token;
7471
const requestPayload = {
7572
comment: {
7673
body: newComment
@@ -82,21 +79,19 @@ const addCommentCommand = commandFactory<AddCommentPayload>(async ({ get, path,
8279
body: JSON.stringify(requestPayload)
8380
});
8481
const json = await response.json();
85-
const comments = get(path('article', slug, 'comments'));
82+
const comments = state.article[slug].comments;
8683

87-
return [
88-
replace(path('article', slug, 'comments'), [...comments, json.comment]),
89-
replace(path('article', slug, 'newComment'), '')
90-
];
84+
state.article[slug].comments = [...comments, json.comment];
85+
state.article[slug].newComment = '';
9186
});
9287

93-
const deleteCommentCommand = commandFactory<DeleteCommentPayload>(async ({ at, get, path, payload: { slug, id } }) => {
94-
const token = get(path('session', 'token'));
88+
const deleteCommentCommand = commandFactory<DeleteCommentPayload>(async ({ state, payload: { slug, id } }) => {
89+
const token = state.session?.token;
9590
await fetch(`${baseUrl}/articles/${slug}/comments/${id}`, {
9691
method: 'delete',
9792
headers: getHeaders(token)
9893
});
99-
const comments = get(path('article', slug, 'comments'));
94+
const comments = state.article[slug].comments;
10095
let index = -1;
10196
for (let i = 0; i < comments.length; i++) {
10297
if (comments[i].id === id) {
@@ -106,22 +101,21 @@ const deleteCommentCommand = commandFactory<DeleteCommentPayload>(async ({ at, g
106101
}
107102

108103
if (index !== -1) {
109-
return [remove(at(path('article', slug, 'comments'), index))];
104+
state.article[slug].comments.splice(index, 1);
110105
}
111-
return [];
112106
});
113107

114-
const newCommentInputCommand = commandFactory<NewCommentPayload>(({ path, payload: { newComment, slug } }) => {
115-
return [replace(path('article', slug, 'newComment'), newComment)];
108+
const newCommentInputCommand = commandFactory<NewCommentPayload>(({ state, payload: { newComment, slug } }) => {
109+
state.article[slug].newComment = newComment;
116110
});
117111

118-
const deleteArticleCommand = commandFactory<SlugPayload>(async ({ get, path, payload: { slug } }) => {
119-
const token = get(path('session', 'token'));
112+
const deleteArticleCommand = commandFactory<SlugPayload>(async ({ state, payload: { slug } }) => {
113+
const token = state.session?.token;
120114
await fetch(`${baseUrl}/articles/${slug}`, {
121115
method: 'delete',
122116
headers: getHeaders(token)
123117
});
124-
return [replace(path('routing', 'outlet'), 'home')];
118+
state.routing.outlet = 'home';
125119
});
126120

127121
export const getArticleProcess = createProcess('get-article', [

realworld/src/processes/editorProcesses.ts

+37-35
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,62 @@
11
import { createProcess } from '@dojo/framework/stores/process';
2-
import { replace, add, remove } from '@dojo/framework/stores/state/operations';
32
import { getHeaders, commandFactory } from './utils';
43
import { baseUrl } from '../config';
54
import { TitlePayload, DescriptionPayload, BodyPayload, TagPayload, SlugPayload } from './interfaces';
65

7-
const titleInputCommand = commandFactory<TitlePayload>(({ path, payload: { title } }) => {
8-
return [replace(path('editor', 'title'), title)];
6+
const titleInputCommand = commandFactory<TitlePayload>(({ state, payload: { title } }) => {
7+
state.editor.title = title;
98
});
109

11-
const descriptionInputCommand = commandFactory<DescriptionPayload>(({ path, payload: { description } }) => {
12-
return [replace(path('editor', 'description'), description)];
10+
const descriptionInputCommand = commandFactory<DescriptionPayload>(({ state, payload: { description } }) => {
11+
state.editor.description = description;
1312
});
1413

15-
const bodyInputCommand = commandFactory<BodyPayload>(({ path, payload: { body } }) => {
16-
return [replace(path('editor', 'body'), body)];
14+
const bodyInputCommand = commandFactory<BodyPayload>(({ state, payload: { body } }) => {
15+
state.editor.body = body;
1716
});
1817

19-
const tagInputCommand = commandFactory<TagPayload>(({ path, payload: { tag } }) => {
20-
return [replace(path('editor', 'tag'), tag)];
18+
const tagInputCommand = commandFactory<TagPayload>(({ state, payload: { tag } }) => {
19+
state.editor.tag = tag;
2120
});
2221

23-
const addTagCommand = commandFactory<TagPayload>(({ get, at, path, payload: { tag } }) => {
24-
const length = (get(path('editor', 'tagList')) || []).length;
25-
return [add(at(path('editor', 'tagList'), length), tag)];
22+
const addTagCommand = commandFactory<TagPayload>(({ state, payload: { tag } }) => {
23+
if (!state.editor.tagList) {
24+
state.editor.tagList = [];
25+
}
26+
state.editor.tagList.push(tag);
2627
});
2728

28-
const clearTagInputCommand = commandFactory(({ path }) => {
29-
return [replace(path('editor', 'tag'), '')];
29+
const clearTagInputCommand = commandFactory(({ state }) => {
30+
state.editor.tag = '';
3031
});
3132

32-
const removeTagCommand = commandFactory<TagPayload>(({ get, at, path, payload: { tag } }) => {
33-
const tags = get(path('editor', 'tagList'));
33+
const removeTagCommand = commandFactory<TagPayload>(({ state, payload: { tag } }) => {
34+
const tags = state.editor.tagList || [];
3435
const index = tags.indexOf(tag);
35-
if (index !== -1) {
36-
return [remove(at(path('editor', 'tagList'), index))];
36+
if (index !== -1 && state.editor.tagList) {
37+
state.editor.tagList.splice(index, 1);
3738
}
38-
return [];
3939
});
4040

41-
const getArticleForEditorCommand = commandFactory<SlugPayload>(async ({ path, payload: { slug } }) => {
41+
const getArticleForEditorCommand = commandFactory<SlugPayload>(async ({ state, payload: { slug } }) => {
4242
const response = await fetch(`${baseUrl}/articles/${slug}`);
4343
const json = await response.json();
44-
return [replace(path('editor'), json.article)];
44+
state.editor = json.article;
4545
});
4646

47-
const clearEditorCommand = commandFactory(({ path }) => {
48-
return [replace(path('editor'), {})];
47+
const clearEditorCommand = commandFactory(({ state }) => {
48+
state.editor = {};
4949
});
5050

51-
const startPublishCommand = commandFactory(({ path }) => {
52-
return [replace(path('editor', 'isLoading'), true)];
51+
const startPublishCommand = commandFactory(({ state }) => {
52+
state.editor.isLoading = true;
5353
});
5454

55-
const publishArticleCommand = commandFactory(async ({ get, path }) => {
56-
const token = get(path('session', 'token'));
57-
const slug = get(path('editor', 'slug'));
55+
const publishArticleCommand = commandFactory(async ({ state }) => {
56+
const token = state.session?.token;
57+
const slug = state.editor.slug;
5858
const requestPayload = {
59-
article: get(path('editor'))
59+
article: state.editor
6060
};
6161

6262
const url = slug ? `${baseUrl}/articles/${slug}` : `${baseUrl}/articles`;
@@ -68,14 +68,16 @@ const publishArticleCommand = commandFactory(async ({ get, path }) => {
6868
const json = await response.json();
6969

7070
if (!response.ok) {
71-
return [replace(path('editor', 'isLoading'), false), replace(path('errors'), json.errors)];
71+
state.editor.isLoading = false;
72+
state.errors = json.errors;
73+
return;
7274
}
7375

74-
return [
75-
replace(path('article', slug, 'item'), json.article),
76-
replace(path('article', slug, 'isLoading'), true),
77-
replace(path('editor'), undefined)
78-
];
76+
if (slug) {
77+
state.article[slug].item = json.article;
78+
state.article[slug].isLoading = true;
79+
}
80+
state.editor = {};
7981
});
8082

8183
export const titleInputProcess = createProcess('title-input', [titleInputCommand]);

0 commit comments

Comments
 (0)