Laravel5.7 + React + Redux + Thunk


[こちらの記事](https://qiita.com/zaburo/items/fbcdd73d8d707357c25f)のRedux対応版（改修の余地あり）です。個人的な備忘メモなので解説あまりしてません。

##準備

```bash
npm install --save redux react-redux redux-thunk
```

## ディレクトリ構造

Laravelのresourcesのjsの中を以下のようにしました。
Redux-Wayとも呼べない中途半端なもの。結局containerは利用しませんでした。

![スクリーンショット 2018-10-23 15.08.14.png](https://qiita-image-store.s3.amazonaws.com/0/55188/dd1ac12d-1c2e-1a0a-cbfc-c660f79d3e38.png)


## 実装

### app.js

Laravelにおけるエンドポイント。まあ、初期jsを読み込んでるだけ。

```js
require('./index.js');
```

### index.js

index.jsってのを作りました。

```js
import React from 'react';
import ReactDOM from 'react-dom';

import { Provider, connect } from 'react-redux';
import createStore from './createStore';
import TodoApp from './components/TodoApp';


const store = createStore();

ReactDOM.render(
    <Provider store={store}>
        <TodoApp />
    </Provider>,
    document.getElementById('todoApp')
);
```

### components/Todo.js

コンポーネントとしては一番根っこになるTodoApp。
中のテーブル行のレンダリングで、更に子の\<RenderList></RenderList>を呼んでいる。


```js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import { Provider, connect } from 'react-redux';
import createStore from '../createStore';
import * as actions from '../actions/Todo';
import RenderList from './RenderList';


class TodoApp extends Component {

    constructor(){
        super();
        this.inputChange = this.inputChange.bind(this);
        this.addTodo = this.addTodo.bind(this);
    }

    inputChange(event){

        switch(event.target.name){
            case 'todo':
                this.props.changeTodo(event.target.value);
                break;
            default:
                break;

        }
    }

    addTodo(){

        if(this.props.state.todo == ''){
            return;
        }
        
        this.props.addTodo(this.props.state.todo);
        this.props.changeTodo('');
    }


    render() {
        return (
            <React.Fragment>
                {/* form */}
                <div className="form-group mt-4">
                    <label htmlFor="todo">新規Todo</label>
                    <input type="text" className="form-control" id="todo" name="todo" value={this.props.state.todo} onChange={this.inputChange}/>
                </div>
                <button type="submit" className="btn btn-primary" onClick={this.addTodo}>登録</button>
                {/* table */}
                <table className="table mt-5">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>タスク</th>
                            <th>完了</th>
                        </tr>
                    </thead>
                    <tbody>
                        <RenderList/>
                    </tbody>
                </table>
            </React.Fragment>
        );
    }

    componentDidMount(){
        this.props.getTodos();
    }
}

const mapStateToProps = state => {
    return {
        state: state.todos,
    }
}

const mapDispatchToProps = dispatch => {
    return {
        getTodos: () => dispatch(actions.getTodos()),
        addTodo: (todo) => dispatch(actions.addTodo(todo)),
        changeTodo: (todo) => dispatch(actions.changeTodo(todo))
    }
}

export default connect(mapStateToProps,mapDispatchToProps)(TodoApp);
```

### components/RenderList.js

\<tr></tr>をレンダリングしている。
Reduxを利用して、バケツリレーせず、TodoAppでもRenderListでも同じ値やメソッドを呼び出している。

```js
import React from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions/Todo';


class RenderList extends React.Component{

    constructor(){
        super();
        this.deleteTask = this.deleteTask.bind(this);
    }

    deleteTask(todo){
        this.props.deleteTodo(todo);
    }

    render(){

        const todolist = this.props.state.todos.map(todo => {
            return (
                <tr key={todo.id}>
                    <td>{todo.id}</td>
                    <td>{todo.title}</td>
                    <td><button className="btn btn-secondary" onClick={() => this.deleteTask(todo)}>完了</button></td>
                </tr>
            );
        });

        return (todolist);
    }
}

const mapStateToProps = state => {
    return {
        state: state.todos,
    }
}

const mapDispatchToProps = dispatch => {
    return {
        deleteTodo: (todo) => dispatch(actions.deleteTodo(todo)),
    }
}

export default connect(mapStateToProps,mapDispatchToProps)(RenderList);
```

### createStore.js

createStoreってファイル作る文化はないのだけど、作ってみました。storeディレクトリ作ってもいいのかも。
thunkの利用を宣言しています。

```js
import { createStore as reduxCreateStore, applyMiddleware, combineReducers } from "redux";
import { todoReducer } from "./reducers/Todo";
import thunk from 'redux-thunk';

export default function createStore() {
    const store = reduxCreateStore(
        combineReducers({
            todos: todoReducer,
        }),
        applyMiddleware(
            thunk,
        )
    );

    return store;
}
```

### reducers/Todo.js

reducer。基本的にTODOのCRUD用ですが、CHANGE_TODOだけ、input の値更新用となっている。

```js
const initialState = {
    todos: [],
    todo: ''
}

export const todoReducer = (state = initialState, action) => {
    // console.log(action.todos);
    switch (action.type){
        case 'GET_TODOS':
            const getState = Object.assign({}, state);
            getState.todos = action.todos;
            return getState;
        case 'ADD_TODO':
            const addState = Object.assign({}, state); 
            addState.todos = action.todos;
            return addState;
        case 'DELETE_TODO':
            const delState = Object.assign({}, state);
            delState.todos = action.todos;
            return delState;
        case 'CHANGE_TODO':
            const changeState = Object.assign({}, state);
            changeState.todo = action.todo
            return changeState;
        default:
            return state;
    }
}
```

### actions/Todo.js

action。changeTodoはUpdateではなくてinputのonChange()に対応する用のメソッドとした（もっといい方法あると思う）。
thunk使って、actionの中で非同期処理してます。記述は最低限です。

```js
import axios from 'axios';

export const addTodo = (todo) => async dispatch => {

    const response = await axios.post('/api/add',{title: todo});

    dispatch({
        type: 'ADD_TODO',
        todos: response.data
    });
}

export const getTodos = () => async dispatch => {

    const response = await axios.get('/api/get');

    dispatch({
        type: 'GET_TODOS',
        todos: response.data
    });
}

export const deleteTodo = (todo) => async dispatch => {

    const response = await axios.post('/api/del',{id: todo.id});

    dispatch({
        type: 'DELETE_TODO',
        todos: response.data
    });

}

//onChange用
export const changeTodo = (todo) => {
    return {
        type: 'CHANGE_TODO',
        todo: todo
    }
}
```

とりあえず以上。