---
# System prepended metadata

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

---

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

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

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

<br>

## Frontend Authentication
### login
開啟 `./leadmanager/frontend/src/actions/types.js` 在裡面加入：
```javascript=
...
export const AUTH_ERROR = 'AUTH_ERROR';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; // new add
export const LOGIN_FAIL = 'LOGIN_FAIL'; // new add
```

<br><br><br>


開啟 `./leadmanager/frontend/src/actions/auth.js` 在裡面加入：
```javascript=
import axios from 'axios';
import { returnErrors } from './messages';
// modify
import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_SUCCESS, LOGIN_FAIL } from './types';

// CHECK TOKEN & LOAD USER
export const loadUser = () => (dispatch, getState) => {
    ...
}

// new add
// LOGIN USER
export const login = (username, password) => dispatch => {
    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    };

    // Request Body
    const body = JSON.stringify({ username, password });

    axios.post("/api/auth/login", body, config).then(res => {
        dispatch({
            type: LOGIN_SUCCESS,
            payload: res.data
        });
    }).catch(err => {
        dispatch(returnErrors(err.response.data, err.response.status));

        dispatch({ type: LOGIN_FAIL });
    })
}
```

<br><br><br>

開啟 `./leadmanager/frontend/src/reducers/auth.js` 在裡面加入：
```javascript=
// modify
import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_FAIL, LOGIN_SUCCESS } from '../actions/types';

const initialState = {
    ...
};

export default function (state = initialState, action) {
    switch (action.type) {
        case USER_LOADING:
            ...
        case USER_LOADED:
            ...
        
        // new add
        case LOGIN_SUCCESS:
            localStorage.setItem('token', action.payload.token);
            return {
                ...state,
                ...action.payload,
                isAuthenticated: true,
                isLoading: false
            }
        case AUTH_ERROR:
        case LOGIN_FAIL: // new add
            localStorage.removeItem('token');
            ...
        default:
            return state
    }
}
```

<br><br><br>

開啟 `./leadmanager/frontend/src/components/accounts/Login.js` 在裡面寫入：
```javascript=
import React, { Component } from 'react';
import { Link, Redirect } from 'react-router-dom'; // modify
import { connect } from 'react-redux'; // new add
import PropTypes from 'prop-types'; // new add
import { login } from '../../actions/auth'; // new add

export class Login extends Component {
    state = {
        ...
    };

    // new add
    static propTypes = {
        login: PropTypes.func.isRequired,
        isAuthenticated: PropTypes.bool
    }

    onSubmit = e => {
        e.preventDefault();
        // modify
        this.props.login(this.state.username, this.state.password);
    }

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

    render() {
        // new add
        if(this.props.isAuthenticated){
            return <Redirect to="/" />;
        }

        const { username, password } = this.state;

        return (
            ...
        )
    }
}

// new add
const mapStateToProps = state => ({
    isAuthenticated: state.auth.isAuthenticated
});

export default connect(mapStateToProps, { login })(Login); // modify
```

現在可以用登入頁面登入，畫面會自動轉跳到 Add Lead

<br><br><br>

### logout
開啟 `./leadmanager/frontend/src/components/layout/Header.js` 在裡面加入：
```javascript=
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux'; // new add
import PropTypes from 'prop-types'; // new add
import { logout } from '../../actions/auth'; // new add

export class Header extends Component {
    // new add
    static propTypes = {
        auth: PropTypes.object.isRequired,
        logout: PropTypes.func.isRequired
    };

    render() {
        // new add
        const { isAuthenticated, user } = this.props.auth;

        // new add
        const authLinks = (
            <ul className="navbar-nav ml-auto mt-2 mt-lg-0">
                <span className="navbar-text mr-3">
                    <strong>
                        {user ? `Welcom ${user.username}` : ''}
                    </strong>
                </span>
                <button
                    className="nav-link btn btn-info btn-sm text-light"
                    onClick={this.props.logout}>Logout</button>
            </ul>
        );

        // new add
        const guestLinks = (
            <ul className="navbar-nav ml-auto mt-2 mt-lg-0">
                <li className="nav-item">
                    <Link to="/register" className="nav-link">Register</Link>
                </li>
                <li className="nav-item">
                    <Link to="/login" className="nav-link">Login</Link>
                </li>
            </ul>
        );

        return (
                ...
                    <div className="collapse navbar-collapse" id="navbarTogglerDemo01">
                        <a className="navbar-brand" href="#">Lead Manager</a>
                        // modify
                        {isAuthenticated ? authLinks : guestLinks}
                    </div>
                ...
        )
    }
}

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

export default connect(mapStateToProps, { logout })(Header); // modify

```

<br><br><br>

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

<br><br><br>

開啟 `./leadmanager/frontend/src/actions/auth.js` 在裡面加入：
```javascript=
import axios from 'axios';
import { returnErrors } from './messages';
// modify
import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT_SUCCESS } from './types';

// CHECK TOKEN & LOAD USER
export const loadUser = () => (dispatch, getState) => {
    ...
}

// LOGIN USER
export const login = (username, password) => dispatch => {
    ...
}

// new add
// LOGOUT USER
export const logout = () => (dispatch, getState) => {
    // Get token from state
    const token = getState().auth.token;

    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    }

    // If token, add to headers config
    if (token) {
        config.headers['Authorization'] = `Token ${token}`;
    }

    axios
        .post("/api/auth/logout", null, config)
        .then(res => {
            dispatch({
                type: LOGOUT_SUCCESS
            });
        })
        .catch(err => {
            dispatch(returnErrors(err.response.data, err.response.status));
        })
}
```

<br><br><br>

開啟 `./leadmanager/frontend/src/reducers/auth.js` 在裡面加入：
```javascript=
// modify
import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_FAIL, LOGIN_SUCCESS, LOGOUT_SUCCESS } from '../actions/types';

const initialState = {
    ...
};

export default function (state = initialState, action) {
    switch (action.type) {
        ...
        case LOGIN_SUCCESS:
            ...
        case AUTH_ERROR:
        case LOGIN_FAIL:
        case LOGOUT_SUCCESS: // new add
            localStorage.removeItem('token');
            return {
                ...state,
                token: null,
                user: null,
                isAuthenticated: false,
                isLoading: false
            }
        default:
            return state
    }
}
```

現在可以按登出鈕來登出

<br><br><br>

開啟 `./leadmanager/frontend/src/components/layout/Alerts.js` 在裡面加入：
```javascript=
...

export class Alerts extends Component {
    static propTypes = {
        ...
    };

    componentDidUpdate(prevProps) {
        if(error !== prevProps.error ){
            ...
            if (error.msg.message) alert.error(`Message: ${error.msg.message.join()}`);
            // new add
            if (error.msg.non_field_errors) alert.error(error.msg.non_field_errors.join()); 
        }

        if (message !== prevProps.message) {
            ...
```

當你輸入錯誤時會顯示錯誤訊息
![](https://i.imgur.com/aQHoOQ2.png)

<br><br><br>

開啟 `./leadmanager/frontend/src/actions/auth.js` 改寫成：
```javascript=
...

// CHECK TOKEN & LOAD USER
export const loadUser = () => (dispatch, getState) => {
    // User Loading
    dispatch({ type: USER_LOADING });

    // delete token, config, if (token) 
    
    axios
        .get("/api/auth/user", tokenConfig(getState)) // modify
        .then(...)
}

// LOGIN USER
export const login = (username, password) => dispatch => {
    ...
}

// LOGOUT USER
export const logout = () => (dispatch, getState) => {
    // delete token, config, if (token) 
    
    axios
        .post("/api/auth/logout", null, tokenConfig(getState)) // modify
        .then(...)
}

// new add
// Setup config with token - helper funcrion
export const tokenConfig = getState => {
    // Get token from state
    const token = getState().auth.token;

    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    }

    // If token, add to headers config
    if (token) {
        config.headers['Authorization'] = `Token ${token}`;
    }

    return config;
}
```

<br><br><br>

### register
開啟 `./leadmanager/frontend/src/actions/types.js` 在裡面加入：
```javascript=
...
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; // new add
export const REGISTER_FAIL = 'REGISTER_FAIL'; // new add
```

<br><br><br>

開啟 `./leadmanager/frontend/src/actions/auth.js` 在裡面加入：
```javascript=
import axios from 'axios';
import { returnErrors } from './messages';
// modify
import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_SUCCESS, LOGIN_FAIL, LOGOUT_SUCCESS, REGISTER_SUCCESS, REGISTER_FAIL } from './types'; 

// CHECK TOKEN & LOAD USER
export const loadUser = () => (dispatch, getState) => {
    ...
}

// LOGIN USER
export const login = (username, password) => dispatch => {
    ...
}

// new add
// REGISTER USER
export const register = ({username, password, email}) => dispatch => {
    const config = {
        headers: {
            'Content-Type': 'application/json'
        }
    };

    // Request Body
    const body = JSON.stringify({ username, email, password });

    axios
        .post("/api/auth/register", body, config)
        .then(res => {
            dispatch({
                type: REGISTER_SUCCESS,
                payload: res.data
            });
        })
        .catch(err => {
            dispatch(returnErrors(err.response.data, err.response.status));
            dispatch({ type: REGISTER_FAIL });
        })
}

// LOGOUT USER
export const logout = () => (dispatch, getState) => {
    ...
}

// Setup config with token - helper funcrion
export const tokenConfig = getState => {
    ...
}
```

<br><br><br>

開啟 `./leadmanager/frontend/src/reducers/auth.js` 在裡面加入：
```javascript=
// modify
import { USER_LOADED, USER_LOADING, AUTH_ERROR, LOGIN_FAIL, LOGIN_SUCCESS, LOGOUT_SUCCESS, REGISTER_SUCCESS, REGISTER_FAIL } from '../actions/types';

const initialState = {
    ...
};

export default function (state = initialState, action) {
    switch (action.type) {
        case USER_LOADING:
            ...
        case USER_LOADED:
            ...
        case LOGIN_SUCCESS:
        case REGISTER_SUCCESS: // new add
            localStorage.setItem('token', action.payload.token);
            ...
        case AUTH_ERROR:
        case LOGIN_FAIL:
        case LOGOUT_SUCCESS:
        case REGISTER_FAIL: // new add
            localStorage.removeItem('token');
            ...
        default:
            return state
    }
}
```

<br><br><br>

開啟 `./leadmanager/frontend/src/components/accounts/Register.js` 在裡面寫入：
```javascript=
import React, { Component } from 'react';
import { Link, Redirect } from 'react-router-dom'; // modify
import { connect } from 'react-redux'; // new add
import PropTypes from 'prop-types'; // new add
import { register } from '../../actions/auth'; // new add
import { creatMessage } from '../../actions/messages'; // new add

export class Register extends Component {
    state = {
        ...
    };
    
    // new add
    static propTypes = {
        register: PropTypes.func.isRequired,
        isAuthenticated: PropTypes.bool
    }

    onSubmit = e => {
        e.preventDefault();
        
        // new add
        const { username, email, password, password2 } = this.state;
        if (password !== password2) {
            this.props.creatMessage({ passwordNotMatch: "Password do not match" });
        }else{
            const newUser = {
                username,
                password,
                email
            };

            this.props.register(newUser);
        }
    }

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

    render() {
        // new add
        if(this.props.isAuthenticated){
            return <Redirect to="/" />;
        }
        
        const { username, email, password, password2 } = this.state;

        return (
            ...
        )
    }
}

// new add
const mapStateToProps = state => ({
    isAuthenticated: state.auth.isAuthenticated
});

// modify
export default connect(mapStateToProps, { register, creatMessage })(Register);
```


<br><br><br>

開啟 `./leadmanager/frontend/src/components/layout/Alerts.js` 在裡面加入：
```javascript=
...

export class Alerts extends Component {
    static propTypes = {
        ...
    };

    componentDidUpdate(prevProps) {
        const { error, alert, message } = this.props;
        if (error !== prevProps.error) {
            ...
            if (error.msg.non_field_errors) alert.error(error.msg.non_field_errors.join());
            // new add
            if (error.msg.username) alert.error(error.msg.username.join());
        }

        if (message !== prevProps.message) {
            if (message.deleteLead) alert.success(message.deleteLead);
            if (message.addLead) alert.success(message.addLead);
            // new add
            if (message.passwordNotMatch) alert.error(message.passwordNotMatch);
        }
    };
```
現在註冊新帳號，成功後頁面自動轉跳到 Add Lead

<br><br><br>

在註冊時密碼輸入不一樣
![](https://i.imgur.com/oEoiKl9.png)

<br><br><br>

註冊已存在的User
![](https://i.imgur.com/fQ8tige.png)

<br><br><br>

### handel leads
開啟 `./leadmanager/frontend/src/actions/leads.js` 在裡面加入：
```javascript=
...
import { creatMessage, returnErrors } from './messages';
import { tokenConfig } from './auth'; // new add

export const getLeads = () => (dispatch, getState) => { // modify
    axios.get('/api/leads', tokenConfig(getState)) // modify
        .then(...)

export const deleteLead = (id) => (dispatch, getState) => { // modify
    axios.delete(`/api/leads/${id}`, tokenConfig(getState)) // modify
        .then(...)
};

export const addLead = lead => (dispatch, getState) => { // modify
    axios.post('/api/leads/', lead, tokenConfig(getState)) // modify
        .then(...)
};
```

現在你在各自的帳號做新增刪除都不會互相影響了。
### END

<br><br><br>

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