React Native × Hooks => Todo app

## React Hooks on React Native

<img src="https://qiita-image-store.s3.amazonaws.com/0/103475/4e8ce047-f286-7a10-dcda-ce72cff7100f.png" width="450"> 

> **注意**: [v0.59.0-rc.1](https://github.com/facebook/react-native/releases/tag/v0.59.0-rc.1)でhooksが使えるようになり、導入部分は古い内容になります。（2019-02-17）

Hooksでは、`Function Conponent`でstateを扱えるようになりました。
`useState`を使ったTodoアプリやらの記事が結構増えています。

そろそろ、React Native × Hooks でToDoアプリですよね！
> github: [nitaking/react-native-hooks-todo](https://github.com/nitaking/react-native-hooks-todo)


## Hooksについて
既に解説記事が多くありますので割愛しますが、
個人的に「sfcで組んでたけどstate使いたくなったから`Class Componennt`にリファクタ...」という時間が減るのでいいですね。

React Hooksとは？useStateとかってなに？ってかたはこちら↓

- [【React】新機能hooks - Qiita](https://qiita.com/YutamaKotaro/items/ef0430f570779dcf8a26)
- [The Rise of Functional Components - React v16.7.0 (now with Hooks!)](https://blog.weareredlight.com/the-rise-of-functional-components-react-v16-7-0-now-with-hooks-90cac72b8ab7)

## 環境構築
### react/react-native
まずはinitします。

```shell
$ react-native init hooks_todo
```

2018/11/26時点ではreact nativeはhooksに対応していません。
使えるようにするには、reactのリポジトリからcloneしてゴニョゴニョして・・・と面倒です。
> [Using React Hooks in React Native · Issue #21967 · facebook/react-native](https://github.com/facebook/react-native/issues/21967#issuecomment-434113687)
 
今回は有志の方がforkしたセットアップ済みreact-nativeを使っていきます！
> https://github.com/facebook/react-native/issues/21967#issuecomment-438473603

```shell
$ yarn add react@next react-native@"npm:@brunolemos/react-native"
```

#### *注意*
うまくいかないときは以下のエラーが出ます。あくまでbeta版を力技で使えるようにしているので不安定な部分が多く、注意です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103475/592f11fe-0b65-d8c3-2731-494eb671f128.png)



#### *蛇足*
[react-native-hooks](https://github.com/react-native-community/react-native-hooks)というライブラリも作られ始めているようですが、これはreact-nativeのmoduleにhooksをラッパーとして組み込んだもののようなので今回の記事では触れません！

#### そして

```shell
$ yarn start
```

いつもの画面がでました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/103475/ce8d5bee-0008-a86e-c2f6-7c29019dfb15.png)


### native-base
今回は見え感を良くするために`native-base`を使用します。

React Native版Bootstrapです。

```shell
$ yarn add native-base
$ react-native link
```


## 構成
今回の構成についてです。 [refs](https://qiita.com/nitaking/items/e9bf4c52046004ef3186#refs)の記事を参考にしています。

### Component
- ToDoの入力
- ToDo一覧

```js
<Layout>
  <AddTodoForm />
  <TodoList />
</Layout>
```

### 機能
- Todoの追加
- Todoのcheck/unCheck
- Todoの削除

今回はuseStateを使用し、それ拡張して実装していきます。

## 実装
### `<Layout>`
`<Header>`を持ったComponentを作ります。

```jsx:Layout.js
import React, { memo } from 'react';
import { StyleSheet, View } from 'react-native';
import { Container, Header, Body, Title, Content } from "native-base";

const Layout = memo((props) => {
  const { children } = props;

  return (
    <Container>
      <Header>
        <Body>
          <Title>Hooks Todo List</Title>
        </Body>
      </Header>
      <View style={styles.container}>
      <Content style={styles.content}>
        {children}
      </Content>
      </View>
    </Container>
  );
});

export default Layout;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  content: {
    width: 350,
  },
});
```

### `AddTodoForm`
TextInputと入力ボタンを持つComponentを
`native-base`の`Input`と`Icon`で実装していきます。

```jsx:AddTodoForm.js
import React, { memo }  from 'react';
import { Input, Item, Icon } from 'native-base';

const AddTodoList = memo((props) => {
  const { inputValue, changeInput, onIconPress } = props;

  return (
    <Item success>
      <Input
        placeholder='Input todo...'
        value={inputValue}
        onChange={changeInput}
        onChangeText={props.onInputKeyPress}
      />
      <Icon type="Feather" name='plus' onPress={onIconPress} />
    </Item>
  );
});

export default AddTodoList;
```

### `TodoList`
propsからtodoListを受け取って表示します。

- todo-list
- check/uncheck
- remove task

これらをuseStateの関数に定義し、propsを経由して受け取り、onPressで実行します。

```jsx:TodoList.js
import React, { memo } from 'react';
import { StyleSheet, FlatList } from 'react-native';
import { Text, ListItem, Left, Icon, Body, Right } from "native-base";

const TodoList = memo((props) => {
  const { items, onItemCheck, onItemRemove } = props;

  return (
    <FlatList
      data={items}
      contentContainerStyle={styles.listView}
      renderItem={({ item, index }) =>
        <ListItem icon>
          <Left>
            <Icon
              type="MaterialIcons"
              style={styles.checkbox}
              name={item.checked ? "check-box" : "check-box-outline-blank"}
              onPress={() => onItemCheck(index)}
            />
          </Left>
          <Body>
          <Text>{item.todoText}</Text>
          </Body>
          <Right>
            <Icon
              type="FontAwesome"
              style={styles.icon}
              name="trash-o"
              onPress={() => onItemRemove(index)}
            />
          </Right>
        </ListItem>
      }
    />
  );
});

export default TodoList;

const styles = StyleSheet.create({
  listView: {
    flex: 1,
  },
  icon: {
    color: 'grey',
  },
  checkbox: {
    color: 'grey',
    fontSize: 20,
  }
});
```

#### React.memo?
```diff
-- const Component = (props) => {};
++ const Component = memo((props) => {});
```
Reactでは再レンダリングが多いとパフォーマンスが悪くなります。
`shouldComponentUpdate`などでpropsが同じ場合に再レンダリングしないようにパフォーマンスチューニングを行うのですが、
Class Componentには`shouldComponentUpdate`をデフォルトで行う`React.PureComponent`がありました。
`Function Component`でこれを実装しているのが`React.memo`になります。
> 詳細: [React.memoでrecomposeのpureが要らなくなった話 - Qiita](https://qiita.com/tatane616/items/b0e8b4863e28104abd6c)

### App.js
App.jsでカスタムuseStateをそれぞれのComponentに渡していきます。
useStateのラッパーを`hooks/todoList`に定義しています。

```jsx:App.js
import React, { memo }  from 'react';
import Layout from "./component/Layout";
import TodoList from "./component/TodoList";
import AddTodoForm from "./component/AddTodoForm";

import { useInputValue, useTodos } from "./hooks/todoList";

const App = memo((props) => {
  const { inputValue, changeInput, clearInput } = useInputValue();
  const { todos, addTodo, checkTodo, removeTodo } = useTodos();

  const clearInputAndAddTodo = _ => {
    clearInput();
    addTodo(inputValue);
  };

  return (
      <Layout>
        <AddTodoForm
          inputValue={inputValue}
          changeInput={changeInput}
          onIconPress={clearInputAndAddTodo}
        />
        <TodoList
          items={todos}
          onItemCheck={idx => checkTodo(idx)}
          onItemRemove={idx => removeTodo(idx)}
        />
      </Layout>
  );
});

export default App;
```

###  custom hooks
ここでは以下のuseStateだけでは足りない用途を補うため、

```jsx
const [state, setState] = useState(initialState);
```
用途別の関数を返すラッパーを定義します。


```jsx:hooks/todolist.js
import { useState } from 'react';

export const useInputValue = (initialValue = '') => {
  const [inputValue, setInputValue] = useState(initialValue);

  return {
    inputValue,
    changeInput: event => {
      setInputValue(event.nativeEvent.text)},
    clearInput: () => setInputValue(''),
  };
};


export const useTodos = (initialValue = []) => {
  const [todos, setTodos] = useState(initialValue);

  return {
    todos,

    addTodo: todoText => {
      if (todoText !== '') {
        setTodos(todos.concat({ todoText, checked: false }));
      }
    },

    checkTodo: checkIndex => {
      setTodos(
        todos.map((todo, index) => {
          if (checkIndex === index) todo.checked = !todo.checked;
        return todo;
        })
      )
    },

    removeTodo: removeIndex => {
      setTodos(todos.filter((todos, index) => removeIndex !== index));
    }
  };
};
```


そうすると・・・
![2018-11-25_01.gif](https://qiita-image-store.s3.amazonaws.com/0/103475/f9e7e31c-b9f3-a227-f2f4-ffa4d66e4bdc.gif)

reduxよりもサクッと作成できて、使いやすいですね。
native-baseを使って簡単にいい感じの見た目にできました。

https://github.com/nitaking/react-native-hooks-todo

## refs
Special Thanks.
- [Making a beautiful Todo App using React Hooks + Material UI](https://itnext.io/making-a-beautiful-todo-app-using-react-hooks-material-ui-52dacf3245f4)