# [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)