---
# System prepended metadata

title: '[3]Django Rest API + React.js + Redux + webpack 前後端整合'
tags: [redux, Django, webpack, python, React.js, Django Rest framework]

---

# [3]Django Rest API + React.js + Redux + webpack 前後端整合

###### tags: `python` `Django` `Django Rest framework`  `React.js` `redux` `webpack`
> [time= 2019 11 30 ] 

> 原文 & 參考：
> https://www.youtube.com/watch?v=BmL8iaLMnQ0&list=PLillGF-RfqbbRA-CIUxlxkUpbq0IFkX60&index=3

<br>

## Redux & HTTP
在 Chrome 瀏覽器安裝 Redux DevTools 
![](https://i.imgur.com/EijeTgJ.png)

<br><br><br>

安裝 redux react-redux redux-thunk redux-devtools-extension
*終端機目前位置 `~/django_rest_api_react/`*
```=
$ npm i redux react-redux redux-thunk redux-devtools-extension
```
> *redux-devtools-extension@2.13.8*
> *redux-thunk@2.3.0*
> *react-redux@7.1.3*
> *redux@4.0.4*

<br><br><br>

新增一個檔案命名為 `./leadmanager/frontend/src/store.js` 在裡面寫入：
```javascript=
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const initialState = {};
const middleware = [thunk];
const store = createStore(
    rootReducer,
    initialState,
    composeWithDevTools(applyMiddleware(...middleware))
);

export default store;
```

資料結構
```
django_rest_api_react
├──leadmanager
│  ├──frontend
│  │  ├──migrations
│  │  ├──src
│  │  │  ├──components
│  │  │  ├──index.js
│  │  │  └──store.js
│  │  │
...
```

<br><br><br>

新增一個檔案命名為 `./leadmanager/frontend/src/reducers/index.js` 在裡面寫入：
```javascript=
import { combineReducers } from 'redux';
import leads from './leads';

export default combineReducers({
    leads
});
```

資料結構
```
django_rest_api_react
├──leadmanager
│  ├──frontend
│  │  ├──migrations
│  │  ├──src
│  │  │  ├──components
│  │  │  ├──reducers
│  │  │  │  └──index.js
│  │  │  │
│  │  │  ├──index.js
│  │  │  └──store.js
│  │  │
...
```

<br><br><br>

開啟 `./leadmanager/frontend/src/components/App.js` 在裡面加入：
```javascript=
...
import Dashboard from './leads/Dashboard';
import { Provider } from 'react-redux'; // new add
import store from '../store'; // new add

export class App extends Component {
    render() {
        return (
            <Provider store={store}> // new add
                <Fragment>
                    <Header />
                    <div className="container">
                        <Dashborad />
                    </div>
                </Fragment>
            </Provider> // new add
        )
    }
}

ReactDom.render(<App />, document.getElementById('app'));
```

<br><br><br>

### GET LEADS
新增一個檔案命名為 `./leadmanager/frontend/src/reducers/leads.js` 在裡面寫入：
```javascript=
import { GET_LEADS } from '../actions/types.js'

const initailState = {
    leads: []
};

export default function (state = initailState, action) {
    switch (action.type) {
        case GET_LEADS:
            return {
                ...state,
                leads: action.payload
            }
        default:
            return state;
    }
}
```

資料結構
```
django_rest_api_react
├──leadmanager
│  ├──frontend
│  │  ├──migrations
│  │  ├──src
│  │  │  ├──components
│  │  │  ├──reducers
│  │  │  │  ├──index.js
│  │  │  │  └──leads.js
│  │  │  │
│  │  │  ├──index.js
│  │  │  └──store.js
│  │  │
...
```

<br><br><br>

新增一個檔案命名為 `./leadmanager/frontend/src/actions/types.js` 在裡面寫入：
```javascript=
export const GET_LEADS = 'GET_LEADS';
```

資料結構
```
django_rest_api_react
├──leadmanager
│  ├──frontend
│  │  ├──migrations
│  │  ├──src
│  │  │  ├──actions
│  │  │  │  └──types.js
│  │  │  │
│  │  │  ├──components
│  │  │  ├──reducers
│  │  │  ├──index.js
│  │  │  └──store.js
│  │  │
...
```

<br><br><br>

安裝 axios
```=
$ npm i axios
```
> *axios@0.19.0*

<br><br><br>

新增一個檔案命名為 `./leadmanager/frontend/src/actions/leads.js` 在裡面寫入：
```javascript=
import axios from 'axios';
import { GET_LEADS } from './types';

export const getLeads = () => dispatch => {
    axios.get('/api/leads')
        .then(res => {
            dispatch({
                type: GET_LEADS,
                payload: res.data
            });
        })
        .catch(err => console.log(err));
};
```

資料結構
```
django_rest_api_react
├──leadmanager
│  ├──frontend
│  │  ├──migrations
│  │  ├──src
│  │  │  ├──actions
│  │  │  │  ├──leads.js
│  │  │  │  └──types.js
│  │  │  │
│  │  │  ├──components
│  │  │  ├──reducers
│  │  │  ├──index.js
│  │  │  └──store.js
│  │  │
...
```

<br><br><br>

開啟 `./leadmanager/frontend/src/components/leads/Leads.js` 在裡面加入：
```javascript=
import React, { Component } from 'react'
import { connect } from 'react-redux'; // new add
import PropTypes from 'prop-types'; // new add
import { getLeads } from '../../actions/leads'; // new add

export class Leads extends Component {
    // new add
    static propTypes = {
        leads: PropTypes.array.isRequired,
        getLeads: PropTypes.func.isRequired
    }

    // new add
    componentDidMount(){
        this.props.getLeads();
    }
    
    render() {
        return (
            <div>
                <h1>Leads List</h1>
            </div>
        )
    }
}

// new add
const mapStateToProps = state => ({
    leads: state.leads.leads
})

// modify
export default connect(mapStateToProps, { getLeads })(Leads);
```

現在 redux 已經取得資料了
![](https://i.imgur.com/qETBoZN.png)

<br><br><br>

開啟 `./leadmanager/frontend/src/components/leads/Leads.js` 在裡面加入：
```javascript=
import React, { Component, Fragment } from 'react' // modify
import { connect } from 'react-redux'; 
...

export class Leads extends Component {
    ...

    render() {
        return (
            // modify
            <Fragment>
                <h2>Leads</h2>
                <table className="table table-striped">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Name</th>
                            <th>Email</th>
                            <th>Message</th>
                            <th />
                        </tr>
                    </thead>
                    <tbody>
                        {this.props.leads.map(lead => (
                            <tr key={lead.id}>
                                <td>{lead.id}</td>
                                <td>{lead.name}</td>
                                <td>{lead.email}</td>
                                <td>{lead.message}</td>
                                <td>
                                    <button className="btn btn-danger btn-sm">
                                        Delete
                                    </button>
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </Fragment>
        )
    }
}

...
```

![](https://i.imgur.com/YeREV7X.png)

<br><br><br>

### DELETE LEAD
開啟 `./leadmanager/frontend/src/actions/leads.js` 在裡面加入：
```javascript=
...
import { GET_LEADS, DELETE_LEAD } from './types'; // modify

export const getLeads = () => dispatch => {
    ...
};

// new add
export const deleteLead = (id) => dispatch => {
    axios.delete(`/api/leads/${id}`)
        .then(res => {
            dispatch({
                type: DELETE_LEAD,
                payload: id
            });
        })
        .catch(err => console.log(err));
};
```

<br><br><br>

開啟 `./leadmanager/frontend/src/actions/types.js` 在裡面加入：
```javascript=
export const GET_LEADS = 'GET_LEADS';
export const DELETE_LEAD = 'DELETE_LEAD'; // new add
```

<br><br><br>

開啟 `./leadmanager/frontend/src/reducers/leads.js` 在裡面寫入：
```javascript=
import { GET_LEADS, DELETE_LEAD } from '../actions/types.js' // modify

...

export default function (state = initailState, action) {
    switch (action.type) {
        case GET_LEADS:
            ...
        //new add
        case DELETE_LEAD:
            return {
                ...state,
                leads: state.leads.filter(lead => lead.id !== action.payload)
            }
        default:
            ...
    }
}
```

<br><br><br>

開啟 `./leadmanager/frontend/src/components/leads/Leads.js` 在裡面加入：
```javascript=
...
import { getLeads, deleteLead } from '../../actions/leads'; // modify

export class Leads extends Component {
    static propTypes = {
        leads: PropTypes.array.isRequired,
        getLeads: PropTypes.func.isRequired,
        deleteLead: PropTypes.func.isRequired // new add
    }
    
    ...

    render() {
        return (
            <Fragment>
                <h2>Leads</h2>
                <table className="table table-striped">
                    ...
                    <tbody>
                        {this.props.leads.map(lead => (
                            <tr key={lead.id}>
                                ...
                                <td>
                                    <button className="btn btn-danger btn-sm"
                                    
                                        // new add
                                        onClick={this.props.deleteLead.bind(this, lead.id)}>
                                        Delete
                                    </button>
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </Fragment>
        )
    }
}

...

// modify
export default connect(mapStateToProps, { getLeads, deleteLead })(Leads);
```

現在你可以刪除項目了
![](https://i.imgur.com/OEOXl7b.png)

<br><br><br>

### ADD LEAD
開啟 `./leadmanager/frontend/src/actions/leads.js` 在裡面加入：
```javascript=
...
import { GET_LEADS, DELETE_LEAD, ADD_LEAD } from './types'; // modify

export const getLeads = () => dispatch => {
    ...
};

export const deleteLead = (id) => dispatch => {
    ...
};

// new add
export const addLead = lead => dispatch => {
    axios.post('/api/leads/', lead)
        .then(res => {
            dispatch({
                type: ADD_LEAD,
                payload: res.data
            });
        })
        .catch(err => console.log(err));
};
```

<br><br><br>

開啟 `./leadmanager/frontend/src/actions/types.js` 在裡面加入：
```javascript=
export const GET_LEADS = 'GET_LEADS';
export const DELETE_LEAD = 'DELETE_LEAD';
export const ADD_LEAD = 'ADD_LEAD'; // new add
```

<br><br><br>

開啟 `./leadmanager/frontend/src/reducers/leads.js` 在裡面寫入：
```javascript=
...
import { GET_LEADS, DELETE_LEAD, ADD_LEAD } from '../actions/types.js' // modify

...

export default function (state = initailState, action) {
    switch (action.type) {
        case GET_LEADS:
            ...
        case DELETE_LEAD:
            ...
        //new add
        case ADD_LEAD:
            return {
                ...state,
                leads: [...state.leads, action.payload]
            }
        default:
            ...
    }
}
```

<br><br><br>

開啟 `./leadmanager/frontend/src/components/leads/Form.js` 複製下列內容整個貼上：
```javascript=
import React, { Component } from 'react'
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { addLead } from '../../actions/leads';

export class Form extends Component {
    state = {
        name: '',
        email: '',
        message: ''
    };

    static propTypes = {
        addLead: PropTypes.func.isRequired
    };

    onChange = e => this.setState({
        [e.target.name]: e.target.value
    });

    onSubmit = e => {
        e.preventDefault();
        const { name, email, message } = this.state;
        const lead = { name, email, message };
        this.props.addLead(lead);
    }

    render() {
        const { name, email, message } = this.state;
        return (
            <div className="card card-body mt-4 mb-4">
                <h2>Add Lead</h2>
                <form onSubmit={this.onSubmit}>
                    <div className="form-group">
                        <label>Name</label>
                        <input
                            className="form-control"
                            type="text"
                            name="name"
                            onChange={this.onChange}
                            value={name}
                        />
                    </div>
                    <div className="form-group">
                        <label>Email</label>
                        <input
                            className="form-control"
                            type="email"
                            name="email"
                            onChange={this.onChange}
                            value={email}
                        />
                    </div>
                    <div className="form-group">
                        <label>Message</label>
                        <textarea
                            className="form-control"
                            type="text"
                            name="message"
                            onChange={this.onChange}
                            value={message}
                        />
                    </div>
                    <div className="form-group">
                        <button type="submit" className="btn btn-primary">
                            Submit
                        </button>
                    </div>
                </form>
            </div>
        )
    }
}

export default connect(null, { addLead })(Form);
```

現在可以使用 add 表單加入新的資料了
![](https://i.imgur.com/NgVfo0Q.png)

<br><br><br>

[[4]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/B1Wg2Egpr)
[[2]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/rJaW62chH)