Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
26
Help us understand the problem. What is going on with this article?
@nitaking

React Native × Hooks => Todo app

More than 1 year has passed since last update.

React Hooks on React Native

注意: 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

Hooksについて

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

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

環境構築

react/react-native

まずはinitします。

$ react-native init hooks_todo

2018/11/26時点ではreact nativeはhooksに対応していません。
使えるようにするには、reactのリポジトリからcloneしてゴニョゴニョして・・・と面倒です。

Using React Hooks in React Native · Issue #21967 · facebook/react-native

今回は有志の方がforkしたセットアップ済みreact-nativeを使っていきます!

https://github.com/facebook/react-native/issues/21967#issuecomment-438473603

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

注意

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

image.png

蛇足

react-native-hooksというライブラリも作られ始めているようですが、これはreact-nativeのmoduleにhooksをラッパーとして組み込んだもののようなので今回の記事では触れません!

そして

$ yarn start

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

image.png

native-base

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

React Native版Bootstrapです。

$ yarn add native-base
$ react-native link

構成

今回の構成についてです。 refsの記事を参考にしています。

Component

  • ToDoの入力
  • ToDo一覧
<Layout>
  <AddTodoForm />
  <TodoList />
</Layout>

機能

  • Todoの追加
  • Todoのcheck/unCheck
  • Todoの削除

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

実装

<Layout>

<Header>を持ったComponentを作ります。

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-baseInputIconで実装していきます。

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で実行します。

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?

-- const Component = (props) => {};
++ const Component = memo((props) => {});

Reactでは再レンダリングが多いとパフォーマンスが悪くなります。
shouldComponentUpdateなどでpropsが同じ場合に再レンダリングしないようにパフォーマンスチューニングを行うのですが、
Class ComponentにはshouldComponentUpdateをデフォルトで行うReact.PureComponentがありました。
Function Componentでこれを実装しているのがReact.memoになります。

詳細: React.memoでrecomposeのpureが要らなくなった話 - Qiita

App.js

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

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だけでは足りない用途を補うため、

const [state, setState] = useState(initialState);

用途別の関数を返すラッパーを定義します。

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

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

refs

Special Thanks.
- Making a beautiful Todo App using React Hooks + Material UI

26
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
nitaking
ReactNativeエンジニアからのフルスタックエンジニア zennはこちら: https://zenn.dev/nitaking
aircloset
「新しい当たり前を作る」を作ることをミッションに、airClosetを開発・運営しています。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
26
Help us understand the problem. What is going on with this article?