# [6]Django Rest API + React.js + Redux + webpack 前後端整合 ###### tags: `python` `Django` `Django Rest framework` `React.js` `redux` `webpack` > [time= 2019 12 04 ] > 原文 & 參考: > https://www.youtube.com/watch?v=EmAc4wQikwY&list=PLillGF-RfqbbRA-CIUxlxkUpbq0IFkX60&index=6 <br> ## Auth State & Private Routes 終端機目前位置 `~/django_rest_api_react` 安裝 react-router-dom ``` $ npm i react-router-dom ``` > *react-router-dom@5.1.2* <br><br><br> 開啟 `./leadmanager/frontend/src/components/App.js` 在裡面加入: ```javascript= ... import Alerts from './layout/Alerts' // new add import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom' import Login from './accounts/Login'; // new add import Register from './accounts/Register'; // new add // Alert Options const alertOptions = { ... } export class App extends Component { render() { return ( <Provider store={store}> <AlertProvider template={AlertTemplate} {...alertOptions}> <Router> {/* new add */} <Fragment> <Header /> <Alerts /> <div className="container"> <Switch> {/* new add */} <Route exact path="/" component={Dashboard} /> {/* modify */} <Route exact path="/register" component={Register} /> {/* new add */} <Route exact path="/login" component={Login} /> {/* new add */} </Switch> {/* new add */} </div> </Fragment> </Router> {/* new add */} </AlertProvider> </Provider> ) } } ReactDom.render(<App />, document.getElementById('app')); ``` <br><br><br> 新增一個檔案命名為 `./leadmanager/frontend/src/components/accounts/Register.js` 在裡面寫入: ```javascript= import React, { Component } from 'react'; import { Link } from 'react-router-dom'; export class Register extends Component { state = { username: '', email: '', password: '', password2: '' }; onSubmit = e => { e.preventDefault(); console.log("submit"); } onChange = e => this.setState({ [e.target.name]: e.target.value }); render() { const { username, email, password, password2 } = this.state; return ( <div className="col-md-6 m-auto"> <div className="card card-body mt-5"> <h2 className="text-center">Register</h2> <form onSubmit={this.onSubmit}> <div className="form-group"> <label>Username</label> <input type="text" className="form-control" name="username" onChange={this.onChange} value={username} /> </div> <div className="form-group"> <label>Email</label> <input type="email" className="form-control" name="email" onChange={this.onChange} value={email} /> </div> <div className="form-group"> <label>Password</label> <input type="password" className="form-control" name="password" onChange={this.onChange} value={password} /> </div> <div className="form-group"> <label>Confirm Password</label> <input type="password" className="form-control" name="password2" onChange={this.onChange} value={password2} /> </div> <div className="form-group"> <button type="submit" className="btn btn-primary"> Register </button> </div> <p> Already have an account? <Link to="/login">Login</Link> </p> </form> </div> </div> ) } } export default Register ``` <br><br><br> 新增一個檔案命名為 `./leadmanager/frontend/src/components/accounts/Login.js` 在裡面寫入: ```javascript= import React, { Component } from 'react'; import { Link } from 'react-router-dom'; export class Login extends Component { state = { username: '', password: '' }; onSubmit = e => { e.preventDefault(); console.log("submit"); } onChange = e => this.setState({ [e.target.name]: e.target.value }); render() { const { username, password } = this.state; return ( <div className="col-md-6 m-auto"> <div className="card card-body mt-5"> <h2 className="text-center">Login</h2> <form onSubmit={this.onSubmit}> <div className="form-group"> <label>Username</label> <input type="text" className="form-control" name="username" onChange={this.onChange} value={username} /> </div> <div className="form-group"> <label>Password</label> <input type="password" className="form-control" name="password" onChange={this.onChange} value={password} /> </div> <div className="form-group"> <button type="submit" className="btn btn-primary"> Login </button> </div> <p> Don't have an account? <Link to="/register">Login</Link> </p> </form> </div> </div> ) } } export default Login ``` 資料結構 ``` django_rest_api_react ├──leadmanager │ ├──accounts │ ├──frontend │ │ ├──migrations │ │ ├──src │ │ │ ├──actions │ │ │ ├──components │ │ │ │ ├──accounts │ │ │ │ │ ├──Login.js │ │ │ │ │ └──Register.js │ │ │ │ │ │ │ │ │ ├──layout │ │ │ │ ├──leads │ │ │ │ └──App.js │ │ │ │ │ │ │ ├──reducers │ │ │ ├──index.js │ │ │ └──store.js │ │ │ ... ``` <br><br><br> 開啟 `./leadmanager/frontend/src/components/layout/Header.js` 在裡面加入: ```javascript= import React, { Component } from 'react'; import { Link } from 'react-router-dom'; // new add export class Header extends Component { render() { return ( ... <ul className="navbar-nav mr-auto mt-2 mt-lg-0"> { /* new add */ } <li className="nav-item"> <Link to="/register" className="nav-link">Register</Link> </li> { /* new add */ } <li className="nav-item"> <Link to="/login" className="nav-link">Login</Link> </li> </ul> ... ) } } export default Header ``` <br><br><br> 開啟 `./leadmanager/frontend/src/reducers/index.js` 在裡面加入: ```javascript= ... import messages from './messages'; import auth from './auth' // new add export default combineReducers({ leads, errors, messages, auth // new add }); ``` <br><br><br> 新增一個檔案命名為 `./leadmanager/frontend/src/reducers/auth.js` 在裡面寫入: ```javascript= const initialState = { token: localStorage.getItem('token'), isAuthenticated: null, isLoading: false, ser: null }; export default function(state = initialState, action) { switch(action.type){ default: return state } } ``` <br><br><br> 新增一個檔案命名為 `./leadmanager/frontend/src/components/common/PrivateRoute.js` 在裡面寫入: ```javascript= import React from 'react'; import { Route, Redirect } from 'react-router-dom'; import { connect } from 'react-redux'; import { PropTypes } from 'prop-types'; const PrivateRoute = ({ component: Component, auth, ...rest }) => ( <Route {...rest} render={props => { if (auth.isLoading) { return <h2>loading...</h2>; } else if (!auth.isAuthenticated) { return <Redirect to="/login" /> } else { return <Component {...props} /> } }} /> ) const mapStateToProps = state => ({ auth: state.auth }); export default connect(mapStateToProps)(PrivateRoute); ``` 資料結構 ``` django_rest_api_react ├──leadmanager │ ├──accounts │ ├──frontend │ │ ├──migrations │ │ ├──src │ │ │ ├──actions │ │ │ ├──components │ │ │ │ ├──accounts │ │ │ │ ├──common │ │ │ │ │ └──PrivateRoute.js │ │ │ │ │ │ │ │ │ ├──layout │ │ │ │ ├──leads │ │ │ │ └──App.js │ │ │ │ │ │ │ ├──reducers │ │ │ ├──index.js │ │ │ └──store.js │ │ │ ... ``` <br><br><br> 開啟 `./leadmanager/frontend/src/components/App.js` 在裡面加入: ```javascript= ... import Register from './accounts/Register'; import PrivateRoute from './common/PrivateRoute'; // new add // Alert Options const alertOptions = { timeout: 30000, position: 'top center' } export class App extends Component { render() { return ( ... <Switch> { /* modify */} <PrivateRoute exact path="/" component={Dashboard} /> <Route exact path="/register" component={Register} /> <Route exact path="/login" component={Login} /> </Switch> ... ) } } ReactDom.render(<App />, document.getElementById('app')); ``` <br><br><br> 開啟 `./leadmanager/frontend/src/actions/types.js` 在裡面加入: ```javascript= ... export const CREATE_MESSAGE = 'CREATE_MESSAGE'; export const USER_LOADING = 'USER_LOADING'; // new add export const USER_LOADED = 'USER_LOADED'; // new add export const AUTH_ERROR = 'AUTH_ERROR'; // new add ``` <br><br><br> 開啟 `./leadmanager/frontend/src/reducers/auth.js` 在裡面加入: ```javascript= // new add import { USER_LOADED, USER_LOADING, AUTH_ERROR } from '../actions/types'; const initialState = { ... }; export default function (state = initialState, action) { switch (action.type) { // new add case USER_LOADING: return { ...state, isLoading: true }; // new add case USER_LOADED: return{ ...state, isAuthenticated: true, isLoading: false, user: action.payload }; // new add case AUTH_ERROR: localStorage.removeItem('token'); return { ...state, token: null, user: null, isAuthenticated: false, isLoading: false } default: return state } } ``` <br><br><br> 新增一個檔案命名為 `./leadmanager/frontend/src/actions/auth.js` 在裡面寫入: ```javascript= import axios from 'axios'; import { returnErrors } from './messages'; import { USER_LOADED, USER_LOADING, AUTH_ERROR } from './types'; // CHECK TOKEN & LOAD USER export const loadUser = () => (dispatch, getState) => { // User Loading dispatch({ type: USER_LOADING }); // 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.get("/api/auth/user", config).then(res => { dispatch({ type: USER_LOADED, payload: res.data })}).catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); dispatch({ type: AUTH_ERROR }); }) } ``` 資料結構 ``` django_rest_api_react ├──leadmanager │ ├──accounts │ ├──frontend │ │ ├──migrations │ │ ├──src │ │ │ ├──actions │ │ │ │ ├──auth.js │ │ │ │ ├──leads.js │ │ │ │ ├──messages.js │ │ │ │ └──types.js │ │ │ │ │ │ │ ├──components │ │ │ ├──reducers │ │ │ ├──index.js │ │ │ └──store.js │ │ │ ... ``` <br><br><br> 開啟 `./leadmanager/frontend/src/components/App.js` 在裡面加入: ```javascript= ... import PrivateRoute from './common/PrivateRoute'; import { loadUser } from '../actions/auth'; // new add // Alert Options const alertOptions = { ... } export class App extends Component { // new add componentDidMount() { store.dispatch(loadUser()); } render() { ... } } ReactDom.render(<App />, document.getElementById('app')); ``` <br><br><br> [[7]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/rkzbP8K6r) [[5]Django Rest API + React.js + Redux + webpack 前後端整合](https://hackmd.io/@RoyChen/rya_kmZTH)