Hi, again. Business Logic as a Good Old Procedural Programming
Tags: typescript, react, ui
Published: 2018-10-20 10:13:54

---
title: Hi, again. Business Logic as a Good Old Procedural Programming
published: true
description: The guide to not to do event driven programming
tags: TypeScript, React, UI
---

## Why?

There is a gap between software specification and implementation. If we could write the program flow with a simple DSL and even run it ... This is just an idea, but we can do it. Half joke, half serious.

## UI as function

My first thought was if we could write the user interface as an async function.  In fact, it’s already we've seen.

```js
if (confirm(“Do you want to send the message?”)) {
  somethingHappen()
} else {
  alert(“cancelled!”)
}
```

Pretty simple, right? There are no any callbacks, no flux, no dispatch, no singleton state. It’s easily readable.

## Re-invent the UI function

Let’s do as same way by using async/await in TypeScript.
We can define the view as async function that returns user input events.

```ts
interface View {
  confirmToSend: async () => boolean
}
```

and then write the business logic.

```ts
const main = async (view: View) => {
  if (await view.confirmToSend()) {
    somethingHappen()
  }
}
```

then implement View. At now I use React. it’s not important anyway.

```tsx
class App implements View {
  async confirmToSend(): boolean {
    return new Promise((resolve) => {
      this.setState({
        isVisibleConfirm: true,
        onClickConfirmYes: () => resolve(true),
        onClickConfirmNo: () => resolve(false),
      })
    })
  }
  
  public render() {
    return <div>
      {this.state.isVisibleConfirm && 
        <div className="modal confirm">
          <p>Do you want to send the message?</p>
          <button onClick={this.state.onClickConfirmYes}>Yes</button>
          <button onClick={this.state.onClickConfirmNo}>No</button>
        </div>}
    <div>
  }
}
```

The point is `confirmToSend` returns Promise that waits for user interaction.

Run them together. 

```jsx
ReactDOM.render(<App ref={view => view && main(view)} />)
```

So, this application works according to business logic written in async / await.

Do you understand how we can write business logic in procedural way with React? We might need another example.

## To-Do App

So let’s see the to-do app example.

First of all we write the business logic.

```ts
export interface Todo {
  title: string
  description: string
}

export interface Store {
  getTodos(): Promise<Todo[]>
  addTodo(todo: Todo): Promise<void>
}

export interface View {
  showTodos(todos: Todo[]): Promise<["click-todo", Todo] | ["create-todo"]>
  showTodo(Todo: Todo): Promise<["close"]>
  showCreateForm(): Promise<["cancel"] | ["create", Todo]>
  closeCreateForm(): Promise<void>
  closeTodo(Todo: Todo): Promise<void>
}

export const mainLoop = async (store: Store, view: View) => {
  initial: while (true) {
    const todos = await store.getTodos()
    const event = await view.showTodos(todos)

    switch (event[0]) {
      case "click-todo": {
        const e = await view.showTodo(event[1])
        switch (e[0]) {
          case "close":
            await view.closeTodo(event[1])
            continue initial
        }
        break
      }
      case "create-todo": {
        const e = await view.showCreateForm()
        switch (e[0]) {
          case "cancel":
            await view.closeCreateForm()
            continue initial
          case "create":
            await view.closeCreateForm()
            await store.addTodo(e[1])
            continue initial
        }
        break
      }
    }
  }
}
```

Looks pretty! This is self-contained, complete behavior definition of this app. We can understand how this app works by just reading this function. And this is a specification for the app.

Let's see some tricks.

### Store

Store fetches data from somewhere and storing. We have the view as an interface. So we should write the store as an interface to separate business logic and implementation. 

### Infinite Loop

Main loop! You probably used an infinite loop long ago to make a game or something ...
This To-Do app should run until the user closes the tab, so it will be written in an infinite loop.

### Label and `continue`

When user closes modal view, app should reset to the first state.
We can use continue to go back to start of while loop.

### Events

We used boolean to handle user-interaction in above example.
But in real app, we have to handle many events for one showSomewhat function.
I defined some event types for each show functions using array. We can use the object that have type and payload. But type inference also works so I don't want to type too much.

```ts
["click-todo", Todo]|["close-todo"]
```

maybe you like

```ts
interface CloseTodoEvent {
  type: "close-todo"
}

interface ClickTodoEvent {
  type: "click-todo"
  payload: Todo
}
```

## To-Do View

Now let see the View implementation.

```tsx
import * as React from "react"
import { Todo } from "./main"

interface State {
  todos: Todo[]
  modalTodo: Todo | null
  isCreateFormVisible: boolean
  formTitle: string
  formDescription: string
  onClickTodo: (todo: Todo) => void
  onClickCreateNew: () => void
  onClickModal: () => void
  onClickAdd: () => void
  onClickCancelCreation: () => void
}

export class AppView extends React.Component<{}, {}> {
  public state: State = {
    todos: [],
    modalTodo: null,
    isCreateFormVisible: false,
    formTitle: "",
    formDescription: "",
    onClickTodo: (todo: Todo) => {},
    onClickCreateNew: () => {},
    onClickModal: () => {},
    onClickAdd: () => {},
    onClickCancelCreation: () => {}
  }

  showTodos(todos: Todo[]) {
    return new Promise<["click-todo", Todo] | ["create-todo"]>(resolve => {
      this.setState({
        todos,
        modalTodo: null,
        onClickTodo: (todo: Todo) => resolve(["click-todo", todo]),
        onClickCreateNew: () => resolve(["create-todo"])
      })
    })
  }

  showTodo(todo: Todo) {
    return new Promise<["close"]>(resolve => {
      this.setState({
        modalTodo: todo,
        onClickModal: () => resolve(["close"])
      })
    })
  }

  closeTodo(todo: Todo): Promise<void> {
    this.setState({ modalTodo: null })
    return Promise.resolve()
  }

  showCreateForm() {
    return new Promise<["cancel"] | ["create", Todo]>(resolve => {
      this.setState({
        formTitle: "",
        formDescription: "",
        isCreateFormVisible: true,
        onClickCancelCreation: () => resolve(["cancel"]),
        onClickAdd: () =>
          resolve([
            "create",
            {
              title: this.state.formTitle,
              description: this.state.formDescription
            }
          ])
      })
    })
  }

  closeCreateForm() {
    this.setState({
      isCreateFormVisible: false
    })
    return Promise.resolve()
  }

  public render() {
    const {
      todos,
      modalTodo,
      isCreateFormVisible,
      formTitle,
      formDescription,
      onClickCreateNew,
      onClickTodo,
      onClickModal,
      onClickCancelCreation,
      onClickAdd
    } = this.state
    return (
      <>
        <ul>
          {todos.map((t, i) => (
            <li className="todo" onClick={() => onClickTodo(t)} key={i}>
              <p className="title">{t.title}</p>
              <p className="description">{t.description}</p>
            </li>
          ))}
        </ul>
        <button onClick={onClickCreateNew}>Create new To-Do</button>
        {modalTodo !== null && (
          <div className="modal">
            <p className="title">{modalTodo.title}</p>
            <p className="description">{modalTodo.description}</p>
            <button onClick={onClickModal}>Close</button>
          </div>
        )}
        {isCreateFormVisible && (
          <div className="modal create-todo-form">
            <label>title</label>
            <input
              type="text"
              value={formTitle}
              onChange={e => this.setState({ formTitle: e.target.value })}
            />
            <label>description</label>
            <input
              type="text"
              value={formDescription}
              onChange={e => this.setState({ formDescription: e.target.value })}
            />
            <button onClick={onClickCancelCreation}>Cancel</button>
            <button onClick={onClickAdd}>Add</button>
          </div>
        )}
      </>
    )
  }
}
```

This is little ugly but works. We may need somewhat library to do this better.

## Screenshot


![todo the movie](https://thepracticaldev.s3.amazonaws.com/i/gg98mw4twq8rhs2seig9.gif)
It works!
Let’s say hi again to good old procedural programming!

## Further more

- Can we apply this method to real-world application?
- How we handle interrupted events?
- How we handle the application that has multiple pane.
- Does this business logic make tests more efficient?

## Source Code

https://github.com/ryohey/Hi-Again