React-Reduxでajaxを呼ぶ

TODOListサンプルコードを参考にしながら、ajaxを呼ぶのを書いてみる。

プロジェクトはcreate-react-appを使用して作成する

> create-react-app Test
> cd ./Test
> npm install --save redux
> npm install --save jquery
> npm install --save material-ui@1.0.0-beta.30

とりあえず適当なjson形式のファイルをajaxで取得して表示するようなのを書いてみました。

自動生成されたindex.jsを書き換えていきます。
本来なら機能ごとにファイル分割するべきですが、
まずは動作確認用なので１ファイルで全部書いてしまいます。

material-uiで少し見た目もよくしてみた

```javascript:index.js
import React from 'react';
import {render} from 'react-dom';
import './index.css';
import registerServiceWorker from './registerServiceWorker';
import { createStore } from 'redux';
import $ from 'jquery';
import Reboot from 'material-ui/Reboot';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import Checkbox from 'material-ui/Checkbox';
import List, {ListItem, ListItemText} from 'material-ui/List';
import Table, { TableBody, TableCell, TableHead, TableRow } from 'material-ui/Table';

const initialState = {
  tasks: []
};

function tasksReducer(state = initialState, action){
  switch(action.type){
    case 'INPUT_TASK':
      return {
        ...state,
        task : action.payload.task
      }
    case 'ADD_TASK':
      return{
        ...state,
        tasks: state.tasks.concat([action.payload.task])
      };
    default:
      return state;
  }
}

const store = createStore(tasksReducer);


const addTask = (task) => ({
  type:'ADD_TASK',
  payload:{
    task,
  }
});


function TodoApp({ store }){
  const { tasks } = store.getState();
  return  (
    <div>
      <Reboot />
      <AppBar position="static">
      <Toolbar style={{backgroundColor: '#ffd699',}}>
        <Typography type="title" color="inherit" style={{color:"white"}}>
          Sample
        </Typography>
      </Toolbar>
      </AppBar>
      <div style={{padding:'16px'}} />
      <Button raised color="primary" type="button" onClick={() => clickButton()}>実行</Button>

      <Table>
        <TableHead>
          <TableRow>
            <TableCell>#</TableCell>
            <TableCell numeric>No.</TableCell>
            <TableCell numeric>menu</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {
            tasks.map(function(item,i){
              return (
                <TableRow key={i}>
                  <TableCell style={{width:'48px'}}>
                  <Checkbox/>
                  </TableCell>
                  <TableCell>{i}</TableCell>
                  <TableCell numeric>{`${item}`}</TableCell>

                </TableRow>
              );
            })
          }
        </TableBody>
      </Table>
    </div>
  );
}

function clickButton(){
  const { task, tasks } = store.getState();

  $.ajax({
    url: './test.json',
    type: 'GET',
    dataType: 'text',
    data: "",
    timeout: 5000,
    })
    .done(function(data){
      var jsonData = JSON.parse(data);
      for( var val in jsonData){
        store.dispatch(addTask(jsonData[val]));
      }

    })
    .fail(function() {
        // 通信失敗時の処理を記述
        store.dispatch(addTask("fail."));
  });
}

function renderApp(store){
  render(
    <TodoApp store={store} />,
    document.getElementById('root')
  );
}

store.subscribe(() => renderApp(store));
renderApp(store);

registerServiceWorker();

```


```json:package.json
{
  "name": "Test",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "jquery": "^3.3.1",
    "material-ui": "^1.0.0-beta.30",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "1.1.1",
    "redux": "^3.7.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}
```

ファイルを作って格納しておいて
> npm start


↑で作ったのをファイルで分割してみます。

最終的なファイル構成的にはこのようにする
│  index.css
│  index.js
│  logo.svg
│  registerServiceWorker.js
├─actions
│      tasks.js
├─components
│      TodoApp.js
│
├─containers
│      TodoApp.js
├─reducers
│       tasks.js

```js:actions/tasks.js
export const addTask = (tasks) => ({
  type:'ADD_TASK',
  payload:{
    tasks,
  }
});
```

```js:containers/TodoApp.js
import {connect} from 'react-redux'
import TodoApp from '../components/TodoApp.js';
import {addTask} from '../actions/tasks.js';

function mapStateToProps({task, tasks}){
  return {
    tasks,
  };
}

function mapDispatchToProps(dispatch){
  return {
    addTask(tasks){
      dispatch(addTask(tasks));
    }
  };
}

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

```js:components/TodoApp.js
import React from 'react';
import $ from 'jquery';
// import Reboot from 'material-ui/Reboot';
import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import Checkbox from 'material-ui/Checkbox';
import Table, { TableBody, TableCell, TableHead, TableRow } from 'material-ui/Table';


function clickButton(addTask){
  $.ajax({
    url: './test.json',
    type: 'GET',
    dataType: 'text',
    data: "",
    timeout: 5000,
    })
    .done(function(data){
      var jsonData = JSON.parse(data);

      for( var val in jsonData){
        addTask(jsonData[val]);
      }

    })
    .fail(function() {
        // 通信失敗時の処理を記述
        addTask("fail.");
  });
}

export default function TodoApp({ tasks,inputTask,addTask }){

  return  (
    <div>
      <AppBar position="static">
      <Toolbar style={{backgroundColor: '#ffd699',}}>
        <Typography type="title" color="inherit" style={{color:"white"}}>
          Sample
        </Typography>
      </Toolbar>
      </AppBar>
      <div style={{padding:'16px'}} />
    <Button variant="raised" color="primary"  type="button" onClick={() => clickButton(addTask)}>実行</Button>

      <Table>
        <TableHead>
          <TableRow>
            <TableCell>#</TableCell>
            <TableCell numeric>No.</TableCell>
            <TableCell numeric>menu</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {

            tasks.map(function(item,i){
              return (
                <TableRow key={i}>
                  <TableCell style={{width:'48px'}}>
                  <Checkbox/>
                  </TableCell>
                  <TableCell>{i}</TableCell>
                  <TableCell numeric>{`${item}`}</TableCell>
                </TableRow>
              );
            })
          }
        </TableBody>
      </Table>
    </div>
  );
}
```

```js:reducers/tasks.js
const initialState = {
  tasks: []
};

export default function tasksReducer(state = initialState, action){
  switch(action.type){
    case 'ADD_TASK':
      return{
        ...state,
        tasks: state.tasks.concat([action.payload.tasks])
      };
    default:
      return state;
  }
}
```

```js:index.js
import React from 'react';
import {Provider} from 'react-redux';
import { createStore } from 'redux';
import {render} from 'react-dom';
import tasksReducer from './reducers/tasks.js';
import TodoApp from './containers/TodoApp.js';

import registerServiceWorker from './registerServiceWorker';


const store = createStore(tasksReducer);

function renderApp(store){
  render(
    <Provider store={store}>
      <TodoApp />
    </Provider>,
    document.getElementById('root')
  );
}

renderApp(store);

registerServiceWorker();
```

```json:package.json
{
  "name": "test",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "jquery": "^3.3.1",
    "material-ui": "^1.0.0-beta.30",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-redux": "^5.0.7",
    "react-scripts": "1.1.1",
    "redux": "^3.7.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}