DevOps - Using Ajax and CORS Headers to Get Data
Designating Allowed Hosts
Once you have begun implementing Redux in your React app for global state management, you will want to begin sending and receiving information between the Django back end and the React front end.
The first thing you'll need is to make sure that the Django back end is able to receive a request from the React front end. Check settings.py for "allowed hosts" and make sure it includes the front end:
ALLOWED_HOSTS = [
'dev.locdocinc.com',
'dev.locdocinc.com:3000',
]Setting up Django CORS Middleware
Because they are on different hosts, we will additionally use django-cors-headers to bypass any CORS security errors and allow the back end to receive requests from the front end.
$sudo docker-compose run --rm backend python -m pip install django-cors-headersYou will then need to update your settings.py file in the /backend folder to make sure the app is using the cors-headers library.
INSTALLED_APPS = [
...
'corsheaders',
...
]
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
CORS_ALLOWED_ORIGINS = [
'http://dev.locdocinc.com',
'http://dev.locdocinc.com:3000',
]You should also add "django-cors-headers" to requirements.txt in the /backend folder.
Installing Axios and Writing an Ajax Call in React
After the Django back end is ready to accept requests from the React front end, it's time to use axios to write an ajax call. For this example, we will be building the login functionality that posts a username and password to the back end and receives a token in response.
First, you will need to install axios:
$sudo docker-compose run --rm frontend npm install --save axiosThen, import axios at the top of any component that will be using it to make an ajax call. Here is the working login function that you saw earlier. Upon success or failure, this function will then call an action that dispatches the correct information to the global state. This change in global state will then prompt a change in any component that calls on it.
import axios from 'axios';
const domain = "http://dev.locdocinc.com:8080"
//log in action that gets a response from the back end via ajax
//and uses the info it returns to dispatch the appropriate action to the reducer
export const login = (loginForm) => dispatch => {
axios
.post(`${domain}/api-token-auth/`, loginForm, { crossdomain: true })
.then(res => {
dispatch({
type: "LOGIN",
payload: {
token: res.data.token,
}
})
})
.catch(err => {
console.log("log in fail: ", err)
dispatch({
type: "LOGIN_ERROR",
payload: err.message
})
})
}Make sure you also have a reducer in place that will modify the global state when it reads an action with the type you designated.
//create an initial version of the state with default values
const initialState = {
token: null,
error: null,
}
// Use the initialState as a default value
export default function sampleReducer(state = initialState, action) {
// The reducer normally looks at the action type field to decide what happens
switch (action.type) {
// Do something here based on the different types of actions
case "LOGIN":
//use the spread operator to return the rest of the state
//then modify the part you want to change
return {
...state,
token: action.payload.token //the token will be received in the "action" object
}
case "LOGIN_ERROR":
return {
...state,
error: action.payload
}
default:
// If this reducer doesn't recognize the action type, or doesn't
// care about this specific action, return the existing state unchanged
return state;
}
}Finally, you can use the Redux "connect" method to connect your action function to a component. Here is a version of Login.js which imports connect from Redux and the login action, and submits the user's email address and password to the back end.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { login } from '../../actions';
import './Login.css';
class Login extends Component {
state = {
email: "",
password: "",
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value,
})
}
loginButtonClicked = event => {
event.preventDefault();
console.log(event)
//confirm something has been entered for both fields
if (this.state.email && this.state.password) {
//use the "event" to cast the form contents into a variable
let loginInfo = {
username: this.state.email,
password: this.state.password,
}
//use the action (connected into props) to send the form to the back end
this.props.login(loginInfo);
}
}
render() {
return (
<div id="login-page" className="auth-page">
<form onSubmit={this.loginButtonClicked}>
<label>
Email:
<input type="text" id="email" required
value={this.state.email}
onChange={this.handleChange} />
</label>
<label>
Password:
<input type="password" id="password" required
value={this.state.password}
onChange={this.handleChange} />
</label>
<input id="form-submit-button" type="submit" value="Log In" />
</form>
{ this.props.error ?
<div className="login-register-error">Login error! {this.props.error} </div>
:null }
</div>
)
}
}
const mapStateToProps = (state) => {
return {
error: state.authentication.error
}
}
export default connect(mapStateToProps, { login })(Login)Note that it also maps the login error from global state to its props. If the back end returns an error after an attempted login, the LOGIN_ERROR reducer will map the error message to the global state, which the Login.js component will be able to detect and then display in div for the user.