React Redux: 公式サイト「Basic Tutorial」の作例をフックで書き替える

React 16.8から、関数コンポーネントにReactの機能を加える[フック(hook)](https://ja.reactjs.org/docs/hooks-intro.html)が採り入れられました。足並みを揃えてReact Redux 7.1.0に備わったのが、Reduxにつなげる[フック](https://react-redux.js.org/api/hooks)です。React Redux公式「[Basic Tutorial](https://react-redux.js.org/introduction/basic-tutorial)」の作例「[Todo App with Redux](https://codesandbox.io/s/9on71rvnyo?file=/src/redux/actionTypes.js)」は、残念ながらReact 16.4.2でつくられ、どちらのフックも使われていません。そこで、フック対応に書き替えてみようというのが本稿のお題です。

[![2005001_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/c6e44df8-1ab2-fc2b-f4e8-8a1125596afc.png)
\>\> Todo App with Redux](https://codesandbox.io/s/9on71rvnyo?file=/src/redux/actionTypes.js)



# クラスコンポーネントを関数コンポーネントに書き替える
まず、モジュール`src/components/AddTodo.js`は、コンポーネントに状態(`state`)をもたせるため、クラスで定められています。Reactのフック`useState()`を使って、関数コンポーネントに直しましょう(「[React Hooks: クラスのコンポーネントをuseState()で関数に書き替える](http://www.fumiononaka.com/Business/html5/FN2004003.html)」参照)。関数コンポーネントの中では`this`参照がなくなることにご注目ください。

```react:src/components/AddTodo.js
// import React from 'react';
import React, { useState } from 'react';

// class AddTodo extends React.Component {
const AddTodo = ({ addTodo }) => {
	/* constructor(props) {
		super(props);
		this.state = { input: '' };
	} */
	const [input, setInput] = useState('');

	// updateInput = input => {
	const updateInput = input => {
		// this.setState({ input });
		setInput(input);
	};

	// handleAddTodo = () => {
	const handleAddTodo = () => {
		// this.props.addTodo(this.state.input);
		addTodo(input);
		// this.setState({ input: '' });
		setInput('');
	};

	// render() {
	return (
		<div>
			<input
				// onChange={e => this.updateInput(e.target.value)}
				onChange={e => updateInput(e.target.value)}
				// value={this.state.input}
				value={input}
			/>
			{/* <button className='add-todo' onClick={this.handleAddTodo}> */}
			<button className='add-todo' onClick={handleAddTodo}>
				Add Todo
			</button>
		</div>
	);
	// }
};
```


# useDispatch()を使う
つぎに、React Reduxのフックを使ってゆきます。`connect()`関数の第2引数に与え、アクションを発行する`dispatch()`につなげるフックが`useDispatch()`です。アクションクリエーター(`addTodo`)は、フックから返された関数(`dispatch()`)で呼び出します。

```react:src/components/AddTodo.js
// import { connect } from 'react-redux';
import { useDispatch } from 'react-redux';

// const AddTodo = ({ addTodo }) => {
const AddTodo = () => {

	const dispatch = useDispatch();

	const handleAddTodo = () => {
		// addTodo(input);
		dispatch(addTodo(input));
	};

};

/* export default connect(
	null,
	{ addTodo }
)(AddTodo); */
export default AddTodo;
```

これでモジュール`src/components/AddTodo.js`が純粋な関数コンポーネントになりました。`src/components/Todo.js`も、同じように`useDispatch()`で`connect()`のラップが外せます。

```react:src/components/Todo.js
// import { connect } from 'react-redux';
import { useDispatch } from 'react-redux';
import cx from 'classnames';
import { toggleTodo } from '../redux/actions';

// const Todo = ({ todo, toggleTodo }) => (
const Todo = ({ todo }) => {
	const dispatch = useDispatch();
	return (
		// <li className='todo-item' onClick={() => toggleTodo(todo.id)}>
		<li className='todo-item' onClick={() => dispatch(toggleTodo(todo.id))}>

		</li>
	);
}

/* export default connect(
	null,
	{ toggleTodo }
)(Todo); */
export default Todo;
```


# useSelector()を使う
`connect()`の第1引数(`mapStateToProps`)が担う、Storeから状態(`state`)を取り出すフックが`useSelector()`です。渡すのは関数で、引数(`state`)の状態から処理した値を返します。モジュール`src/components/TodoList.js`は、つぎのように書き替えればよいでしょう。

```react:src/components/TodoList.js
// import { connect } from 'react-redux';
import { useSelector } from 'react-redux';

// const TodoList = ({ todos }) => (
const TodoList = () => {
	const todos = useSelector(state => {
		const visibilityFilter = state.visibilityFilter;
		const todos = getTodosByVisibilityFilter(state, visibilityFilter);
		return todos;
	});

}

/* const mapStateToProps = state => {
	const { visibilityFilter } = state;
	const todos = getTodosByVisibilityFilter(state, visibilityFilter);
	return { todos };
};
export default connect(mapStateToProps)(TodoList); */
export default TodoList;
```

残るモジュール`src/components/VisibilityFilters.js`は、`connect()`にふたつの引数が与えられています。したがって、フックも`useSelector()`と`useDispatch()`をともに使わなければなりません。

```react:src/components/VisibilityFilters.js
// import { connect } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';

// const VisibilityFilters = ({ activeFilter, setFilter }) => {
const VisibilityFilters = () => {
	const activeFilter = useSelector(state => state.visibilityFilter);
	const dispatch = useDispatch();
	return (
		<div className='visibility-filters'>
			{Object.keys(VISIBILITY_FILTERS).map(filterKey => {
				const currentFilter = VISIBILITY_FILTERS[filterKey];
				return (
					<span

						onClick={() => {
							// setFilter(currentFilter);
							dispatch(setFilter(currentFilter));
						}}
					>
						{currentFilter}
					</span>
				);
			})}
		</div>
	);
};

/* const mapStateToProps = state => {
	return { activeFilter: state.visibilityFilter };
};
export default connect(
	mapStateToProps,
	{ setFilter }
)(VisibilityFilters); */
export default VisibilityFilters;
```

以上でReact Redux公式「Basic Tutorial」の作例が、フックを使った純粋な関数コンポーネントに書き直せました。でき上がりはCodeSandboxに「[Todo App with Redux using Hooks](https://codesandbox.io/s/todo-app-with-redux-using-hooks-8ofkv)」として公開しました。

[![2005001_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/2da7862c-b80f-b616-8b54-226b47d59a2d.png)
\>\> Todo App with Redux using Hooks](https://codesandbox.io/s/todo-app-with-redux-using-hooks-8ofkv)