0

I've recently started learning Reactjs & made a simple Todo react appplication in order to learn it bit-by-bit, step-by-step.The data in this Todo react front-end is coming from a simple Todo Django REST API. But the problem is I want to change the state of "completed" to depict a task completed. Im trying to do this by sending a PUT request to the API using the unique "id" but getting some errors. Below is my approach :

List.js

import React from 'react'
import Todo from './Todo'
import Nav from './Nav'


//Class based Component
class List extends React.Component {

    constructor() {
        super()
        this.state = {
            todos: [],
        }
        this.handleChange = this.handleChange.bind(this)
    }

    fetchData() {
        fetch('http://localhost:8000/Todo-api/')
            .then(response => response.json())
            .then((data) => {
                this.setState({
                    todos: data
                });
            });
    }

    componentDidMount() {
        this.fetchData();
    }

    

    handleChange(id) {
        this.setState(prevState => {
            const updatedData = prevState.todos.map(todo => {
                if (todo.id === id) {
                    todo.completed = !todo.completed
                }
                return todo
            })
            return {
                todos: updatedData
            }
        })

        fetch(`http://127.0.0.1:8000/Todo-api/${id}/?format=json/`,
            {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept' : 'application/json',
                
                    // Other possible headers
                },
                body: JSON.stringify(this.state)

            }).then(function (response) {
                return response.json();
            }).then(function (data) {
                console.log("Data is ok", data);
            }).catch(function (ex) {
                console.log("parsing failed", ex);
            });
    }

    currentDate() {
        return new Date().getFullYear();
    }


    render() {
        const Data = this.state.todos.map(details => <Todo key={details.id} det={details} handleChange={this.handleChange} />)

        const someStyle = {
            marginTop: "40px", marginLeft: "100px", marginRight: "100px",
        }
        console.log(this.state.todos)
        return (
            <div>
                <Nav />

                <div className="todo-list box">
                    {Data}
                </div>

                <hr style={someStyle} />

                <p className="text-center" style={{ fontFamily: "Roboto Mono", lineHeight: 0.5 }}>Copyright ©️ {this.currentDate()} Parth Pandya </p>

                <p className="text-center" style={{ fontFamily: "Roboto Mono", fontSize: 10, lineHeight: 0.5 }}>Some rights reserved.</p>
            </div>
        )
    }
}

export default List

Todo.js

import React from 'react'



function Todo(props) {
  const completeStyles = {
    fontStyle: "italic",
    textDecoration: "Line-through",
    color: "gray",
  }

  return (
    <div className="todo-item">
      <input type="checkbox" onChange={() => props.handleChange(props.det.id)} checked={props.det.completed} />
      <p style={props.det.completed ? completeStyles : null}>{props.det.title}</p>
    </div>
  );
}

export default Todo;

views.py of API

from django.shortcuts import render
from rest_framework.response import Response
from rest_framework import status
from rest_framework.views import APIView
from .serializers import TodoDataSerializer
from .models import TodoData
# Create your views here.

class Todolist(APIView):
    serializer_class = TodoDataSerializer

    def get(self, request):
        ToDoData = TodoData.objects.all()
        serializer = TodoDataSerializer(ToDoData, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = TodoDataSerializer(data=request.data)
        if(serializer.is_valid()):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.data, status=status.HTTP_404_BAD_REQUEST)

class TodoDetail(APIView):
    serializer_class = TodoDataSerializer

    def get(self, request, pk):
        ToDoData = TodoData.objects.get(pk=pk)
        serializer = TodoDataSerializer(ToDoData)
        return Response(serializer.data)

    def delete(self, request, pk):
        ToDoData = TodoData.objects.filter(pk=pk)
        ToDoData.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

    def put(self, request, pk):
        snippets = TodoData.objects.get(pk=pk)
        serializer = TodoDataSerializer(snippets, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The DELETE method works well, but I'm not getting any idea about how to do it for PUT/POST. Went through many resources but most of them were suggesting axios which I don't want to use now. I want to get this done using fetch().

When I use body:JSON.stringify(this.state.todos) I get the error below:

PUT http://127.0.0.1:8000/Todo-api/4/ 400 (Bad Request) Data is ok {non_field_errors: Array(1)}non_field_errors: ["Invalid data. Expected a dictionary, but got list."]__proto__: Object

When I use body:JSON.stringify(this.state) I get the error below:

PUT http://127.0.0.1:8000/Todo-api/4/ 400 (Bad Request) Data is ok {title: Array(1), completed: Array(1)} completed: ["This field is required."] title: ["This field is required."] __proto__: Object

What to do now?

(Note : To test it locally, clone this git repository)

2 Answers2

1

Please try to use PATCH instead of PUT.

  • PUT is for full object update,
  • PATCH is for partial update (you can update only selected field).

What you need is PATCH.

In the update request, please send only the dict with the field that you want to update ({"status": "complete"}) and set id in the request URL.

What is more, you can check the full working CRUD example in my article: CRUD in Django Rest Framework and React.

pplonski
  • 5,023
  • 1
  • 30
  • 34
  • Doing a PATCH request really worked. I also tested it by putting a hardcoded value, true or false into it and again called fetchData() there and it worked. But how can I do it dynamically? The last step where I'm stuck In. That conditional `todo.completed = !todo.completed` is not working. I tested it by implementing simple if else too but it's just executing the else condition. – Parth Pandya Dec 03 '20 at 17:16
  • Now our frontend is able to communicate the backend by sending the patch request, but how to change the value from the react? As that conditional is not working. Any suggestions? – Parth Pandya Dec 03 '20 at 17:18
  • If the answer is correct, please accept it. To set a value dynamically you need to get its value from the component state. – pplonski Dec 03 '20 at 18:46
  • I've already upvoted your answer and accepted it. But I've less than 15 reputation so it's not reflecting. Thanks! – Parth Pandya Dec 04 '20 at 07:23
0

remove ?format=json. Don't forget to add trailing forward slash after id!

Before

 fetch(`http://127.0.0.1:8000/Todo-api/${id}/?format=json/`, {
      method: "PUT"
      ...
    })

After

fetch(`http://127.0.0.1:8000/Todo-api/${id}/`, {
          method: "PUT"
          ...
        })