reactjsdjangodjango-rest-frameworkjsx

React and Django DRF


The backend works, when i test it with an http file. and directly at the endpoint.

POST http://localhost:8000/api/products/ HTTP/1.1
Content-Type: application/json
Authorization: Bearer my_access_token


{
    "name": "test",
    "price": 23,
    "stock": 3,
    "description": "test description",
    "in_sale": true,
    "sale_price": 21,
    "color": [
        {
            "name":"black",
            "color_code": "#000000"
        }
    ]
}

However when i fill the forms created in react jsx and send it to the API, i get Key Error when i leave the Color Nested Serializer active, when i remove it, i get Bad Request.

Here is my serializer.py

class ProductCreateSerializer(serializers.ModelSerializer):
     class ColorCreateSerializer(serializers.ModelSerializer):
         class Meta:
             model = Color
             fields = ('name', 'color_code')
            
    # color = ColorCreateSerializer(many=True, required=False)

    def update(self, instance, validated_data):
        # color_data = validated_data.pop("color")

        with transaction.atomic():
            instance = super().update(instance, validated_data)
             if color_data is not None:
                 instance.color.all().delete()
                 for item in color_data:
                     Color.objects.create(product=instance, **item)
        return instance

    def create(self, validated_data):
        color_data = validated_data.pop('color')

        with transaction.atomic():
            product = Product.objects.create(**validated_data)
             for item in color_data:
                 Color.objects.create(product=product, **item)
        return product

    class Meta:
        model = Product
        fields = (
            'id',
            'name',
            'description',
            'price',
            'stock',
            'in_sale',
            'sale_price',
            'image',
            'color'

        )
        extra_kwargs = {
            'id': {'read_only': True}
        }

Here is my views.py

class ProductView(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    permission_classes = [AllowAny]

    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    def perform_create(self, serializer):
        product = serializer.save(user=self.request.user)

    def get_serializer_class(self):
        if self.action == 'create' or self.action == 'update':
            return ProductCreateSerializer
        return super().get_serializer_class()

And finally my CreateProduct.jsx file

import React, { useState, useEffect } from 'react'
import api from '../../api';
import Form from 'react-bootstrap/Form';

function CreateProduct() {
    const [colors, setColors] = useState([{ name: '', color_code: '' }]);

    const handleColorChange = (index, event) => {
        let data = [...colors];
        data[index][event.target.name] = event.target.value
        setColors(data);
    }

    const addColorFields = () => {
        let newColor = { name: '', color_code: '' }
        setColors([...colors, newColor])
    }

    const removeColorFields = (index) => {
        let data = [...colors];
        data.splice(index, 1)
        setColors(data)
    }

    const [product, setProduct] = useState(
        {
            name: '',
            description: '',
            price: '',
            stock: '',
            image: null,
            in_sale: false,
            sale_price: '',
        }
    );

    const handleProductInputChange = (event) => {
        if (event.target.name === 'in_sale') {
            setProduct({
                ...product,
                [event.target.name]: event.target.checked
            })
        } else {
            setProduct({
                ...product,
                [event.target.name]: event.target.value
            })
        }
    };

    const [selectedFile, setSelectedFile] = useState(null)
    const [image, setImage] = useState(null)

    const handleFileChange = (event) => {
        setSelectedFile(event.target.files[0]);
        setImage(selectedFile)
    }


    const handleSubmit = async (e) => {
        e.preventDefault();
        const formData = new FormData();

        Object.entries(product).forEach(([key, value]) => {
            if (key === 'image' && value) {
                formData.append(key, image);
            } else {
                formData.append(key, value);
            }
        });

        await api.post(`my_endpoint/`, formData);
    };

return (
        <>

            <section className="bg-light">
                <div className="container pb-5">

                    <Form onSubmit={handleSubmit}>

                        {/* Product Info */}
                        <div className="row gutters-sm shadow p-4 rounded">
                            <h4 className="mb-4">Product Info</h4>
                            <div className="col-lg-5 mt-2">
                                <div className="card mb-3">
                                    {image ?
                                        <img src={image} />
                                        :
                                        <img src="http://127.0.0.1:8000/media/products/placeholder-image.png" alt="" />
                                    }
                                </div>
                            </div>
                            {/* <!-- col end --> */}

                            <div className="col-lg-7 mt-2">
                                <div className="card">
                                    <div className="card-body mb-4">
                                        <div style={{ marginBottom: 5 }}>
                                            <label htmlFor="image" className="mb-0">
                                                Product Image
                                            </label>
                                            <input
                                                type="file"
                                                className="form-control form-control-sm"
                                                name="image"
                                                id="image"
                                                onChange={handleFileChange}
                                            />
                                        </div>
                                        <div style={{ marginBottom: 5 }}>
                                            <label htmlFor="name" className="mb-0">
                                                Title
                                            </label>
                                            <input
                                                type="text"
                                                className="form-control form-control-sm"
                                                id="name"
                                                name="name"
                                                value={product.name || ''}
                                                onChange={handleProductInputChange}
                                            />
                                        </div>
                                        <div>
                                            <label htmlFor="name" className="mb-0">
                                                Description
                                            </label>
                                            <input
                                                type="text"
                                                className="form-control form-control-sm"
                                                id="description"
                                                name="description"
                                                value={product.description || ''}
                                                onChange={handleProductInputChange}
                                            />
                                        </div>
                                        <div>
                                            <label htmlFor="price" className="mb-0">
                                                Price
                                            </label>
                                            <input
                                                type="number"
                                                className="form-control form-control-sm"
                                                name="price"
                                                value={product.price || ''}
                                                onChange={handleProductInputChange}
                                            />
                                        </div>
                                        <div>
                                            <label htmlFor="stock" className="mb-0">
                                                Stock Qty
                                            </label>
                                            <input
                                                type="number"
                                                className="form-control form-control-sm"
                                                name="stock"
                                                value={product.stock || ''}
                                                onChange={handleProductInputChange}
                                            />
                                        </div>
                                        <div className='mt-3 mb-2'>
                                            <Form.Check
                                                type="checkbox"
                                                name="in_sale"
                                                label='On Sale'
                                                value={product.in_sale}
                                                onChange={handleProductInputChange}
                                            />
                                        </div>
                                        <div>
                                            <label htmlFor="sale_price" className="mb-0">
                                                Sale Price
                                            </label>
                                            <input
                                                type="number"
                                                className="form-control form-control-sm"
                                                name="sale_price"
                                                value={product.sale_price || ''}
                                                onChange={handleProductInputChange}
                                            />
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

<div className="row gutters-sm shadow p-4 rounded">
                            <h4 className="mb-4">Color</h4>
                            <div className="col-md-12">
                                <div className="card mb-3">
                                    <div className="card-body">
                                        {colors.map((input, index) => (
                                            <div className="row text-dark mb-3" key={index}>
                                                <div className="col-lg-2 mb-2">
                                                    <label htmlFor="color_name" className="mb-2">
                                                        Color Name
                                                    </label>
                                                    <input
                                                        type="text"
                                                        className="form-control"
                                                        name="name"
                                                        id="color_name"
                                                        value={input.name}
                                                        onChange={(event) => handleColorChange(index, event)} />
                                                </div>
                                                <div className="col-lg-2 mb-2">
                                                    <label htmlFor="color_code" className="mb-2">
                                                        Color Code
                                                    </label>
                                                    <input
                                                        type="text"
                                                        className="form-control"
                                                        name="color_code"
                                                        id="color_code"
                                                        value={input.color_code}
                                                        onChange={(event) => handleColorChange(index, event)} />
                                                </div>

                                                <div className="col-lg-2 mt-2">
                                                    <button type='button' onClick={() => removeColorFields(index)} className='btn btn-danger mt-4'>Remove</button>
                                                </div>

                                            </div>
                                        ))}
                                        <button type='button' onClick={addColorFields} className="btn btn-primary mt-2">
                                            <i className="fas fa-plus" /> Add More...
                                        </button>
                                    </div>
                                </div>
                            </div>
                        </div>

    )
}

export default CreateProduct

What's wrong here??? If the endpoints works, it should be a frontend error, right? I'm new at React. Trying the FullStack life here. Please Help!


Solution

  • import React, { useState } from 'react';
    import Form from 'react-bootstrap/Form';
    import api from './api'; // Adjust the import path as needed
    
    function CreateProduct() {
        const [colors, setColors] = useState([{ name: '', color_code: '' }]);
        const [image, setImage] = useState(null);
        const [selectedFile, setSelectedFile] = useState(null);
    
        const handleColorChange = (index, event) => {
            let data = [...colors];
            data[index][event.target.name] = event.target.value
            setColors(data);
        }
    
        const addColorFields = () => {
            let newColor = { name: '', color_code: '' }
            setColors([...colors, newColor])
        }
    
        const removeColorFields = (index) => {
            let data = [...colors];
            data.splice(index, 1)
            setColors(data)
    const [product, setProduct] = useState(
        {
            name: '',
            description: '',
            price: '',
            stock: '',
            image: null,
            in_sale: false,
            sale_price: '',
        }
    );
    
    const handleProductInputChange = (event) => {
        if (event.target.name === 'in_sale') {
            setProduct({
                ...product,
                [event.target.name]: event.target.checked
            })
        } else {
            setProduct({
                ...product,
                [event.target.name]: event.target.value
            })
        }
    };
    
    const handleFileChange = (event) => {
        setSelectedFile(event.target.files[0]);
        setImage(URL.createObjectURL(event.target.files[0]));
        setProduct({
            ...product,
            image: event.target.files[0]
        });
    };
            setImage(selectedFile)
        const handleSubmit = async (e) => {
            e.preventDefault();
            const formData = new FormData();
    
            Object.entries(product).forEach(([key, value]) => {
                if (key === 'image' && value) {
                    formData.append(key, value);
                } else {
                    formData.append(key, value);
                }
            });
    
            // Optionally add colors to formData if needed
            // formData.append('colors', JSON.stringify(colors));
    
            await api.post(`my_endpoint/`, formData);
        };
    
        return (
            <>
                <section className="bg-light">
                    <div className="container pb-5">
                        <Form onSubmit={handleSubmit}>
                            {/* Product Info */}
                            <div className="row gutters-sm shadow p-4 rounded">
                                <h4 className="mb-4">Product Info</h4>
                                <div className="col-lg-5 mt-2">
                                    <div className="card mb-3">
                                        {image ?
                                            <img src={image} alt="preview" />
                                            :
                                            <img src="http://127.0.0.1:8000/media/products/placeholder-image.png" alt="" />
                                        }
                                    </div>
                                </div>
                                {/* <!-- col end --> */}
                                <div className="col-lg-7 mt-2">
                                    <div className="card">
                                        <div className="card-body mb-4">
                                            <div style={{ marginBottom: 5 }}>
                                                <label htmlFor="image" className="mb-0">
                                                    Product Image
                                                </label>
                                                <input
                                                    type="file"
                                                    className="form-control form-control-sm"
                                                    name="image"
                                                    id="image"
                                                    onChange={handleFileChange}
                                                />
                                            </div>
                                            <div style={{ marginBottom: 5 }}>
                                                <label htmlFor="name" className="mb-0">
                                                    Title
                                                </label>
                                                <input
                                                    type="text"
                                                    className="form-control form-control-sm"
                                                    id="name"
                                                    name="name"
                                                    value={product.name || ''}
                                                    onChange={handleProductInputChange}
                                                />
                                            </div>
                                            <div>
                                                <label htmlFor="description" className="mb-0">
                                                    Description
                                                </label>
                                                <input
                                                    type="text"
                                                    className="form-control form-control-sm"
                                                    id="description"
                                                    name="description"
                                                    value={product.description || ''}
                                                    onChange={handleProductInputChange}
                                                />
                                            </div>
                                            <div>
                                                <label htmlFor="price" className="mb-0">
                                                    Price
                                                </label>
                                                <input
                                                    type="number"
                                                    className="form-control form-control-sm"
                                                    name="price"
                                                    value={product.price || ''}
                                                    onChange={handleProductInputChange}
                                                />
                                            </div>
                                            <div>
                                                <label htmlFor="stock" className="mb-0">
                                                    Stock Qty
                                                </label>
                                                <input
                                                    type="number"
                                                    className="form-control form-control-sm"
                                                    name="stock"
                                                    value={product.stock || ''}
                                                    onChange={handleProductInputChange}
                                                />
                                            </div>
                                            <div className='mt-3 mb-2'>
                                                <Form.Check
                                                    type="checkbox"
                                                    name="in_sale"
                                                    label='On Sale'
                                                    checked={product.in_sale}
                                                    onChange={handleProductInputChange}
                                                />
                                            </div>
                                            <div>
                                                <label htmlFor="sale_price" className="mb-0">
                                                    Sale Price
                                                </label>
                                                <input
                                                    type="number"
                                                    className="form-control form-control-sm"
                                                    name="sale_price"
                                                    value={product.sale_price || ''}
                                                    onChange={handleProductInputChange}
                                                />
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
    
                            <div className="row gutters-sm shadow p-4 rounded">
                                <h4 className="mb-4">Color</h4>
                                <div className="col-md-12">
                                    <div className="card mb-3">
                                        <div className="card-body">
                                            {colors.map((input, index) => (
                                                <div className="row text-dark mb-3" key={index}>
                                                    <div className="col-lg-2 mb-2">
                                                        <label htmlFor={`color_name_${index}`} className="mb-2">
                                                            Color Name
                                                        </label>
                                                        <input
                                                            type="text"
                                                            className="form-control"
                                                            name="name"
                                                            id={`color_name_${index}`}
                                                            value={input.name}
                                                            onChange={(event) => handleColorChange(index, event)} />
                                                    </div>
                                                    <div className="col-lg-2 mb-2">
                                                        <label htmlFor={`color_code_${index}`} className="mb-2">
                                                            Color Code
                                                        </label>
                                                        <input
                                                            type="text"
                                                            className="form-control"
                                                            name="color_code"
                                                            id={`color_code_${index}`}
                                                            value={input.color_code}
                                                            onChange={(event) => handleColorChange(index, event)} />
                                                    </div>
                                                    <div className="col-lg-2 mt-2">
                                                        <button type='button' onClick={() => removeColorFields(index)} className='btn btn-danger mt-4'>Remove</button>
                                                    </div>
                                                </div>
                                            ))}
                                            <button type='button' onClick={addColorFields} className="btn btn-primary mt-2">
                                                <i className="fas fa-plus" /> Add More...
                                            </button>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <button type="submit" className="btn btn-success mt-4">Create Product</button>
                        </Form>
                    </div>
                </section>
            </>
        );
    }
    
    export default CreateProduct;
    }
    
    export default CreateProduct
    
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    class ColorCreateSerializer(serializers.ModelSerializer):
        class Meta:
            model = Color
            fields = ('name', 'color_code')
    
    class ProductCreateSerializer(serializers.ModelSerializer):
        color = ColorCreateSerializer(many=True, required=False)
    
        class Meta:
            model = Product
            fields = (
                'id',
                'name',
                'description',
                'price',
                'stock',
                'in_sale',
                'sale_price',
                'image',
                'color'
            )
            extra_kwargs = {
                'id': {'read_only': True}
            }
    
        def update(self, instance, validated_data):
            color_data = validated_data.pop("color", None)
    
            with transaction.atomic():
                instance = super().update(instance, validated_data)
                if color_data is not None:
                    instance.color.all().delete()
                    for item in color_data:
                        Color.objects.create(product=instance, **item)
            return instance
    
        def create(self, validated_data):
            color_data = validated_data.pop('color', [])
            with transaction.atomic():
                product = Product.objects.create(**validated_data)
                for item in color_data:
                    Color.objects.create(product=product, **item)
            return product