Подключение React приложения к Django API

Часы
24.07.2024
Часы
18.12.2024
Часы
13 минут
Глазик
151
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0

Введение

В данной статье я опишу процесс объединения React и Django под одним проектом. Мы настроим API для общения обоих между собой, а так же подключим TailwindCSS библиотеку, дабы упростить создание сайта ещё сильнее.
Весь код статьи можно скачать с github, ветка называется simple-integration-react-django https://github.com/DmRafaule/SearchResultParser/tree/simple-integration-react-django

Создание базового проекта на Django

Корневая директория будет называться SearchResultParser. В ней создайте новый django проект. Он тоже будет иметь название SearchResultParser. После установки базового проекта, можно перейти к созданию первого приложения для данного проекта.

Добавление приложения к Django проекту

Игнорируй всё ниже описанное в данной главе, если ты хочешь просто подключить react к djanog и создать своё приложение.
Перед тем как ты перейдёшь по ссылке в заголовке, ты должен знать, что название у приложения будет Main, а название модели в ней будет Result, для админки модель будет называться ResultAdmin. И ты должен будешь добавить следующие поля в неё:
  • user (CharField)
  • file (FileField)
  • time_created (DateTimeField)

Настройка API для общения между React и Django

Установка APIs


pip install djangorestframework django-cors-headers
В файле settings.py, необходимо будет добавить ново установленные приложения, coreheaders и rest_framework в INSTALLED_APP, а так же добавить одну мидлвари corsheaders.middleware.CorsMiddleware в MIDDLEWARE список.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework',
    'Main.apps.MainConfig',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware',
]
Добавь следующие строчки внизу файла settings.py

CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000'
]

Создание cериализатора для модели

Чтобы фронтенд мог работать с моделями, необходимо их как-то получать. Для этого я создам сериализатор(экспортёр), который превратит отправляемые бэкендом записи баз данных, в json файлы.
Создай новый файл в директории приложения Main.

cd Main
touch serializers.py
Добавь следующий контент:

from rest_framework import serializers
from .models import Result

class ResultSerializer(serializers.ModelSerializer):
    class Meta:
        model = Result
        fields = (‘id’, 'user', 'file', 'time_created')

Подготовка и настройка APIs

В файле Main/views.py добавим новое представление для API. Файл будет выглядеть так:

from django.shortcuts import render
from rest_framework import viewsets
from .serializers import ResultSerializer
from .models import Result

class ResultView(viewsets.ModelViewSet):
    serializer_class = ResultSerializer
    queryset = Result.objects.all()

def home(request):
    return render(request, 'Main/home.html')
В файле SearchResultParser/urls.py нужно будет создать роутер и подключить новые пути. Вот новое содержимое файла:

from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from Main import views

router = routers.DefaultRouter()
router.register(r'results', views.ResultView, 'result')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
    path('', include('Main.urls'))
]
После всего этого у тебя появятся следующие адреса для навигации:
  • localhost:8000/api/results – Здесь ты можешь просмотреть все записи в твоей базе данных
  • localhost:8000/api/results/1/ - Так ты можешь просмотреть одну единственную запись, 1 это id записи
страница с выводом базы данных, rest api страница с выводом одного элемента базы данных, rest api

Создание базового проекта для React

Я создал react проект в корневой директории, где .venv и назвал его searchresultparser-frontend.

Устанавливаем и подключаем TailwindCSS к проекту (опционально)

Опционально, если хотите использовать что-нибудь другое. Заходим в директорию react проекта:

cd  searchresultparser-frontend
Установим пакет

npm install -D tailwindcss
Создадим файл конфигурации (tailwind.config.js) и отредактируем его

npx tailwindcss init
Замени содержание файла searchresultparser-frontend/tailwind.config.js на

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
	"./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Добавим базовые директивы в searchresultparser-frontend/src/index.css

@tailwind base;
@tailwind components;
@tailwind utilities; 
Начнём сборку и запустим проект

npm run start
Теперь библиотека tailwindcss готова к использованию.

Создадим базовый интерфейс на React

Модальное окно

В директории searchresultparser-frontend/src/ создайте новый файл.

touch Modal.js
Данный файл описывает, появление и вид модального окна. Которое нам потребуется в будущем для отправки различных запросов к API нашего сайта. Вставьте следующий код в файл Modal.js:


import React from "react";
 
const Modal = ({ isOpen, onClose, children }) => {
    if (!isOpen) return null;

    return (
        <div className="fixed top-0 left-0 w-full h-full bg-opacity-50 flex items-center justify-center">
            <div className="bg-white h-auto  min-w-80  p-3 rounded border-2 z-10 gap-3">
                {children}
                <div className="flex justify-end  mr-2">
                    <div onClick={onClose} className="bg-red-400 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Close</div>
                </div>
            </div>
        </div>
    );
};
 
export default Modal;

Страница приложения

Следующий «кусочек» кода описывает базовый интерфейс для общения с сервером и выполнения таких базовых операций как CRUD. Откройте файл App.js и вместо кода по умолчанию вставьте следующий код:


import React, { useState } from 'react';
import Modal from './Modal';

var items =[
  {
    id: 1,
    user: "dima",
    file: "0020kfjewl01232",
    time_created: "11.07.24"
  },
  {
    id: 2,
    user: "artur",
    file: "ert0k355wl01232",
    time_created: "12.07.24"
  },
  {
    id: 3,
    user: "pasha",
    file: "aaaakfje23ddf32",
    time_created: "13.07.24"
  },
];

let currentChildren = null

function App() {
  const [isModal, setOpen] = React.useState(false);
  const renderItems = () => {
    return items.map((item) => (
        <div className="flex flex-row pl-4 h-8 border-2 rounded bg-amber-100">
        <div className="flex-grow">
          {item.user+'__'+item.file}
        </div>
        <div className="flex">
          <div onClick={()=> editItem(item)} className="bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Edit</div>
          <div onClick={()=> deleteItem(item)} className="bg-orange-300 pl-3 pr-3 hover:bg-orange-50 active:scale-150 hover:cursor-pointer transition-transform">Delete</div>
        </div>
      </div>
    ));
  }
  const handleClose = () => {
    setOpen(false);
  };
 
  const handleOpen = () => {
    setOpen(true);
  };

  const deleteItem = (item) => {
    handleOpen();
    currentChildren = 
    <div className='flex flex-col justify-between h-full gap-9'>
      <b className='flex w-full justify-center text-lg'>
        DELETE ITEM
      </b>  
      <div className='flex w-full justify-center'>
        Are you sure ?
      </div>
      <div className='flex w-full justify-center gap-2'>
        <button className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>Yes</button>
        <button className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>No</button>
      </div>
    </div>
  };
  
  const createItem = () => {
    handleOpen();
    currentChildren = 
      <div>
        <b className='flex w-full justify-center text-lg'>
          ADD NEW ITEM
        </b>  
        <div className='flex w-full justify-around g-4'>
          <form className='flex flex-col gap-3'>
              <input type="text" name="username" placeholder="Name" maxlength="25" minlength="3" required="true" id="id_username"/>
              <input type="datetime-local" name="date" placeholder="Time" required="true" id="id_date"/>
              <input type="file" name="file" placeholder="File" required="true" id="id_file"/>
          </form>
        </div>
        <div className='flex w-full justify-end gap-2'>
          <button className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>Send</button>
        </div>
      </div>
  };
  
  const editItem = (item) => {
    handleOpen();
    currentChildren = 
      <div>
        <b className='flex w-full justify-center text-lg'>
          EDIT ITEM
        </b>  
        <div className='flex w-full justify-around g-4'>
          <form className='flex flex-col gap-3'>
              <input type="text" name="username" placeholder="Name" maxlength="25" minlength="3" required="true" id="id_username" value={item.user} />
              <input type="datetime-local" name="date" placeholder="Time" required="true" id="id_date" value={item.time_created} />
              <input type="text" name="file" placeholder="File" required="true" id="id_file" value={item.file} />
          </form>
        </div>
        <div className='flex w-full justify-end gap-2'>
          <button className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>Save</button>
        </div>
      </div>
  };

  return (
    <div className="p-10 flex flex-col gap-7 ">
      <header className="flex flex-row ">
        <div onClick={createItem} className="pt-2 pb-2 pl-3 pr-3 border-2 rounded hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Add new item</div>
      </header>
      <main className=" flex flex-col flex-auto flex-wrap gap-2">
        {renderItems()}
      </main>
      <Modal isOpen={isModal} onClose={handleClose} children={currentChildren}>
        
      </Modal>
    </div>
  );
}

export default App;

список элементов на react, из базы данных на django
Опять же, не особо зацикливайтесь на данном «кусочке» кода, мы его всё равно в следующей главе будем менять и препарировать. Вот так.

Соединение React и Django

Axios или fetch ?

После того как с фронтендом и бэкендом было покончено, нам необходимо их как-то связать. Для это предлагаю использовать javascript библиотеку axios.
Почему не fetch, а axios ?
Лично я выбрал axios потому что мне понравился его синтаксис, он просто элегантней и ещё одно, он поддерживает старые браузеры и их версии. Вот. Но я так же прикреплю таблицу их сравнения, что бы вы смогли сами решить что из этого лучше.
Фичи fetch axios
Доступность Встроенный Требуется установка
Синтакс Подробный, более шаблонный Краткий, удобный для пользователя
Обратка JSON Ручная Автоматическая
Обработка ошибок Ручная Встроенная
Наличие перехватчиков Не доступно Доступно
Закрытие запросов Не доступно Доступно
Поддержка браузеров Только современные браузеры Поддержка старых версий доступна
Установим и настроим axios.

npm install axios
После установки в файл package.json можно добавить путь до нашего сервера. Вставьте это значение:

"proxy": "http://localhost:8000",
Это вообще не обязательно, но позволяет не писать лишний раз полный адрес вашего сервера. Меньше кода, меньше ошибок.
Теперь осталось только указать, где и откуда мы собираемся обращаться к API нашего приложения.

Пошагово, с комментариями меняем код для App.js

Импорты и переменные состояния

Во-первых, импортируем функцию из react: useEffect . Во-вторых, импортируем сам axios. В-третьих, уберём var items. Итого, до определения функции App у нас будет только 3 импорта, вот так:


import React, { useEffect, useState, useCallback } from 'react';
import Modal from './Modal';
import axios from "axios";

Все последующие куски кода будут вставляться в функцию App


function App() {
    …
}

В начале, объявляем необходимые переменные и привязываем к ним функции для их изменения.


const [isModal, setOpen] = useState(false);
const [isUpdate, setUpdateTrigger] = useState(true);
const [itemsData, setItemsData] = useState(null);
const [formData, setFormData] = useState({
  username: "",
  datetime: "",
  file: null,
})
const [formBody, setFormBody] = useState(null);

Модальное окно

Для появления и скрытия модального окна, добавим следующие функции:


const handleClose = () => {
  setOpen(false);
};
const handleOpen =() => {
  setOpen(true);
};

Они работают таким образом, что при изменении переменной isModal, модальное окно будет либо появляться, либо исчезать.

Отрисовка списка

Давай сразу вставим код, который будет отрисовывать наш список. Этот код мы вставим в конце функции App.


return (
  <div className="p-10 flex flex-col gap-7 ">
    <header className="flex flex-row ">
      <div onClick={createItem} className="pt-2 pb-2 pl-3 pr-3 border-2 rounded hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Add new item</div>
    </header>
    <main className=" flex flex-col flex-auto flex-wrap gap-2">
    {/* Place for a list of displaying items  */}
    {itemsData ? (itemsData.map((item) => (
      <div className="flex flex-row pl-4 h-8 border-2 rounded bg-amber-100">
        <div className="flex-grow">
          {item.user+'__'+item.file}
        </div>
        <div className="flex">
          <div onClick={()=> editItem(item)} className="bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Edit</div>
          <div onClick={()=> deleteItem(item)} className="bg-orange-300 pl-3 pr-3 hover:bg-orange-50 active:scale-150 hover:cursor-pointer transition-transform">Delete</div>
        </div>
      </div>
    ))) : (<p>Loading...</p>)
    }
    </main>
    {/* Place for modal window */}
    <Modal isOpen={isModal} onClose={handleClose} children={formBody}>
    </Modal>
  </div>
);

Отрисовка модального окна, CRUD

Как видно из шаблона отрисовки списка, есть такие функции как:
  • createItem
  • editItem
  • deleteItem
Идея этих функций проста. При нажатии одной из кнопок, к которым данные функции присоединены, будет вставлена соответствующая форма в компонент Modal.


const deleteItem = (item) => {
  handleOpen();
  setFormBody(
  <div className='flex flex-col justify-between h-full gap-9'>
    <b className='flex w-full justify-center text-lg'>
      DELETE ITEM
    </b>  
    <div className='flex w-full justify-center'>
      Are you sure ?
    </div>
    <div className='flex w-full justify-center gap-2'>
      <button onClick={ () => deletionSubmit(item) } className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>Yes</button>
      <button onClick={handleClose} className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>No</button>
    </div>
  </div>)
};

react модальное окно удалить элемент


const createItem = () => {
  handleOpen();
  
  setFormBody( 
    <div>
      <b className='flex w-full justify-center text-lg'>
        ADD NEW ITEM
      </b>  
      <div className='flex w-full justify-around g-4'>
        <form className='forma flex flex-col gap-3' onSubmit={creationSubmit} encType="multipart/form-data">
            <input type="text" name="username" placeholder="Name" onChange={handleChange}/>
            <input type="datetime-local" name="date" placeholder="Time" onChange={handleChange}/>
            <input type="file" name="file" placeholder="File" onChange={handleChange}/>
            <button className='flex justify-end' type="submit">
              <div className='bg-slate-200 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform pl-3 pr-3'>
                Submit
              </div>
            </button>
        </form>
      </div>
    </div>
  )
};

react модальное окно создать элемент


const editItem = (item) => {
  handleOpen();
  setFormBody( 
    <div>
      <b className='flex w-full justify-center text-lg'>
        EDIT ITEM
      </b>  
      <div className='flex w-full justify-around g-4'>
        <form id={item.id} className='flex flex-col gap-3' onSubmit={editingSubmit}>
            <input type="text" name="username" defaultValue={item.user}  onChange={handleChange} />
            <input type="datetime-local" name="date" defaultValue={item.time_created}  onChange={handleChange} />
            <input type="file" name="file" placeholder='File'  onChange={handleChange}/>
            <button className='flex justify-end' type="submit">
              <div className='bg-slate-200 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform pl-3 pr-3'>
                Submit
              </div>
            </button>
        </form>
      </div>
    </div>)
};

react модальное окно изменить элемент
Вставь их выше return.

Обновление данных форм

В данных формах за обновление данных отвечает функция handleChange


const handleChange = (e) => {
    if (e.target.name === 'file') {
      formData.file = e.target.files[0]
      setFormData(formData)
    } else if (e.target.name === 'username') {
      formData.username = e.target.value
      setFormData(formData)
    } else if (e.target.name === 'date') {
      formData.datetime = e.target.value
      setFormData(formData)
    }
}

Общение с сервером

За непосредственную отправку запросов на сервер отвечают функции:
  • editingSubmit
  • creationSubmit
  • deletionSubmit
Они и отвечают за правильное использование axios библиотеки или fetch API
Функция creationSubmit отправляет POST запросы по адресу /api/results/ . Она сохраняет и отправляет введённые данные и триггерит обновление списка.
Шеврон Кстати, если бы мы не использовали proxy в package.json, то путь, который мы используем в наших запросах к серверу выглядел бы так: http://localhost:8000/api/results/
axios
fetch

const creationSubmit = (form_el) =>{
  form_el.preventDefault()

  var form_data = new FormData();
  form_data.append("user", formData.username)
  form_data.append("time_created", formData.datetime)
  form_data.append("file", formData.file)

  // Use the fetch API to send the form data to the server  
  axios.post('/api/results/', form_data)
  .then(data => {
      console.log('Success:', data);
      setUpdateTrigger(true)
  })
  .catch(error => {
      console.error('Error:', error);
  });

  handleClose()
};
        

const creationSubmit = (form_el) =>{
    form_el.preventDefault()

    var form_data = new FormData();
    form_data.append("user", formData.username)
    form_data.append("time_created", formData.datetime)
    form_data.append("file", formData.file)

    fetch(`http://localhost:8000/api/results/`,{
      method: 'POST',
      headers: {
        'Accept': 'application/json',
      },
      body: form_data
    })
    .then( response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      console.log('Success');
      setUpdateTrigger(true)
    })
    .catch(error => {
        console.error('There was a problem with your fetch operation:', error);
    });

    handleClose()
};
        
пример post запроса react django rest api
Функция editingSubmit отправляет PUT запросы по адресу /api/results/{ID} . Она сохраняет и отправляет введённые данные и триггерит обновление списка.
axios
fetch

const editingSubmit = (form_el) => {
        console.log(form_el)
        form_el.preventDefault()
    
        var form_data = new FormData();
        form_data.append("user", formData.username)
        form_data.append("time_created", formData.datetime)
        form_data.append("file", formData.file)
    
        // Use the fetch API to send the form data to the server  
        axios.put(`/api/results/${form_el.target.id}/`, form_data)
        .then(data => {
            console.log('Success:', data);
            setUpdateTrigger(true)
        })
        .catch(error => {
            console.error('Error:', error);
        });
    
        handleClose()
}
        

const editingSubmit = (form_el) => {
        console.log(form_el)
        form_el.preventDefault()
    
        var form_data = new FormData();
        form_data.append("user", formData.username)
        form_data.append("time_created", formData.datetime)
        form_data.append("file", formData.file)
    
        fetch(`http://localhost:8000/api/results/${form_el.target.id}/`,{
          method: 'PUT',
          headers: {
            'Accept': 'application/json',
          },
          body: form_data
        })
        .then( response => {
          if (!response.ok) {
            throw new Error('Network response was not ok');
          }
          console.log('Success');
          setUpdateTrigger(true)
        })
        .catch(error => {
            console.error('There was a problem with your fetch operation:', error);
        });
    
        handleClose()
}
        
пример put запроса react django rest api
Функция deletionSubmit отправляет DELETE запросы по адресу /api/results/{ID} . Тем самым удаляя из списка соответствующую запись по ID. Так же в конце триггерит обновление списка.
axios
fetch

const deletionSubmit = (item) => {
        axios.delete(`/api/results/${item.id}/`)
          .then((res) => setUpdateTrigger(true));
        handleClose()
}
        

const deletionSubmit = (item) => {
        fetch(`http://localhost:8000/api/results/${item.id}/`,{
          method: "DELETE",
          headers: {
            'Accept': 'application/json',
          }
        })
        .then( response => {
          if (!response.ok) {
            throw new Error('Network response was not ok');
          }
          setUpdateTrigger(true)
        })
        .catch(error => {
            console.error('There was a problem with your fetch operation:', error);
        });
        
        handleClose()
}
        
пример delete запроса react django rest api

Обновление списка

До этого момента я писал о том что функции триггерят обновление списка. В чём дело, а дело в функции useEffect. В моём случае эта функция вызывает handleUpdate, только тогда когда переменная состояния isUpdate (смотри выше) изменена. И меняется она в функциях подтверждения удаления, изменения и добавления записей в базу данных.
axios
fetch

const handleUpdate = () => {
        axios.get('/api/results/')
            .then(response => {
                setItemsData(response.data); // Save the data in state  
            })
            .catch(error => {
                console.error('There was an error fetching the data!', error);
            });
        setUpdateTrigger(false)
};
        

const handleUpdate = () => {
        fetch('http://localhost:8000/api/results/',{
          method: 'GET',
          headers: {
            'Accept': 'application/json',
          },
        })
        .then( response => {
          if (!response.ok) {
            throw new Error('Network response was not ok');
          }
          return response.json(); 
        })
        .then(data => {
          const dataArray = Array.isArray(data) ? data : [data];
          setItemsData(dataArray); // Save the data in state  
        })
        .catch(error => {
            console.error('There was a problem with your fetch operation:', error);
        });
        setUpdateTrigger(false)
};
        
Вставь вызов функции useEffect перед самым началом return.

useEffect(handleUpdate, [isUpdate]);
Если использовать useEffect без списка зависимостей (т. е. isUpdate в нашем случае ), react будет отправлять запросы об обновлении списка постоянно, что конечно сильно нагружает сервер.
На этом всё. React приложение готово и оно успешно обменивается запросами с сервером.

Готовый код для App.js

[Раскрыть] Шеврон
axios
fetch

                
import React, { useEffect, useState } from 'react';
import Modal from './Modal';
import axios from "axios";

function App() {
  const [isModal, setOpen] = useState(false);
  const [isUpdate, setUpdateTrigger] = useState(true);
  const [itemsData, setItemsData] = useState(null);
  const [formData, setFormData] = useState({
    username: "",
    datetime: "",
    file: null,
  })
  const [formBody, setFormBody] = useState(null);

  // Is going to close modal window 
  const handleClose = () => {
    setOpen(false);
  };
 
  // Is going to open modal window 
  const handleOpen =() => {
    setOpen(true);
  };

  // Is going to updating formdata 
  const handleChange = (e) => {
    if (e.target.name === 'file') {
      formData.file = e.target.files[0]
      setFormData(formData)
    } else if (e.target.name === 'username') {
      formData.username = e.target.value
      setFormData(formData)
    } else if (e.target.name === 'date') {
      formData.datetime = e.target.value
      setFormData(formData)
    }

  }

  // Is going to get all data from server
  const handleUpdate = () => {
    axios.get('/api/results/')
        .then(response => {
            setItemsData(response.data); // Save the data in state  
        })
        .catch(error => {
            console.error('There was an error fetching the data!', error);
        });
    setUpdateTrigger(false)
  };

  // Send a delete request and update list for user 
  const deletionSubmit = (item) => {
    axios.delete(`/api/results/${item.id}/`)
      .then((res) => setUpdateTrigger(true));
    handleClose()
  }

  // Paste in modal window delete form 
  const deleteItem = (item) => {
    handleOpen();
    setFormBody(
    <div className='flex flex-col justify-between h-full gap-9'>
      <b className='flex w-full justify-center text-lg'>
        DELETE ITEM
      </b>  
      <div className='flex w-full justify-center'>
        Are you sure ?
      </div>
      <div className='flex w-full justify-center gap-2'>
        <button onClick={ () => deletionSubmit(item) } className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>Yes</button>
        <button onClick={handleClose} className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>No</button>
      </div>
    </div>)
  };
  
  // Send a post request with form data to create a new record in database
  const creationSubmit = (form_el) =>{
    form_el.preventDefault()

    var form_data = new FormData();
    form_data.append("user", formData.username)
    form_data.append("time_created", formData.datetime)
    form_data.append("file", formData.file)

    // Use the fetch API to send the form data to the server  
    axios.post('/api/results/', form_data)
    .then(data => {
        console.log('Success:', data);
        setUpdateTrigger(true)
    })
    .catch(error => {
        console.error('Error:', error);
    });

    handleClose()
  };

  // Paste in modal window create form 
  const createItem = () => {
    handleOpen();
    
    setFormBody( 
      <div>
        <b className='flex w-full justify-center text-lg'>
          ADD NEW ITEM
        </b>  
        <div className='flex w-full justify-around g-4'>
          <form className='forma flex flex-col gap-3' onSubmit={creationSubmit} encType="multipart/form-data">
              <input type="text" name="username" placeholder="Name" onChange={handleChange}/>
              <input type="datetime-local" name="date" placeholder="Time" onChange={handleChange}/>
              <input type="file" name="file" placeholder="File" onChange={handleChange}/>
              <button className='flex justify-end' type="submit">
                <div className='bg-slate-200 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform pl-3 pr-3'>
                  Submit
                </div>
              </button>
          </form>
        </div>
      </div>
    )
  };
  
  // Send a put request with form data to change existing a record in database 
  const editingSubmit = (form_el) => {
    console.log(form_el)
    form_el.preventDefault()

    var form_data = new FormData();
    form_data.append("user", formData.username)
    form_data.append("time_created", formData.datetime)
    form_data.append("file", formData.file)

    // Use the fetch API to send the form data to the server  
    axios.put(`/api/results/${form_el.target.id}/`, form_data)
    .then(data => {
        console.log('Success:', data);
        setUpdateTrigger(true)
    })
    .catch(error => {
        console.error('Error:', error);
    });

    handleClose()
  }

  // Paste in modal window edit form 
  const editItem = (item) => {
    handleOpen();
    setFormBody( 
      <div>
        <b className='flex w-full justify-center text-lg'>
          EDIT ITEM
        </b>  
        <div className='flex w-full justify-around g-4'>
          <form id={item.id} className='flex flex-col gap-3' onSubmit={editingSubmit}>
              <input type="text" name="username" defaultValue={item.user}  onChange={handleChange} />
              <input type="datetime-local" name="date" defaultValue={item.time_created}  onChange={handleChange} />
              <input type="file" name="file" placeholder='File'  onChange={handleChange}/>
              <button className='flex justify-end' type="submit">
                <div className='bg-slate-200 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform pl-3 pr-3'>
                  Submit
                </div>
              </button>
          </form>
        </div>
      </div>)
  };

  // Updating list of items when isUpdate is changed 
  useEffect(handleUpdate, [isUpdate]);

  return (
    <div className="p-10 flex flex-col gap-7 ">
      <header className="flex flex-row ">
        <div onClick={createItem} className="pt-2 pb-2 pl-3 pr-3 border-2 rounded hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Add new item</div>
      </header>
      <main className=" flex flex-col flex-auto flex-wrap gap-2">
      {/* Place for a list of displaying items  */}
      {itemsData ? (itemsData.map((item) => (
        <div className="flex flex-row pl-4 h-8 border-2 rounded bg-amber-100">
          <div className="flex-grow">
            {item.user+'__'+item.file}
          </div>
          <div className="flex">
            <div onClick={()=> editItem(item)} className="bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Edit</div>
            <div onClick={()=> deleteItem(item)} className="bg-orange-300 pl-3 pr-3 hover:bg-orange-50 active:scale-150 hover:cursor-pointer transition-transform">Delete</div>
          </div>
        </div>
      ))) : (<p>Loading...</p>)
      }
      </main>
      {/* Place for modal window */}
      <Modal isOpen={isModal} onClose={handleClose} children={formBody}>
      </Modal>
    </div>
  );
}

export default App;
                
                

                
import React, { useEffect, useState } from 'react';
import Modal from './Modal';
import axios from "axios";

function App() {
  const [isModal, setOpen] = useState(false);
  const [isUpdate, setUpdateTrigger] = useState(true);
  const [itemsData, setItemsData] = useState(null);
  const [formData, setFormData] = useState({
    username: "",
    datetime: "",
    file: null,
  })
  const [formBody, setFormBody] = useState(null);

  // Is going to close modal window 
  const handleClose = () => {
    setOpen(false);
  };
 
  // Is going to open modal window 
  const handleOpen =() => {
    setOpen(true);
  };

  // Is going to updating formdata 
  const handleChange = (e) => {
    if (e.target.name === 'file') {
      formData.file = e.target.files[0]
      setFormData(formData)
    } else if (e.target.name === 'username') {
      formData.username = e.target.value
      setFormData(formData)
    } else if (e.target.name === 'date') {
      formData.datetime = e.target.value
      setFormData(formData)
    }

  }

  // Is going to get all data from server
  const handleUpdate = () => {
    fetch('http://localhost:8000/api/results/',{
      method: 'GET',
      headers: {
        'Accept': 'application/json',
      },
    })
    .then( response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json(); 
    })
    .then(data => {
      const dataArray = Array.isArray(data) ? data : [data];
      setItemsData(dataArray); // Save the data in state  
    })
    .catch(error => {
        console.error('There was a problem with your fetch operation:', error);
    });
    setUpdateTrigger(false)
  };

  // Send a delete request and update list for user 
  const deletionSubmit = (item) => {
    fetch(`http://localhost:8000/api/results/${item.id}/`,{
      method: "DELETE",
      headers: {
        'Accept': 'application/json',
      }
    })
    .then( response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      setUpdateTrigger(true)
    })
    .catch(error => {
        console.error('There was a problem with your fetch operation:', error);
    });
    
    handleClose()
  }

  // Paste in modal window delete form 
  const deleteItem = (item) => {
    handleOpen();
    setFormBody(
    <div className='flex flex-col justify-between h-full gap-9'>
      <b className='flex w-full justify-center text-lg'>
        DELETE ITEM
      </b>  
      <div className='flex w-full justify-center'>
        Are you sure ?
      </div>
      <div className='flex w-full justify-center gap-2'>
        <button onClick={ () => deletionSubmit(item) } className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>Yes</button>
        <button onClick={handleClose} className='bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform'>No</button>
      </div>
    </div>)
  };
  
  // Send a post request with form data to create a new record in database
  const creationSubmit = (form_el) =>{
    form_el.preventDefault()

    var form_data = new FormData();
    form_data.append("user", formData.username)
    form_data.append("time_created", formData.datetime)
    form_data.append("file", formData.file)

    fetch(`http://localhost:8000/api/results/`,{
      method: 'POST',
      headers: {
        'Accept': 'application/json',
      },
      body: form_data
    })
    .then( response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      console.log('Success');
      setUpdateTrigger(true)
    })
    .catch(error => {
        console.error('There was a problem with your fetch operation:', error);
    });

    handleClose()
  };

  // Paste in modal window create form 
  const createItem = () => {
    handleOpen();
    
    setFormBody( 
      <div>
        <b className='flex w-full justify-center text-lg'>
          ADD NEW ITEM
        </b>  
        <div className='flex w-full justify-around g-4'>
          <form className='forma flex flex-col gap-3' onSubmit={creationSubmit} encType="multipart/form-data">
              <input type="text" name="username" placeholder="Name" onChange={handleChange}/>
              <input type="datetime-local" name="date" placeholder="Time" onChange={handleChange}/>
              <input type="file" name="file" placeholder="File" onChange={handleChange}/>
              <button className='flex justify-end' type="submit">
                <div className='bg-slate-200 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform pl-3 pr-3'>
                  Submit
                </div>
              </button>
          </form>
        </div>
      </div>
    )
  };
  
  // Send a put request with form data to change existing a record in database 
  const editingSubmit = (form_el) => {
    console.log(form_el)
    form_el.preventDefault()

    var form_data = new FormData();
    form_data.append("user", formData.username)
    form_data.append("time_created", formData.datetime)
    form_data.append("file", formData.file)

    fetch(`http://localhost:8000/api/results/${form_el.target.id}/`,{
      method: 'PUT',
      headers: {
        'Accept': 'application/json',
      },
      body: form_data
    })
    .then( response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      console.log('Success');
      setUpdateTrigger(true)
    })
    .catch(error => {
        console.error('There was a problem with your fetch operation:', error);
    });

    handleClose()
  }

  // Paste in modal window edit form 
  const editItem = (item) => {
    handleOpen();
    setFormBody( 
      <div>
        <b className='flex w-full justify-center text-lg'>
          EDIT ITEM
        </b>  
        <div className='flex w-full justify-around g-4'>
          <form id={item.id} className='flex flex-col gap-3' onSubmit={editingSubmit}>
              <input type="text" name="username" defaultValue={item.user}  onChange={handleChange} />
              <input type="datetime-local" name="date" defaultValue={item.time_created}  onChange={handleChange} />
              <input type="file" name="file" placeholder='File'  onChange={handleChange}/>
              <button className='flex justify-end' type="submit">
                <div className='bg-slate-200 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform pl-3 pr-3'>
                  Submit
                </div>
              </button>
          </form>
        </div>
      </div>)
  };

  // Updating list of items when isUpdate is changed 
  useEffect(handleUpdate, [isUpdate]);

  return (
    <div className="p-10 flex flex-col gap-7 ">
      <header className="flex flex-row ">
        <div onClick={createItem} className="pt-2 pb-2 pl-3 pr-3 border-2 rounded hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Add new item</div>
      </header>
      <main className=" flex flex-col flex-auto flex-wrap gap-2">
      {/* Place for a list of displaying items  */}
      {itemsData ? (itemsData.map((item) => (
        <div className="flex flex-row pl-4 h-8 border-2 rounded bg-amber-100">
          <div className="flex-grow">
            {item.user+'__'+item.file}
          </div>
          <div className="flex">
            <div onClick={()=> editItem(item)} className="bg-slate-200 pl-3 pr-3 hover:bg-slate-50 active:scale-150 hover:cursor-pointer transition-transform">Edit</div>
            <div onClick={()=> deleteItem(item)} className="bg-orange-300 pl-3 pr-3 hover:bg-orange-50 active:scale-150 hover:cursor-pointer transition-transform">Delete</div>
          </div>
        </div>
      ))) : (<p>Loading...</p>)
      }
      </main>
      {/* Place for modal window */}
      <Modal isOpen={isModal} onClose={handleClose} children={formBody}>
      </Modal>
    </div>
  );
}

export default App;
                
                

Заключение

Конечно, я не рассказал о том как деплоить данный проект на сервер, или то как react будет взаимодействовать с шаблонами от django. Конечно, для них будут отдельные статьи и всё со временем уляжеться. Ну а пока вот так.
Весь код статьи можно скачать с github, ветка называется simple-integration-react-django https://github.com/DmRafaule/SearchResultParser/tree/simple-integration-react-django

Комментарии

(0)

captcha
Отправить
Сейчас тут пусто. Буть первым (o゚v゚)ノ

Другое

Похожие статьи


Django rest framework, как настроить и как использовать

Часы
24.02.2025
В этой статье описывается процесс настройки и добавления REST фреймворка на сайт, написанный на Django. Добавляется он для того, чтобы построить API для доступа со стороны клиента (фронтенда). Так же …

Как соединить React приложение с Django проектом ч. 1

Часы
03.08.2024
В этой статье ты узнаешь как можно объединить React фреймворк с Django проектом, таким образом чтобы получилось полноценное фулстак приложение. Так же в статье приведено видео-туториал и скачиваемые образцы для …