React 編 - 初心者こそ、Docker を使おう！


# React 編

同じ Docker の環境を使って、React を使おうかと思います。
今回も、Pacel-bundler をそのまま使います。
React にしても特に Parcel の設定はいらないです。
では、ライブラリを読み込んで始めましょう

Docker コンテナを起動して、`uname` で linux が出るようにしてください。
このあたりは、最初の投稿を参照ねがいます。
`docker-compose run --service-ports node js` が書いているあたりです。

一応 yml　ファイルを載せておきます


`dcoker-compose.yml`

```ts
version: "3"
services:
  node:
    container_name: node
    image: atoris1192/node:0.1.5
    # build: .
    # volumes は上書きに注意
    volumes:
      - .:/app
    ports:
      - "1234:1234"
      - "1235:1235"
    working_dir: /app
    command: cp -rp /tmp/src /app
    # command: npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug
    tty: true
```
```
docker-compose up  # 同じディレクトリでするなら必要ないです
docker-compose ps
docker-compose run --service-ports node bash
# コンテナ linux
uname
cd src
yarn add react react-dom
npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug
```


`index.pug`

```ts
<!DOCTYPE html>
html(lang="en")
  head
    meta(charset="UTF-8")
    meta(name="viewport", content="width=, initial-scale=1.0")
    meta(http-equiv="X-UA-Compatible", content="ie=edge")
    title Document
  body
    #app
    script(src="./index.tsx")
```

`index.tsx`

```ts
import * as React from 'react'
import * as ReactDOM from 'react-dom';
import App from './App';
import './style.stylus'

ReactDOM.render(
  <App />,
  document.getElementById('app')
)
```

`App.tsx`

```ts
import * as React from 'react';

const todos = [
  {id: 0, title: "Task 0", isDone: false },
  {id: 1, title: "Task 1", isDone: true },
  {id: 2, title: "Task 2", isDone: false },
]


class App extends React.Component {
  private state: any;
  constructor() {
    super();
    // 仮データを直接いれてますが、Reactでは禁止行為です
    this.state = {
      todos: todos,
    }
  }
  render() {
    const todos = this.state.todos.map ( todo => {
      return (<li key={ todo.id }>{ todo.title }</li>)
    })
    return(
      <div className="container">
        <ul>
          { todos }
        </ul>
      </div>
    )
  }
}

export default App;
```

## addTodo
input 処理が面倒くさい。


`Att.tsx`

```ts
import * as React from 'react';

const todos = [
  {id: 0, title: "Task 0", isDone: false },
  {id: 1, title: "Task 1", isDone: true },
  {id: 2, title: "Task 2", isDone: false },
]

function TodoForm(props) {
  return(
    <form onSubmit={ props.addTodo }>
      <input type="text"
        value={ props.item }
        onChange={ props.updateItem }
      />
      <input type="submit" value="Add"/>
    </form>
  )

}

class App extends React.Component {
  private state: any;
  private setState: any;
  constructor() {
    super();
    this.state = {
      todos: todos,
      item: '',
    }
    this.addTodo = this.addTodo.bind(this);
    this.updateItem = this.updateItem.bind(this);
  }

  addTodo(e) {
    e.preventDefault();
    interface Item {
      id: number;
      title: string;
      isDone: boolean;
    }

    const newTodos = this.state.todos.slice();
    const item: Item = {
      id: new Date().getTime(),
      title: this.state.item,
      isDone: false
    }
    newTodos.push(item);
    this.setState({
      todos: newTodos
      item: ''
    })
  }

  updateItem(e) {
    this.setState({
      item: e.target.value,
    })
  }
  render() {
    const todos = this.state.todos.map ( todo => {
      return (<li key={ todo.id }>{ todo.title } : { todo.id } : { String(todo.isDone) }</li>)
    })

    return(
      <div className="container">
        <TodoForm
          item={ this.state.item }
          updateItem={ this.updateItem }
          addTodo={ this.addTodo }
        />
        <ul>
          { todos }
        </ul>
      </div>
    )
  }
}

export default App;
```

## todoList 関数化

```ts
import * as React from 'react';

const todos = [
  {id: 0, title: "Task 0", isDone: false },
  {id: 1, title: "Task 1", isDone: true },
  {id: 2, title: "Task 2", isDone: false },
]

function TodoList (props) {
  const todos = props.todos.map( todo => {
    return (<li key={ todo.id }>
      { todo.title } : { todo.id } : { String(todo.isDone) }
      </li>)
  });
  return(
    <ul>
      { todos }
    </ul>
  )
};

function TodoForm(props) {
  return(
    <form onSubmit={ props.addTodo }>
      <input type="text"
        value={ props.item }
        onChange={ props.updateItem }
      />
      <input type="submit" value="Add"/>
    </form>
  )

}

class App extends React.Component {
  private state: any;
  private setState: any;
  constructor() {
    super();
    this.state = {
      todos: todos,
      item: '',
    }
    this.addTodo = this.addTodo.bind(this);
    this.updateItem = this.updateItem.bind(this);
  }

  addTodo(e) {
    e.preventDefault();
    interface Item {
      id: number;
      title: string;
      isDone: boolean;
    }

    const newTodos = this.state.todos.slice();
    const item: Item = {
      id: new Date().getTime(),
      title: this.state.item,
      isDone: false
    }
    newTodos.push(item);
    this.setState({
      todos: newTodos
      item: ''
    })
  }

  updateItem(e) {
    this.setState({
      item: e.target.value,
    })
  }
  render() {
    // const todos = this.state.todos.map ( todo => {
    //   return (<li key={ todo.id }>{ todo.title } : { todo.id } : { String(todo.isDone) }</li>)
    // })

    return(
      <div className="container">
        <TodoForm
          item={ this.state.item }
          updateItem={ this.updateItem }
          addTodo={ this.addTodo }
        />
        <TodoList todos={ this.state.todos } />
        {/* <ul>
          { todos }
        </ul> */}
      </div>
    )
  }
}

export default App;
```


## checkbox, deleteTodo 処理

`App.tsx`


```ts
import * as React from 'react';

const todos = [
  {id: 0, title: "Task 0", isDone: false },
  {id: 1, title: "Task 1", isDone: true },
  {id: 2, title: "Task 2", isDone: false },
]

function TodoList (props) {
  const todos = props.todos.map( todo => {
    return (<li key={ todo.id } className={ todo.isDone ? 'done' : ''}>

      <input type="checkbox"
       onChange={ () => {props.changeCheckbox(todo)} }
       checked={ todo.isDone ? "chacked" : ""}
      />

      { todo.id } : { todo.title } : { String(todo.isDone) }

      <span className="delete" onClick={ () => {props.deleteTodo(todo.id)} } >[x]</span>

      </li>)
  });
  return(
    <ul>
      { todos }
    </ul>
  )
};

function TodoForm(props) {
  return(
    <form onSubmit={ props.addTodo }>
      <input type="text"
        value={ props.item }
        onChange={ props.updateItem }
      />
      <input type="submit" value="Add"/>
    </form>
  )

}


class App extends React.Component {
  private state: any;
  private setState: any;
  constructor() {
    super();
    this.state = {
      todos: todos,
      item: '',
    }
    this.addTodo = this.addTodo.bind(this);
    this.updateItem = this.updateItem.bind(this);
    this.deleteTodo = this.deleteTodo.bind(this);
    this.changeCheckbox = this.changeCheckbox.bind(this);
  }

  changeCheckbox(todo) {
    console.log("changeCheckbox ...");
    const newTodos = this.state.todos.slice();
    const pos = newTodos.map( todo => {
      return todo.id
    }).indexOf(todo.id)
    newTodos[pos].isDone = !newTodos[pos].isDone

    this.setState({
      todos: newTodos
    })
  }

  deleteTodo(id) {
    // console.log("deleteBtn");
    const newTodos = this.state.todos.slice();
    const pos = newTodos.map( todo => {
      return todo.id
    }).indexOf(id)
    // console.log(pos);
    newTodos.splice(pos,1)

    this.setState({
      todos: newTodos
    })
  }

  addTodo(e) {
    e.preventDefault();
    interface Item {
      id: number;
      title: string;
      isDone: boolean;
    }

    const newTodos = this.state.todos.slice();
    const item: Item = {
      id: new Date().getTime(),
      title: this.state.item,
      isDone: false
    }
    newTodos.push(item);
    this.setState({
      todos: newTodos
      item: ''
    })
  }

  updateItem(e) {
    this.setState({
      item: e.target.value,
    })
  }
  render() {
    return(
      <div className="container">
        <TodoForm
          item={ this.state.item }
          updateItem={ this.updateItem }
          addTodo={ this.addTodo }
        />
        <TodoList
         todos={ this.state.todos }
         deleteTodo={ this.deleteTodo }
         changeCheckbox={ this.changeCheckbox }
         />
      </div>
    )
  }
}

export default App;
```

## todoList を todoItem でさらに分割
`todoList`の分割は好みです。　関数の見通しが悪くなりそうだったら、分割したらいいかと思います。
バケツリレーがもれなくついてくるので、子要素は程々に

```ts
import * as React from 'react';

const todos = [
  {id: 0, title: "Task 0", isDone: false },
  {id: 1, title: "Task 1", isDone: true },
  {id: 2, title: "Task 2", isDone: false },
]
function TodoItem(props) {
  const todo = props.todo;
  return (<li className={ todo.isDone ? 'done' : ''}>

    <input type="checkbox"
      onChange={ () => {props.changeCheckbox(todo)} }
      checked={ todo.isDone ? "chacked" : ""}
    />

    { todo.id } : { todo.title } : { String(todo.isDone) }

    <span className="delete" onClick={ () => {props.deleteTodo(todo.id)} } >[x]</span>

    </li>)
}

function TodoList (props) {
  const todos = props.todos.map( todo => {
    return <TodoItem
      key={ todo.id }
      todo={ todo }
      changeCheckbox={ props.changeCheckbox }
      deleteTodo={ props.deleteTodo }
      />
  });
  return(
    <ul>
      { todos }
    </ul>
  )
};

// function TodoList (props) {
//   const todos = props.todos.map( todo => {
//     return (<li key={ todo.id } className={ todo.isDone ? 'done' : ''}>

//       <input type="checkbox"
//        onChange={ () => {props.changeCheckbox(todo)} }
//        checked={ todo.isDone ? "chacked" : ""}
//       />

//       { todo.id } : { todo.title } : { String(todo.isDone) }

//       <span className="delete" onClick={ () => {props.deleteTodo(todo.id)} } >[x]</span>

//       </li>)
//   });
//   return(
//     <ul>
//       { todos }
//     </ul>
//   )
// };

function TodoForm(props) {
  return(
    <form onSubmit={ props.addTodo }>
      <input type="text"
        value={ props.item }
        onChange={ props.updateItem }
      />
      <input type="submit" value="Add"/>
    </form>
  )

}


class App extends React.Component {
  private state: any;
  private setState: any;
  constructor() {
    super();
    this.state = {
      todos: todos,
      item: '',
    }
    this.addTodo = this.addTodo.bind(this);
    this.updateItem = this.updateItem.bind(this);
    this.deleteTodo = this.deleteTodo.bind(this);
    this.changeCheckbox = this.changeCheckbox.bind(this);
  }

  changeCheckbox(todo) {
    console.log("changeCheckbox ...");
    const newTodos = this.state.todos.slice();
    const pos = newTodos.map( todo => {
      return todo.id
    }).indexOf(todo.id)
    newTodos[pos].isDone = !newTodos[pos].isDone

    this.setState({
      todos: newTodos
    })
  }

  deleteTodo(id) {
    // console.log("deleteBtn");
    const newTodos = this.state.todos.slice();
    const pos = newTodos.map( todo => {
      return todo.id
    }).indexOf(id)
    // console.log(pos);
    newTodos.splice(pos,1)

    this.setState({
      todos: newTodos
    })
  }

  addTodo(e) {
    e.preventDefault();
    interface Item {
      id: number;
      title: string;
      isDone: boolean;
    }

    const newTodos = this.state.todos.slice();
    const item: Item = {
      id: new Date().getTime(),
      title: this.state.item,
      isDone: false
    }
    newTodos.push(item);
    this.setState({
      todos: newTodos
      item: ''
    })
  }

  updateItem(e) {
    this.setState({
      item: e.target.value,
    })
  }
  render() {
    return(
      <div className="container">
        <TodoForm
          item={ this.state.item }
          updateItem={ this.updateItem }
          addTodo={ this.addTodo }
        />
        <TodoList
         todos={ this.state.todos }
         deleteTodo={ this.deleteTodo }
         changeCheckbox={ this.changeCheckbox }
         />
      </div>
    )
  }
}

export default App;
```

## 一括削除　purge

```ts
import * as React from 'react';

const todos = [
  {id: 0, title: "Task 0", isDone: false },
  {id: 1, title: "Task 1", isDone: true },
  {id: 2, title: "Task 2", isDone: false },
]
function TodoItem(props) {
  const todo = props.todo;
  return (<li className={ todo.isDone ? 'done' : ''}>

    <input type="checkbox"
      onChange={ () => {props.changeCheckbox(todo)} }
      checked={ todo.isDone ? "chacked" : ""}
    />

    { todo.id } : { todo.title } : { String(todo.isDone) }

    <span className="delete" onClick={ () => {props.deleteTodo(todo.id)} } >[x]</span>

    </li>)
}

function TodoList (props) {
  const todos = props.todos.map( todo => {
    return <TodoItem
      key={ todo.id }
      todo={ todo }
      changeCheckbox={ props.changeCheckbox }
      deleteTodo={ props.deleteTodo }
      />
  });
  return(
    <ul>
      { todos }
    </ul>
  )
};

function TodoForm(props) {
  return(
    <form onSubmit={ props.addTodo }>
      <input type="text"
        value={ props.item }
        onChange={ props.updateItem }
      />
      <input type="submit" value="Add"/>
    </form>
  )

}

function TodoTitle(props) {
  return (
    <h1>
      Todo
      <button className="purge" onClick={ props.todoPurge }>Purge</button>
    </h1>
   )
}


class App extends React.Component {
  private state: any;
  private setState: any;
  constructor() {
    super();
    this.state = {
      todos: todos,
      item: '',
    }
    this.addTodo = this.addTodo.bind(this);
    this.updateItem = this.updateItem.bind(this);
    this.deleteTodo = this.deleteTodo.bind(this);
    this.changeCheckbox = this.changeCheckbox.bind(this);
    this.todoPurge = this.todoPurge.bind(this);
  }

  todoPurge() {
    const todos = this.state.todos.slice();
    const newTodos = todos.filter( todo => {
      return !todo.isDone;
    })

    this.setState({
      todos: newTodos
    })
  }

  changeCheckbox(todo) {
    const newTodos = this.state.todos.slice();
    const pos = newTodos.map( todo => {
      return todo.id
    }).indexOf(todo.id)
    newTodos[pos].isDone = !newTodos[pos].isDone

    this.setState({
      todos: newTodos
    })
  }

  deleteTodo(id) {
    // console.log("deleteBtn");
    const newTodos = this.state.todos.slice();
    const pos = newTodos.map( todo => {
      return todo.id
    }).indexOf(id)
    // console.log(pos);
    newTodos.splice(pos,1)

    this.setState({
      todos: newTodos
    })
  }

  addTodo(e) {
    e.preventDefault();
    interface Item {
      id: number;
      title: string;
      isDone: boolean;
    }

    const newTodos = this.state.todos.slice();
    const item: Item = {
      id: new Date().getTime(),
      title: this.state.item,
      isDone: false
    }
    newTodos.push(item);
    this.setState({
      todos: newTodos
      item: ''
    })
  }

  updateItem(e) {
    this.setState({
      item: e.target.value,
    })
  }
  render() {
    return(
      <div className="container">
        <TodoTitle todoPurge={ this.todoPurge } />
        <TodoForm
          item={ this.state.item }
          updateItem={ this.updateItem }
          addTodo={ this.addTodo }
        />
        <TodoList
         todos={ this.state.todos }
         deleteTodo={ this.deleteTodo }
         changeCheckbox={ this.changeCheckbox }
         />
      </div>
    )
  }
}

export default App;
```


## firebase / fireStore

fireStore を　組み込んでいます。
Vue.js編 と似ているので、同じように組み込んで見てください.
ただ, FireStore の組み込み方が React の方法に遵守できていないように思います。
`setState` が　からんでいない。。。
ここは、また更新するかも。


* コンテナ linux

```
uname
cd src
yarn add firebase
parcel --hmr-port 1235 --hmr-hostname localhost index.pug

```

`firebase.js`

```js
export const config = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
  measurementId: ""
};
```


React の　一方通行ルールは、 Vue.js をした後では、辛いですね。
`input`　処理書くだけでも、正直面倒くさい(笑)
`setState` を必ず使う必要があるのも、初心者キラーでコードも増えがちです。
個人でちょこちょこ書くなら, Vue.js の方が楽しいかと思います。　チームプレイで他のコードも見ないとだめなら、ルールの厳しいReact のほうがコードを追いやすいのかなと思います。




`src/App.tsx`

```ts
import * as React from 'react';
import * as firebase from 'firebase/app';
import 'firebase/firestore';
import { config } from '../firebase';

firebase.initializeApp(config);
const firestore = firebase.firestore()
const collection = firestore.collection('reactTodo');

function TodoItem(props) {
  const todo = props.todo;
  return (<li className={ todo.isDone ? 'done' : ''}>

    <input type="checkbox"
      onChange={ () => {props.changeCheckbox(todo)} }
      checked={ todo.isDone ? "chacked" : ""}
    />

    { todo.title } : { String(todo.isDone) } : { todo.id } : { todo.dbId }

    <span className="delete" onClick={ () => {props.deleteTodo(todo)} } >[x]</span>

    </li>)
}

function TodoList (props) {
  const todos = props.todos.map( todo => {
    return <TodoItem
      key={ todo.id }
      todo={ todo }
      changeCheckbox={ props.changeCheckbox }
      deleteTodo={ props.deleteTodo }
      />
  });
  return(
    <ul>
      { todos }
    </ul>
  )
};

function TodoForm(props) {
  return(
    <form onSubmit={ props.addTodo }>
      <input type="text"
        value={ props.item }
        onChange={ props.updateItem }
      />
      <input type="submit" value="Add"/>
    </form>
  )
}

function TodoTitle(props) {
  return (
    <h1>
      Todo
      <button className="purge" onClick={ props.todoPurge }>Purge</button>
    </h1>
   )
}


class App extends React.Component {
  private state: any;
  private setState: any;
  constructor() {
    super();
    this.state = {
      todos: [],
      itemsId: [],
      item: '',
    }
    this.addTodo = this.addTodo.bind(this);
    this.updateItem = this.updateItem.bind(this);
    this.deleteTodo = this.deleteTodo.bind(this);
    this.changeCheckbox = this.changeCheckbox.bind(this);
    this.todoPurge = this.todoPurge.bind(this);
  }

  async todoPurge() {
    console.log("purge...");
    const todos = this.state.todos.slice();
    const newTodos = todos.filter( todo => {
      return !todo.isDone;
    })

    this.setState({
      todos: newTodos
    })

      const itemsQuery = await collection.where("isDone", "==" , true)
        .get()
        .then(function(querySnapshot) {
          querySnapshot.forEach(function(doc) {
            const item = collection.doc(doc.id).delete();
            console.log(item);
          });
        })
        .catch(function(error) {
          console.log("Error getting documents: ", error);
        })


  }

  async changeCheckbox(todo) {
    // const newTodos = this.state.todos.slice();
    // const pos = newTodos.map( todo => {
    //   return todo.id
    // }).indexOf(todo.id)

    // newTodos[pos].isDone = !newTodos[pos].isDone
    // this.setState({
    //   todos: newTodos
    // })

    // setStateを使っていないので、ルール違反だが、簡潔に処理する方法が思いつかなかったよ
    await collection.doc(todo.dbId).update({
      isDone: !todo.isDone
    });
  }

  async deleteTodo(deleteTodo) {
    const newTodos = this.state.todos.slice();
    const pos = newTodos.map( todo => {
      return todo.id
    }).indexOf(deleteTodo.id)

    // なくても動くが、React ルール上付けている
    newTodos.splice(pos,1)
    this.setState({
      todos: newTodos,
    })

    await collection.doc(deleteTodo.dbId).delete();
  }

  async addTodo(e) {
    e.preventDefault();
    if (!this.state.item.trim()) return

    interface Item {
      id: number;
      title: string;
      isDone: boolean;
      created_at: any;
    }

    const newTodos = this.state.todos.slice(); // todos Deep Copy
    const item: Item = {
      id: new Date().getTime(),
      title: this.state.item,
      isDone: false,
      created_at: firebase.firestore.FieldValue.serverTimestamp()
    }
    newTodos.push(item);
    this.setState({
      todos: newTodos,
      item: '',
    })

    await collection.add(item)
      .then( (doc) => {
        console.log('Writed: ',doc.id);
      })
      .catch( err => {
        console.log(err);
      })
  }
  async componentDidUpdate() { // 更新用
    const query = await collection.orderBy("created_at", "desc").get()
    const itemsData = query.docs.map( item => {
      return ({
        dbId: item.id,
        id: item.data().id,
        title: item.data().title,
        isDone: item.data().isDone,
        created_at: item.data().created_at,
        date: item.data().date,
      })
    })
    this.setState({
      todos: itemsData,
    })
  }
  async componentDidMount() { // リロード用
    const query = await collection.orderBy("created_at", "desc").get()
    const itemsData = query.docs.map( item => {
      return ({
        dbId: item.id,
        id: item.data().id,
        title: item.data().title,
        isDone: item.data().isDone,
        created_at: item.data().created_at,
        date: item.data().date,
      })
    })
    this.setState({
      todos: itemsData,
    })
  }

  updateItem(e) { // input用
    this.setState({
      item: e.target.value,
    })
  }
  render() {
    return(
      <div className="container">
        <TodoTitle todoPurge={ this.todoPurge } />
        <TodoForm
          item={ this.state.item }
          updateItem={ this.updateItem }
          addTodo={ this.addTodo }
        />
        <TodoList
         todos={ this.state.todos }
         deleteTodo={ this.deleteTodo }
         changeCheckbox={ this.changeCheckbox }
         />
      </div>
    )
  }
}

export default App;

```

React 編　終了です。
React って便利! を実感するために、VanillaJS(素のJS) も予定しています。