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

Commit 1b5517e

Browse files
committed
fix: Fixes todo editing.
1 parent 590c57d commit 1b5517e

File tree

8 files changed

+206
-170
lines changed

8 files changed

+206
-170
lines changed

src/presenter/Footer.tsx

+33-30
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,43 @@
11
import * as React from 'react';
22
import Service from '../service/Facade';
3-
import asyncRender from './utils/asyncRender';
4-
import connect from './utils/connect';
3+
import AsyncConnect from './utils/AsyncConnect';
54

65
export interface Props {
76
readonly service: Service;
87
}
98

10-
export default connect(asyncRender(async ({ service }: Props) => {
11-
const route = service.getRoute();
12-
const completeCount = await service.getCompleteCount();
13-
const incompleteCount = await service.getIncompleteCount();
9+
export default ({ service }: Props) => {
10+
return (
11+
<AsyncConnect service={service} render={async () => {
12+
const route = service.getRoute();
13+
const completeCount = await service.getCompleteCount();
14+
const incompleteCount = await service.getIncompleteCount();
1415

15-
const renderLink = (text: string, link: string) => {
16-
const className = route === link ? 'selected' : '';
17-
const href = `#${link}`;
18-
return <li><a href={href} className={className}>{text}</a></li>;
19-
};
16+
const renderLink = (text: string, link: string) => {
17+
const className = route === link ? 'selected' : '';
18+
const href = `#${link}`;
19+
return <li><a href={href} className={className}>{text}</a></li>;
20+
};
2021

21-
return (
22-
<footer className="footer">
23-
<span className="todo-count">
24-
<strong>{incompleteCount}</strong> items left
25-
</span>
26-
<ul className="filters">
27-
{renderLink('All', '')}
28-
{renderLink('Active', 'active')}
29-
{renderLink('Completed', 'completed')}
30-
</ul>
31-
{completeCount > 0 ? (
32-
<button
33-
className="clear-completed"
34-
onClick={async () => { await service.deleteCompleted(); }}>
35-
Clear completed
36-
</button>
37-
) : <noscript />}
38-
</footer>
22+
return (
23+
<footer className="footer">
24+
<span className="todo-count">
25+
<strong>{incompleteCount}</strong> items left
26+
</span>
27+
<ul className="filters">
28+
{renderLink('All', '')}
29+
{renderLink('Active', 'active')}
30+
{renderLink('Completed', 'completed')}
31+
</ul>
32+
{completeCount > 0 ? (
33+
<button
34+
className="clear-completed"
35+
onClick={async () => { await service.deleteCompleted(); }}>
36+
Clear completed
37+
</button>
38+
) : <noscript />}
39+
</footer>
40+
);
41+
}} />
3942
);
40-
}));
43+
};

src/presenter/NewTodo.tsx

+23-19
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,34 @@
11
import * as React from 'react';
22
import Service from '../service/Facade';
3-
import connect from './utils/connect';
3+
import SyncConnect from './utils/SyncConnect';
44

55
const enterKey = 13;
66

77
export interface Props {
88
readonly service: Service;
99
}
1010

11-
export default connect(({ service }: Props) => {
12-
const newTodoTitle = service.getNewTodoTitle();
13-
11+
export default ({ service }: Props) => {
1412
return (
15-
<input
16-
className="new-todo"
17-
placeholder="What needs to be done?"
18-
value={newTodoTitle}
19-
onChange={(event) => {
20-
service.changeNewTodoTitle((event.target as any).value);
21-
}}
22-
onKeyDown={async (event) => {
23-
if (event.keyCode === enterKey) {
24-
await service.createNewTodo();
25-
service.changeNewTodoTitle('');
26-
}
27-
}}
28-
/>
13+
<SyncConnect service={service} render={() => {
14+
const newTodoTitle = service.getNewTodoTitle();
15+
16+
return (
17+
<input
18+
className="new-todo"
19+
placeholder="What needs to be done?"
20+
value={newTodoTitle}
21+
onChange={(event) => {
22+
service.changeNewTodoTitle((event.target as any).value);
23+
}}
24+
onKeyDown={async (event) => {
25+
if (event.keyCode === enterKey) {
26+
await service.createNewTodo();
27+
service.changeNewTodoTitle('');
28+
}
29+
}}
30+
/>
31+
);
32+
}} />
2933
);
30-
});
34+
};

src/presenter/TodoItem.tsx

+57-52
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import Service from '../service/Facade';
33
import TodoEntity from '../utils/TodoEntity';
4-
import connect from './utils/connect';
4+
import SyncConnect from './utils/SyncConnect';
55

66
const enterKey = 13;
77
const escapeKey = 27;
@@ -12,57 +12,62 @@ export interface Props {
1212
readonly service: Service;
1313
}
1414

15-
export default connect(({ todo, service }: Props) => {
16-
const isEditing = service.getIsEditing(todo.id);
17-
const editedTitle = service.getEditedTitle(todo.id);
18-
19-
const handleSave = async () => {
20-
const title = editedTitle.trim();
21-
if (title.length === 0) {
22-
await service.removeTodo(todo.id);
23-
return;
24-
}
25-
service.setEditedTitle(todo.id, title);
26-
service.setIsEditing(todo.id, false);
27-
await service.setTodoTitle(todo.id, title);
28-
};
29-
15+
export default ({ todo, service }: Props) => {
3016
return (
31-
<li className={`${todo.completed ? 'completed' : ''} ${isEditing ? 'editing' : ''}`}>
32-
<div className="view">
33-
<input
34-
className="toggle"
35-
type="checkbox"
36-
checked={todo.completed}
37-
onChange={async () => {
38-
await service.setTodoCompletion(todo.id, !todo.completed);
39-
}}
40-
/>
41-
<label onDoubleClick={async () => {
42-
service.setIsEditing(todo.id, true);
43-
}}>
44-
{todo.title}
45-
</label>
46-
<button className="destroy" onClick={async () => {
17+
<SyncConnect service={service} render={() => {
18+
const isEditing = service.getIsEditing(todo.id);
19+
const editedTitle = service.getEditedTitle(todo.id);
20+
21+
const handleSave = async () => {
22+
const title = editedTitle.trim();
23+
if (title.length === 0) {
4724
await service.removeTodo(todo.id);
48-
}} />
49-
</div>
50-
<input
51-
className="edit"
52-
value={editedTitle}
53-
onBlur={handleSave}
54-
onChange={async (event) => {
55-
service.setEditedTitle(todo.id, (event.target as any).value);
56-
}}
57-
onKeyDown={async (event) => {
58-
if (event.keyCode === escapeKey) {
59-
service.setEditedTitle(todo.id, todo.title);
60-
service.setIsEditing(todo.id, false);
61-
} else if (event.keyCode === enterKey) {
62-
await handleSave();
63-
}
64-
}}
65-
/>
66-
</li>
25+
return;
26+
}
27+
service.setEditedTitle(todo.id, title);
28+
service.setIsEditing(todo.id, false);
29+
await service.setTodoTitle(todo.id, title);
30+
};
31+
32+
return (
33+
<li className={`${todo.completed ? 'completed' : ''} ${isEditing ? 'editing' : ''}`}>
34+
<div className="view">
35+
<input
36+
className="toggle"
37+
type="checkbox"
38+
checked={todo.completed}
39+
onChange={async () => {
40+
await service.setTodoCompletion(todo.id, !todo.completed);
41+
}}
42+
/>
43+
<label onDoubleClick={async () => {
44+
service.setEditedTitle(todo.id, todo.title);
45+
service.setIsEditing(todo.id, true);
46+
}}>
47+
{todo.title}
48+
</label>
49+
<button className="destroy" onClick={async () => {
50+
await service.removeTodo(todo.id);
51+
}} />
52+
</div>
53+
<input
54+
className="edit"
55+
value={editedTitle}
56+
onBlur={handleSave}
57+
onChange={async (event) => {
58+
service.setEditedTitle(todo.id, (event.target as any).value);
59+
}}
60+
onKeyDown={async (event) => {
61+
if (event.keyCode === escapeKey) {
62+
service.setEditedTitle(todo.id, todo.title);
63+
service.setIsEditing(todo.id, false);
64+
} else if (event.keyCode === enterKey) {
65+
await handleSave();
66+
}
67+
}}
68+
/>
69+
</li>
70+
);
71+
}} />
6772
);
68-
});
73+
};

src/presenter/TodoItems.tsx

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
import * as React from 'react';
22
import Service from '../service/Facade';
33
import TodoItem from './TodoItem';
4-
import asyncRender from './utils/asyncRender';
5-
import connect from './utils/connect';
4+
import AsyncConnect from './utils/AsyncConnect';
65

76
export interface Props {
87
readonly service: Service;
98
}
109

11-
export default connect(asyncRender(async ({ service }: Props) => {
12-
const todos = await service.getRouteTodos();
13-
10+
export default ({ service }: Props) => {
1411
return (
15-
<div>
16-
<ul className="todo-list">
17-
{todos.map((todo) => {
18-
return <TodoItem key={todo.id} todo={todo} service={service} />;
19-
})}
20-
</ul>
21-
</div>
12+
<AsyncConnect service={service} render={async () => {
13+
const todos = await service.getRouteTodos();
14+
15+
return (
16+
<div>
17+
<ul className="todo-list">
18+
{todos.map((todo) => {
19+
return <TodoItem key={todo.id} todo={todo} service={service} />;
20+
})}
21+
</ul>
22+
</div>
23+
);
24+
}} />
2225
);
23-
}));
26+
};

src/presenter/utils/AsyncConnect.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as React from 'react';
2+
import Service from '../../service/Facade';
3+
import observer from '../../utils/observer';
4+
5+
const loader = () => {
6+
return <span>Loading</span>;
7+
};
8+
9+
export interface Props {
10+
readonly service: Service;
11+
readonly render: () => Promise<JSX.Element>;
12+
}
13+
14+
export interface State {
15+
readonly renderedComponent: JSX.Element;
16+
}
17+
18+
// tslint:disable:no-class no-this
19+
export default class AsyncConnect extends React.Component<Props, State> {
20+
constructor(p: Props, s: State) {
21+
super(p, s);
22+
const renderedComponent = loader();
23+
this.state = { renderedComponent };
24+
}
25+
26+
public componentDidMount() {
27+
observer.addListener('change', this.update.bind(this));
28+
this.update().catch((err) => {
29+
// tslint:disable-next-line:no-console
30+
console.error(err);
31+
});
32+
}
33+
34+
public componentWillUnmount() {
35+
observer.removeListener('change', this.update.bind(this));
36+
}
37+
38+
private async update() {
39+
const renderedComponent = await this.props.render();
40+
this.setState({ renderedComponent });
41+
}
42+
43+
public render() {
44+
return this.state.renderedComponent;
45+
}
46+
}

src/presenter/utils/SyncConnect.tsx

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as React from 'react';
2+
import Service from '../../service/Facade';
3+
import observer from '../../utils/observer';
4+
5+
export interface Props {
6+
readonly service: Service;
7+
readonly render: () => JSX.Element;
8+
}
9+
10+
export interface State {
11+
readonly change: Date;
12+
}
13+
14+
// tslint:disable:no-class no-this
15+
export default class Connect extends React.Component<Props, State> {
16+
public componentDidMount() {
17+
observer.addListener('change', this.update.bind(this));
18+
}
19+
20+
public componentWillUnmount() {
21+
observer.removeListener('change', this.update.bind(this));
22+
}
23+
24+
private async update() {
25+
this.setState({ change: new Date() });
26+
}
27+
28+
public render() {
29+
return this.props.render();
30+
}
31+
}

src/presenter/utils/asyncRender.tsx

-29
This file was deleted.

0 commit comments

Comments
 (0)