From 1b5517ea998c63a38a527821fa2d48050ec8275c Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Sat, 17 Feb 2018 10:29:25 +0000 Subject: [PATCH] fix: Fixes todo editing. --- src/presenter/Footer.tsx | 63 ++++++++-------- src/presenter/NewTodo.tsx | 42 ++++++----- src/presenter/TodoItem.tsx | 109 ++++++++++++++------------- src/presenter/TodoItems.tsx | 29 +++---- src/presenter/utils/AsyncConnect.tsx | 46 +++++++++++ src/presenter/utils/SyncConnect.tsx | 31 ++++++++ src/presenter/utils/asyncRender.tsx | 29 ------- src/presenter/utils/connect.tsx | 27 ------- 8 files changed, 206 insertions(+), 170 deletions(-) create mode 100644 src/presenter/utils/AsyncConnect.tsx create mode 100644 src/presenter/utils/SyncConnect.tsx delete mode 100644 src/presenter/utils/asyncRender.tsx delete mode 100644 src/presenter/utils/connect.tsx diff --git a/src/presenter/Footer.tsx b/src/presenter/Footer.tsx index 6df6d645d..17d2f209d 100644 --- a/src/presenter/Footer.tsx +++ b/src/presenter/Footer.tsx @@ -1,40 +1,43 @@ import * as React from 'react'; import Service from '../service/Facade'; -import asyncRender from './utils/asyncRender'; -import connect from './utils/connect'; +import AsyncConnect from './utils/AsyncConnect'; export interface Props { readonly service: Service; } -export default connect(asyncRender(async ({ service }: Props) => { - const route = service.getRoute(); - const completeCount = await service.getCompleteCount(); - const incompleteCount = await service.getIncompleteCount(); +export default ({ service }: Props) => { + return ( + { + const route = service.getRoute(); + const completeCount = await service.getCompleteCount(); + const incompleteCount = await service.getIncompleteCount(); - const renderLink = (text: string, link: string) => { - const className = route === link ? 'selected' : ''; - const href = `#${link}`; - return
  • {text}
  • ; - }; + const renderLink = (text: string, link: string) => { + const className = route === link ? 'selected' : ''; + const href = `#${link}`; + return
  • {text}
  • ; + }; - return ( - + return ( + + ); + }} /> ); -})); +}; diff --git a/src/presenter/NewTodo.tsx b/src/presenter/NewTodo.tsx index a381f41a9..9c3b493d7 100644 --- a/src/presenter/NewTodo.tsx +++ b/src/presenter/NewTodo.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import Service from '../service/Facade'; -import connect from './utils/connect'; +import SyncConnect from './utils/SyncConnect'; const enterKey = 13; @@ -8,23 +8,27 @@ export interface Props { readonly service: Service; } -export default connect(({ service }: Props) => { - const newTodoTitle = service.getNewTodoTitle(); - +export default ({ service }: Props) => { return ( - { - service.changeNewTodoTitle((event.target as any).value); - }} - onKeyDown={async (event) => { - if (event.keyCode === enterKey) { - await service.createNewTodo(); - service.changeNewTodoTitle(''); - } - }} - /> + { + const newTodoTitle = service.getNewTodoTitle(); + + return ( + { + service.changeNewTodoTitle((event.target as any).value); + }} + onKeyDown={async (event) => { + if (event.keyCode === enterKey) { + await service.createNewTodo(); + service.changeNewTodoTitle(''); + } + }} + /> + ); + }} /> ); -}); +}; diff --git a/src/presenter/TodoItem.tsx b/src/presenter/TodoItem.tsx index e23464e9e..7c8672397 100644 --- a/src/presenter/TodoItem.tsx +++ b/src/presenter/TodoItem.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import Service from '../service/Facade'; import TodoEntity from '../utils/TodoEntity'; -import connect from './utils/connect'; +import SyncConnect from './utils/SyncConnect'; const enterKey = 13; const escapeKey = 27; @@ -12,57 +12,62 @@ export interface Props { readonly service: Service; } -export default connect(({ todo, service }: Props) => { - const isEditing = service.getIsEditing(todo.id); - const editedTitle = service.getEditedTitle(todo.id); - - const handleSave = async () => { - const title = editedTitle.trim(); - if (title.length === 0) { - await service.removeTodo(todo.id); - return; - } - service.setEditedTitle(todo.id, title); - service.setIsEditing(todo.id, false); - await service.setTodoTitle(todo.id, title); - }; - +export default ({ todo, service }: Props) => { return ( -
  • -
    - { - await service.setTodoCompletion(todo.id, !todo.completed); - }} - /> - -
    - { - service.setEditedTitle(todo.id, (event.target as any).value); - }} - onKeyDown={async (event) => { - if (event.keyCode === escapeKey) { - service.setEditedTitle(todo.id, todo.title); - service.setIsEditing(todo.id, false); - } else if (event.keyCode === enterKey) { - await handleSave(); - } - }} - /> -
  • + return; + } + service.setEditedTitle(todo.id, title); + service.setIsEditing(todo.id, false); + await service.setTodoTitle(todo.id, title); + }; + + return ( +
  • +
    + { + await service.setTodoCompletion(todo.id, !todo.completed); + }} + /> + +
    + { + service.setEditedTitle(todo.id, (event.target as any).value); + }} + onKeyDown={async (event) => { + if (event.keyCode === escapeKey) { + service.setEditedTitle(todo.id, todo.title); + service.setIsEditing(todo.id, false); + } else if (event.keyCode === enterKey) { + await handleSave(); + } + }} + /> +
  • + ); + }} /> ); -}); +}; diff --git a/src/presenter/TodoItems.tsx b/src/presenter/TodoItems.tsx index 0a28e2e69..6fe98ef07 100644 --- a/src/presenter/TodoItems.tsx +++ b/src/presenter/TodoItems.tsx @@ -1,23 +1,26 @@ import * as React from 'react'; import Service from '../service/Facade'; import TodoItem from './TodoItem'; -import asyncRender from './utils/asyncRender'; -import connect from './utils/connect'; +import AsyncConnect from './utils/AsyncConnect'; export interface Props { readonly service: Service; } -export default connect(asyncRender(async ({ service }: Props) => { - const todos = await service.getRouteTodos(); - +export default ({ service }: Props) => { return ( -
    -
      - {todos.map((todo) => { - return ; - })} -
    -
    + { + const todos = await service.getRouteTodos(); + + return ( +
    +
      + {todos.map((todo) => { + return ; + })} +
    +
    + ); + }} /> ); -})); +}; diff --git a/src/presenter/utils/AsyncConnect.tsx b/src/presenter/utils/AsyncConnect.tsx new file mode 100644 index 000000000..e019fdc46 --- /dev/null +++ b/src/presenter/utils/AsyncConnect.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import Service from '../../service/Facade'; +import observer from '../../utils/observer'; + +const loader = () => { + return Loading; +}; + +export interface Props { + readonly service: Service; + readonly render: () => Promise; +} + +export interface State { + readonly renderedComponent: JSX.Element; +} + +// tslint:disable:no-class no-this +export default class AsyncConnect extends React.Component { + constructor(p: Props, s: State) { + super(p, s); + const renderedComponent = loader(); + this.state = { renderedComponent }; + } + + public componentDidMount() { + observer.addListener('change', this.update.bind(this)); + this.update().catch((err) => { + // tslint:disable-next-line:no-console + console.error(err); + }); + } + + public componentWillUnmount() { + observer.removeListener('change', this.update.bind(this)); + } + + private async update() { + const renderedComponent = await this.props.render(); + this.setState({ renderedComponent }); + } + + public render() { + return this.state.renderedComponent; + } +} diff --git a/src/presenter/utils/SyncConnect.tsx b/src/presenter/utils/SyncConnect.tsx new file mode 100644 index 000000000..ec7c35541 --- /dev/null +++ b/src/presenter/utils/SyncConnect.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import Service from '../../service/Facade'; +import observer from '../../utils/observer'; + +export interface Props { + readonly service: Service; + readonly render: () => JSX.Element; +} + +export interface State { + readonly change: Date; +} + +// tslint:disable:no-class no-this +export default class Connect extends React.Component { + public componentDidMount() { + observer.addListener('change', this.update.bind(this)); + } + + public componentWillUnmount() { + observer.removeListener('change', this.update.bind(this)); + } + + private async update() { + this.setState({ change: new Date() }); + } + + public render() { + return this.props.render(); + } +} diff --git a/src/presenter/utils/asyncRender.tsx b/src/presenter/utils/asyncRender.tsx deleted file mode 100644 index f43a72b54..000000000 --- a/src/presenter/utils/asyncRender.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react'; - -const loader = () => { - return Loading; -}; - -// tslint:disable:no-class no-this -const asyncRender = (component: (props: P) => Promise) => (props: P): C => { - class AsyncWrapper extends React.Component { - constructor(p: any, s: any) { - super(p, s); - const renderedComponent = loader(); - this.state = { renderedComponent }; - } - - public async componentDidMount() { - const renderedComponent = await component(props); - this.setState({ renderedComponent }); - } - - public render() { - return this.state.renderedComponent; - } - } - - return as any as C; -}; - -export default asyncRender; diff --git a/src/presenter/utils/connect.tsx b/src/presenter/utils/connect.tsx deleted file mode 100644 index b03ce6631..000000000 --- a/src/presenter/utils/connect.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as React from 'react'; -import observer from '../../utils/observer'; - -// tslint:disable:no-class no-this prefer-function-over-method -const connect = (component: (props: P) => C) => (props: P): C => { - class ConnectWrapper extends React.Component { - public componentDidMount() { - observer.addListener('change', this.update.bind(this)); - } - - public componentWillUnmount() { - observer.removeListener('change', this.update.bind(this)); - } - - private update() { - this.setState({ change: new Date() }); - } - - public render() { - return component(props); - } - } - - return as any as C; -}; - -export default connect;