React Django & Mysql to build a full stack job application website

In the earlier tutorials, you created APIs to perform CRUD operations on products table of MYSQL database, and to allow a visitor to register a user account and login to obtain json web token to access protected views. The backend APIs are ready to be accessed by a client app.

Now, we create a client app using React to use the APIs. After finishing the client app, we have a full stack job application website using React Django and MYSQL. The job application website allows a visitor to create an account and login to post new job application form or update an existing one. 

To get started with React, you have to install NPM (Node Package Manager) and a code editor. In my machine, i have Node 16.10.0 and NPM 7.24.0 and VS Code.

To create React jobapp app, change to a working directory and execute the following command:

D:/> npm init react-app jobapp

After the initialization complete, jobapp folder of the React app is created in the drive D:.

To open the jobapp app in VS Code, launch the VS Code -> Open Folder... choose jobapp.

On the top menu, select New Terminal. In the Terminal window, run the following command to run the jobapp.

jobapp>npm run start

We are going to style the React app using bootstrap and use reactstrap components in our forms. Thus, execute the following command to install the dependencies:

jobapp>npm install bootstrap reactstrap

In the src folder, create components folder. Add navbar.js, register.js, login.js, logout.js, home.js files to the components folder. The navbar.js setups  the NavBar component for easy navigation in the app. The register.js contains Register component to display registration form and access register API to save the user account in database. The login.js handles login task. It uses login API to authenticate the user and save responded token using localStorage. The logout.js simply removes the token from the storage. The home.js is allows an authorized user to upload and update job application form. 

components/NavBar.js
import React, { useState } from 'react';

import {
  Collapse,
  Navbar,
  NavbarToggler,
  NavbarBrand,
  Nav,
  NavItem,
  NavLink,

} from 'reactstrap';

function NavBar(props) {
  const {token} = props;
  const [isOpen, setIsOpen] = useState(false);
  const toggle = () => setIsOpen(!isOpen);

  return (
    <div>
    <Navbar color="light" light expand="md">
      <NavbarBrand href="/">Home</NavbarBrand>
      <NavbarToggler onClick={toggle} />
      <Collapse isOpen={isOpen} navbar>
        <Nav navbar>
          <NavItem >
            {
            token?  
            <NavLink href="/logout">

              Logout
             
            </NavLink>
            :
            <NavLink href="/login">

              Login
             
            </NavLink>

           }
          </NavItem>
         
        </Nav>
      </Collapse>
     
    </Navbar>
  </div>
  );
}

export default NavBar;

components/register.js

import React, { useState } from "react"
import axios from "axios";
import { useNavigate } from "react-router-dom";

import {
  Button,
  FormGroup,Card,
  CardHeader,CardBody,CardFooter,  
  Input,
  Form,
} from "reactstrap";

const Register = (props) => {

      const [errors,setErrors] = useState({});  
       const [inputs, setInputs] = useState({});
      const history=useNavigate();


      function handleValidation() {
        
        let formIsValid = true;
        let es = {};
        //Name
        if (!inputs.username) {
          formIsValid = false;
          es['username']="can not empty!";
         
        }
    
        if (typeof inputs.username !== "undefined") {
            
          if (!inputs.username.match(/^[a-zA-Z0-9]+$/)) {
            formIsValid = false;
            es['username']="only letters and numbers allowed!";
          }
        }
        // password
        if (!inputs.password) {
            formIsValid = false;
            es['password']="can not empty!";
          }
      
          if (typeof inputs.password !== "undefined") {
            if (inputs.password.length<8) {
              formIsValid = false;
              es['password']="week password!";
            }
          }
    
        //Email
        if (!inputs.password2) {
          formIsValid = false;
          es['password2']="can not empty!";
        }
    
        if (inputs.password!==inputs.password2) {
        
          formIsValid = false;
          es['password2']="Passwords not match!";
          
        }
        setErrors(es);
        return formIsValid;
      }

      function handleSubmit(e){
        e.preventDefault();
        console.log(inputs.username,inputs.password,inputs.password2);
        if(handleValidation()){
            axios({
                method: 'post',
                url: "register/",
                data: {username: inputs.username,password:inputs.password,password2:inputs.password2},
                headers: { "Content-type":"application/json",}
            }).then(response=>{

                    try {
                        
                        let dt=JSON.parse(JSON.stringify(response));
                        if(dt.status===201){
                            history("/login");
                        }
                        else{
                            alert('Failed to create user!');
                        }
                        
                    } catch (e) {
                        alert('Failed to create user!');
                    }
                    
                    
                    
            });
        }
      }
      
      const handleChange = (event) =>{
        const name = event.target.name;
        const value = event.target.value;
        setInputs(values => ({...values, [name]: value}))
      }
      return (
        <div className="container"  style={{width: '18rem'}}>
            <Card
                className="my-2" style={{width: '18rem'}}
            >

            <CardHeader>Register</CardHeader>
            
            <Form className="form">
                <CardBody>
                <FormGroup>
                    <Input
                        type="text"
                        name="username"
                        placeholder="Enter username"
                        value={inputs.username || ""} 
                        onChange={handleChange}
                       
                        />
                    <span style={{color: '#ff2222'}}>{errors.username}</span>
                    </FormGroup>
                   
                    <FormGroup>

                        <Input
                        type="password"
                        name="password"
                        placeholder="Enter password"
                        value={inputs.password || ""} 
                        onChange={handleChange}
                       
                        />
                    <span style={{color: '#ff2222'}}>{errors.password}</span>
                    </FormGroup>

                    <FormGroup>

                        <Input
                        type="password"
                        name="password2"
                        placeholder="Enter confirm password"
                        value={inputs.password2 || ""} 
                        onChange={handleChange}
                       
                        />
                    <span style={{color: '#ff2222'}}>{errors.password2}</span>
                    </FormGroup>
                </CardBody>   

                <CardFooter>
                    <FormGroup>
        
                    <Button color="primary" onClick={handleSubmit}>Register</Button>
                    </FormGroup>
                </CardFooter>
            </Form>  
            </Card>      
        </div>
      
      )
}
export default Register

We are using axios to access APIs created in Django. So, executing the command below to install axios into the app: 
jobapp>npm install axios

components/login.js
import React, { useState } from "react"
import axios from "axios";
import { useNavigate } from "react-router-dom";
import { Link } from "react-router-dom"
import {
  Button,
  FormGroup,Card,
  CardHeader,CardBody,CardFooter,  
  Input,
  Form,
  Label,
} from "reactstrap";

const Login = (props) => {
      const [username, setUsername]=useState('');
      const [password,setPassword]=useState('');
      const [logerr,setLogError] = useState('');  
      const {tokenchange} = props;
      const history=useNavigate();
      
      function handleTokenChange(tk){
            tokenchange(tk); // send token to parent component
      }

      function handleSubmit(e){
        e.preventDefault();
   
        // Make the POST call by passing a config object to the instance
        axios({
            method: 'post',
 
            url: "login/",
            data: {username: username,password:password},
            headers: { "Content-type":"application/json"}
        }).then(response=>{
                
 
                try {
                    let dt=JSON.parse(JSON.stringify(response));
                    
                    if(dt.status===200){
               
                        localStorage.setItem("token", dt.data.access);
                        handleTokenChange(dt.data.access);
                        history("/");
                    }
                    else{
                        setLogError("Error in login");
                    }
                    
                } catch (e) {
                    setLogError('Invalid user!');
                }
                
                
                
        })
        .catch(error => { 
          
            setLogError('Invalid user!');
        });
      }
      const handleUsernameInputChange = (event) => {
        setUsername(event.target.value);
      }
      const handlePasswordInputChange = (event) => {
        setPassword(event.target.value);
      }
      return (
        <div className="container"  style={{width: '25rem'}}>
            <Card
                className="my-2" style={{width: '25rem'}}
            >

            <CardHeader>Login</CardHeader>
            
            <Form className="form">
                <CardBody>
                    <FormGroup>

                    <Input
                        type="text"
                        name="username"
                        placeholder="Enter username"
                        value={username}
                        onChange={(e) =>handleUsernameInputChange(e)}
                        required
                        />
        
                    </FormGroup>
                    <FormGroup>

                        <Input
                        type="password"
                        name="password"
                        placeholder="Enter password"
                        value={password}
                        onChange={(e) =>handlePasswordInputChange(e)}
                        required
                        />
        
                    </FormGroup>
                </CardBody>   

                <CardFooter>
                    <FormGroup>
        
                    <Button color="primary" onClick={handleSubmit}>Login</Button>
                    <Label style={{marginLeft: '5px'}}>Don't have an account?</Label>  <Link to="/register">create user</Link>
                    <Label className="text-danger">{logerr}</Label>
                    
                    </FormGroup>
                </CardFooter>
            </Form>  
            </Card>      
        </div>
      
      )
}
export default Login


components/logout.js
import React,{useEffect} from "react"
import { useNavigate } from "react-router-dom";

const Logout = (props) => {

      const {tokenchange} = props; 
      const history=useNavigate();
      function handleTokenChange (tk) {
          tokenchange(tk); // send token updated to parent component
      }

      useEffect(() => {
        localStorage.removeItem('token');
        handleTokenChange(null);
        history("/");
         }, []);

      return (
        <><div>Logout</div></>
) } export default Logout

components/home.js

import React, { useState, useEffect } from "react"
import axios from "axios";
import { getUserId } from "../utils/AuthUser";
import DatePicker from "react-datepicker";
import Moment from 'moment';
import {  useNavigate} from "react-router-dom";
import "react-datepicker/dist/react-datepicker.css";
import {
  Button,
  FormGroup,Col, 
  Card, CardHeader,CardBody,   
  Input,
  Form,
  Label,
} from "reactstrap";

const MainComponent = (props) => {
  const history=useNavigate();
  const token= localStorage.getItem("token");
  const user=getUserId(token);
 

  const [inputs, setInputs] = useState(
    {

    'first_name':'',
    'last_name':'',
    'email':'',
    'sex':'F',
    'education':1,
    'position': 1,
    'employment': 1,
    'id':0,

  }
    
    );
  const [fileatt, setDocument] = useState(null);
  const [dob, setDob] = useState(new Date());
  const [isEditMode, setIsEditmode]= useState(false);
  const [message,setMessage]= useState('');

  useEffect(() => {
    if(user) // if login
      axios.get(`/api/jobbyuser/${user.user_id}/`,{
        headers:{
            "Content-type":"application/json",
        },

      
      })
      .then((res) => {
        
        const jform=JSON.parse(JSON.stringify(res));
        if(jform.status===200){
          setIsEditmode(true);
          setInputs(values => ({...values, ['id']: res.data.id}));
          setInputs(values => ({...values, ['email']: res.data.email}));
          setInputs(values => ({...values, ['first_name']: res.data.first_name})) ;
          setInputs(values => ({...values, ['last_name']: res.data.last_name})) ;
          setInputs(values => ({...values, ['sex']: res.data.sex})) ;
          const formatDate = Moment(res.data.dob).format('YYYY-MM-DD');
          setDob(new Date(formatDate));
          setInputs(values => ({...values, ['education']: res.data.education})) ;
          setInputs(values => ({...values, ['position']: res.data.position})) ;
          setInputs(values => ({...values, ['employment']: res.data.employment})) ;
          
        }
       
      
      }) 
      .catch((err) => console.log(err));

    else{ // if not login, redirect to login page
      history('/login');
    }

   }, []);
   

   const handleChange = (event) =>{
    const name = event.target.name;
    const value = event.target.value;
    setInputs(values => ({...values, [name]: value}))
  } 
  const handleFileChange = (e) => {
    
    setDocument(e.target.files[0]);
  };

  const HIGHSCHOOL = 'High School';
  const ASSOCIATE = 'Associate';
  const BACHELOR = 'Bachelor';
  const EDUCATION_CHOICES=[

    {"id":1,"text":HIGHSCHOOL},
    {"id":2,"text":ASSOCIATE},
    {"id":3,"text":BACHELOR},

  ];

  const  MANAGER = 'manager';
  const  SALES ='sales';
  const  CASHIER = 'cashier';
  const  OTHER ='other';
   

  const  POSITION_CHOICES = [
    {"id":1,"text":MANAGER},
    {"id":2,"text":SALES},
    {"id":3,"text":CASHIER},
    {"id":4,"text":OTHER},     
    ];
  const  EMPLOYED = 'employed';
  const  SELFEMPLOYED = 'self-employed';
  const  UNEMPLOYED = 'unemployed';
  const  STUDENT = 'student';

  const  EMPLOYMENT_CHOICES = [
    {"id":1,"text":EMPLOYED},
    {"id":2,"text":SELFEMPLOYED},
    {"id":3,"text":UNEMPLOYED},
    {"id":4,"text":STUDENT},   
         
    ];
  
    const handleSubmit = (e) => {
    e.preventDefault();
  
    let form_data = new FormData();
    form_data.append('id', inputs.id);
    form_data.append('first_name', inputs.first_name);
    form_data.append('last_name', inputs.last_name);
    form_data.append('sex', inputs.sex);
    form_data.append('dob',Moment(dob).format('YYYY-MM-DD'));
    form_data.append('email',inputs.email);
    form_data.append('position',inputs.position);
    form_data.append('education',inputs.education);
    form_data.append('employment',inputs.employment);
    form_data.append('user_id',user.user_id);

 

    if(fileatt!=null) {
      
      form_data.append('fileatt', fileatt, fileatt.name);
    }
   

    axios({
      method: isEditMode?'put':'post',
      url: "/api/jforms/"+(isEditMode?inputs.id+"/":""),
      data: form_data,
      headers: { 
      "authorization": `Bearer ${token}` //send token to get permission to access protected apis
      }
    }).then(res=>{
      setIsEditmode(!isEditMode);  
      console.log(res);
      const resObj=JSON.parse(JSON.stringify(res));
      if(resObj.status===201 || resObj.status===200){
        setMessage(" saved successfully!");
      }
      else{
        setMessage(" Failed to save!");
      }
     
    });

  };  
 
 return (
        <div className="container"  style={{width: '25rem',marginTop:"10px"}}>
           <Card
                className="my-2" style={{width: '25rem'}}
            >

            <CardHeader>Job Application Form</CardHeader>
        <Form>
        <CardBody>
        <FormGroup row>
          <Label for="first_name" sm={4} size="lg">First Name</Label>
          <Col sm={8}>
            <Input type="text" value={inputs.first_name} name="first_name" id="first_name" placeholder="First Name" bsSize="lg" onChange={(e) =>handleChange(e)} required />
          </Col>
        </FormGroup>
        <FormGroup row>
          <Label for="last_name" sm={4}>Last Name</Label>
          <Col sm={8}>
          <Input type="text" value={inputs.last_name} name="last_name" id="last_name" placeholder="Last Name" bsSize="lg" onChange={(e) =>handleChange(e)} required />
          
          </Col>
        </FormGroup>  
        <FormGroup row>
          <Label for="email" sm={2}>Email</Label>
          <Col sm={10}>
          <Input type="text" value={inputs.email} name="email" id="email" placeholder="Email" bsSize="lg" onChange={(e) =>handleChange(e)} required />
          
          </Col>
          </FormGroup>
          
    
        <FormGroup row>
          <Label for="sex"  sm={2}>Sex</Label>
          <Col sm={10}>
          <Input onChange={(e) =>handleChange(e)} type="select" name="sex" id="sex" value={inputs.sex}>
                <option value="F">Female</option>
                <option value="M">Male</option>
          </Input>
          </Col>
        </FormGroup>
        <FormGroup row>
        <Label for="dob"  sm={4}>Date Of Birth</Label>
          <Col sm={10}>
          <DatePicker id="dob" name="dob"  selected={dob} onChange={(date) => setDob(date)} 
            dateFormat="yyyy-MM-dd"
          />
          </Col>
        </FormGroup>

        <FormGroup row>
          <Label for="edu"  sm={4}>Education</Label>
          <Col sm={10}>
          <Input onChange={(e) =>handleChange(e)} type="select" name="education" id="education" value={inputs.education}>
                {
                  EDUCATION_CHOICES.map((edu)=>{
                    return (
                      <option key={edu.id} value={edu.id}>{edu.text}</option>
                    );
                  })
                }
          </Input>
          </Col>
        </FormGroup>
        <FormGroup row>
          <Label for="pos"  sm={10}>What Postion are you applying for?</Label>
          <Col sm={10}>
          <Input onChange={(e) =>handleChange(e)} type="select" name="position" id="position" value={inputs.position}>
                {
                  POSITION_CHOICES.map((pos,index)=>{
                    return (
                      <option key={pos.id} value={pos.id}>{pos.text}</option>
                    );
                  })
                }
          </Input>
          </Col>
        </FormGroup>

        <FormGroup row>
          <Label for="emp_status"  sm={10}>What is your current employment?</Label>
          <Col sm={10}>
          <Input onChange={(e) =>handleChange(e)} type="select" name="employment" id="employment" value={inputs.employment}>
                {
                  EMPLOYMENT_CHOICES.map((emp)=>{
                    return (
                      <option key={emp.id} value={emp.id}>{emp.text}</option>
                    );
                  })
                }
          </Input>
          </Col>
        </FormGroup>

        <FormGroup>
          <Label for="file"  sm={10}>File attachment</Label>
          <Col sm={10}>
              <Input
                type="file"
                id="fileatt"
                name="fileatt"
				        accept="application/pdf"  onChange={handleFileChange} 
              
              />

            </Col>
            </FormGroup>
            <FormGroup>
              <Button color="primary"  onClick={handleSubmit}>Submit</Button>
              <span>{message}</span>
            </FormGroup>
         </CardBody>
         
        </Form>     
        </Card>      
        </div>
      
      )
}
export default MainComponent

In the MainComponent, we use date picker from react-datepicker and moment to format date. Execute the following commands to install the dependencies:

npm install react-datepicker moment

Update src/App.js file to add NavBar and routes to home, register, login, and logout components.

import React, { useState } from "react"
import "bootstrap/dist/css/bootstrap.min.css";
import NavBar from './components/navbar';
import MainComponent from './components/home';
import { BrowserRouter as Router, Routes,Route } from "react-router-dom";
import Register from "./components/register";
import Login from "./components/login";
import Logout from "./components/logout";
function App() {
  const [token,setToken] = useState(localStorage.getItem("token"));
  const handleTokenChange = (tk) =>{
    setToken(tk);
  }
  return (
    <div className="App">
    <Router>
      <NavBar token={token}/>
      <Routes>
      <Route path="/" element ={<MainComponent />}/>
      <Route path="/login" element ={<Login  tokenchange={handleTokenChange} />}/>
      <Route path="/logout" element ={<Logout  tokenchange={handleTokenChange} />}/>
      <Route path="/register" element ={<Register/>}/>
      </Routes>
    </Router>
  </div>
  );
}

Open package.js file to add proxy to the APIs at localhost:5000. All APIs accesses will forward to http://localhost:5000/.
  ................
  "name": "jobapp",
  "version": "0.1.0",
  "private": true,
  "homepage": ".",
  "proxy": "http://localhost:5000/",
.........

Save the project. While MYSQL is running. access http://localhost:3000. You will be redirected to the login page.


From the login page, click create user link to open register page. Try register an new user account. 



After the account is successfully created, back to the login page to login using username and password provided in the register page. Successful login will show up the home page that allows you to submit job application form:






Video Demo

Comments

Popular posts from this blog

User Authentication - Register & Login in Django with Simple JWT & Mysql

Django - connect to Mysql database & CRUD APIs