Sławomir Kwiatkowski

by: Sławomir Kwiatkowski

2024/11/17

APIs built with Django Rest Framework with React Client

Content description:
In this post I'll describe how to use APIs created in Django Rest Framework with a React client.
I'll describe how to use tokens generated with JWT.
The created component will renew the token automatically when it expires.
If the token renewal fails (the refresh token has also expired), the login component will be displayed. After successfully logging in with your credentials data, you'll be redirected to the page that invoked the login component.

In the example below I will use a component that displays the warehouses owned by the user.

The useEffect() function runs the fetchWarechouses() function when the page loads.

An attempt is made to download a list of warehouses from the API, and when the result is negative (status 401 - authorization error), a request is made to renew the access token using the refresh token.

If this also fails, the login component is displayed. Additionally, the path to the current page is sent as a parameter, so that you can return to it after a successful login.

     
import { useEffect, useState } from "react"
import { useNavigate, useLocation } from "react-router-dom"

function Warehouses() {
  const navigate = useNavigate()
  const location = useLocation()
  const [warehouses, setWarehouses] = useState([])
  
  const fetchWarehouses = async () => {
    try {
      const token = localStorage.getItem('accessToken')
      const res = await fetch('/api/warehouses/', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        }
      })
      if (res.status==401) {
        const token = localStorage.getItem('refreshToken')
        const res = await fetch('/api/token/refresh/', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({"refresh": token})
        })
        if (res.ok) {
          const data = await res.json()
          localStorage.setItem("accessToken", data["access"])
          fetchWarehouses()
        } else {
          navigate('/login', {state: {"from": location.pathname}})
        }
      }
      const data = await res.json()
      setWarehouses(data)
    } catch (err) {
      console.log("error:", err)
    }
  }

  useEffect(() => {
    fetchWarehouses()
  }, []);

  return (
    <>
      {warehouses.length>0 ? 
        warehouses.map((warehouse) => (
          <ul key={warehouse.id}>
            <li>{warehouse.warehouse_name}</li>
          </ul>
          )) : 
          <h1>Loading...</h1>}
    </>
  )
}

export default Warehouses

I'll also modify the Login component so that upon successful login it restores the page that called the component (by default after login the login component displays the home page).

     
import { useState } from "react"
import { useNavigate, useLocation } from "react-router-dom"

function Login() {
  const navigate = useNavigate()
  const location = useLocation()
  const [username, setUsername] = useState("")
  const [password, setPassword] = useState("")
  const [error, setError] = useState('')

  const handleLogin = async (e) => {
    e.preventDefault()
    try {
      const res = await fetch('/api/token/', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({username, password})
      })
      const data = await res.json()
      if (!res.ok) {
        console.log(data.detail)
      } else{
        localStorage.setItem("refreshToken", data["refresh"])
        localStorage.setItem("accessToken", data["access"])
        location.state? navigate(location.state.from) : navigate("/")
      }      
    } catch (err) {
      console.error(err)
    }
  }

  return (
    
    <form onSubmit={handleLogin}>
      <div>
        <label>Username:</label>
        <input
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            required
          />
      </div>
      <div>
        <label>Password:</label>
        <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
          />
      </div>
      <button type="submit">Login</button>
    </form>
  )
}

export default Login