Search code examples
reactjslaravelreact-hook-form

Laravel validation Failed to Upload


I'm using Laravel on the backend and React.js on the frontend. When I use Laravel validation for images, the image fails to upload. However, if I don't use validation for images, it works correctly. I wonder why. Can you please help me?

Custom Hooks

import { useEffect } from "react";
import { useNavigate, useParams } from "react-router";
import { useDispatch, useSelector } from "react-redux";
import { useForm } from "react-hook-form";
import { toast } from "@/components/ui/use-toast.js";
import { formatISO } from "date-fns";
import { siswaDetail, siswaUpdate } from "@/features/siswaSlice.js";

function UseSiswaEdit() {
  const { id } = useParams();
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const { formError, formSuccess, formLoading, detailSiswa } = useSelector(
    (state) => state.siswa,
  );

  const form = useForm({
    defaultValues: {
      nama: "",
      nisn: "",
      agama: "",
      no_telpon: "",
      jenis_kelamin: "",
      tgl_lahir: "",
      tempat_lahir: "",
      foto: "",
      alamat: "",
    },
  });

  const storage = JSON.parse(sessionStorage.getItem("siswaDetail"));

  useEffect(() => {
    dispatch(siswaDetail(id));
    if (storage) {
      Object.keys(storage).forEach((key) => {
        form.setValue(key, storage[key]);
      });
    }

    if (formSuccess) {
      toast({
        title: "Success",
        description: "Data siswa berhasil diubah!",
      });

      setTimeout(() => {
        navigate("/siswa");
      }, 1000);
    }
  }, [formSuccess, dispatch, form]);

  function onSubmit(values) {
    if (values.tgl_lahir) {
      values.tgl_lahir = formatISO(values.tgl_lahir, {
        representation: "date",
      });
    }
    const formData = new FormData();
    Object.keys(values).forEach((key) => {
      formData.append(key, values[key]);
    });
    dispatch(siswaUpdate({ id, formData }));
  }

  return {
    id,
    formLoading,
    formSuccess,
    formError,
    detailSiswa,
    onSubmit,
    form,
  };
}

export default UseSiswaEdit;
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card.jsx";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select.jsx";
import { Input } from "@/components/ui/input.jsx";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover.jsx";
import { Button } from "@/components/ui/button.jsx";
import { cn } from "@/lib/utils.js";
import { CalendarIcon } from "@radix-ui/react-icons";
import { format } from "date-fns";
import { Calendar } from "@/components/ui/calendar.jsx";
import { Textarea } from "@/components/ui/textarea.jsx";
import { Link } from "react-router-dom";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
} from "@/components/ui/form.jsx";
import { asset } from "@/service/apiCall.js";
import { Label } from "@/components/ui/label.jsx";
import useSiswaEdit from "@/hooks/siswa/useSiswaEdit.jsx";

function SiswaEdit() {
  const { formLoading, formError, detailSiswa, onSubmit, form } =
    useSiswaEdit();
  return (
    <Card>
      <CardHeader>
        <CardTitle>Ubah Data Siswa</CardTitle>
        <CardDescription>
          Mohon isi formulir ini dengan baik dan benar
        </CardDescription>
      </CardHeader>
      <CardContent>
        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)}>
            <FormField
              control={form.control}
              name="nama"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="name">
                      Nama
                      <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Input
                        type="text"
                        id="name"
                        placeholder="John Doe"
                        {...field}
                      />
                      {formError?.name ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.name[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="nisn"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="nisn">
                      NISN <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Input
                        type="number"
                        id="nisn"
                        placeholder="123xxx"
                        {...field}
                      />
                      {formError?.nisn ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.nisn[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="agama"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="agama">
                      Agama <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Input
                        type="text"
                        id="agama"
                        placeholder="Islam"
                        {...field}
                      />
                      {formError?.agama ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.agama[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="no_telpon"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="no-telpon">
                      No. Telepon
                      <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Input
                        type="number"
                        id="no-telpon"
                        placeholder="0823xxx"
                        {...field}
                      />
                      {formError?.no_telpon ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.no_telpon[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="jenis_kelamin"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="jenis-kelamin">
                      Jenis Kelamin
                      <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Select
                        id="jenis-kelamin"
                        defaultValue={field.value}
                        onValueChange={field.onChange}
                      >
                        <SelectTrigger className="w-full">
                          <SelectValue placeholder="Pilih Jenis Kelamin" />
                        </SelectTrigger>
                        <SelectContent>
                          <SelectGroup>
                            <SelectLabel>Jenis Kelamin</SelectLabel>
                            <SelectItem value="laki-laki">Laki-Laki</SelectItem>
                            <SelectItem value="perempuan">Perempuan</SelectItem>
                          </SelectGroup>
                        </SelectContent>
                      </Select>
                      {formError?.jenis_kelamin ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.jenis_kelamin[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="tgl_lahir"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="tanggal-lahir">
                      Tanggal Lahir
                      <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Popover>
                        <PopoverTrigger asChild>
                          <Button
                            variant={"outline"}
                            className={cn(
                              "w-[868px] justify-start text-left font-normal",
                              !field.value && "text-muted-foreground",
                            )}
                          >
                            <CalendarIcon className="mr-2 h-4 w-4" />
                            {field.value ? (
                              format(field.value, "PPP")
                            ) : (
                              <span>Pick a date</span>
                            )}
                          </Button>
                        </PopoverTrigger>
                        <PopoverContent className="w-auto p-0">
                          <Calendar
                            mode="single"
                            selected={field.value}
                            onSelect={field.onChange}
                            initialFocus
                          />
                        </PopoverContent>
                      </Popover>
                      {formError?.tgl_lahir ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.tgl_lahir[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="tempat_lahir"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="tempat-lahir">
                      Tempat Lahir
                      <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Input
                        type="text"
                        id="tempat-lahir"
                        placeholder="Pekanbaru, Indonesia"
                        {...field}
                      />
                      {formError?.tempat_lahir ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.tempat_lahir[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="alamat"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="alamat">
                      Alamat
                      <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Textarea
                        type="text"
                        id="alamat"
                        placeholder="Jl. Sudirman No.xx"
                        {...field}
                      />
                      {formError?.alamat ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.alamat[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />

            <FormField
              control={form.control}
              name="foto"
              render={({ field }) => (
                <FormItem className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
                  <div className="sm:col-span-3">
                    <FormLabel htmlFor="foto">
                      Foto
                      <span className="text-red-500 font-bold">*</span>
                    </FormLabel>
                  </div>
                  <FormControl>
                    <div className="sm:col-span-9">
                      <Input
                        id="foto"
                        type="file"
                        accept=".jpg,.png,.jpeg"
                        onChange={(e) =>
                          field.onChange(
                            e.target.files ? e.target.files[0] : null,
                          )
                        }
                      />
                      {formError?.foto ? (
                        <p className="mt-2 ml-1 text-xs text-red-500">
                          {formError.foto[0]}
                        </p>
                      ) : (
                        ""
                      )}
                    </div>
                  </FormControl>
                </FormItem>
              )}
            />
            <div className="grid sm:grid-cols-12 gap-2 sm:gap-6 items-center mb-2">
              <div className="col-span-3">
                <Label htmlFor="name">Foto Sebelumnya</Label>
              </div>

              <div className="sm:col-span-9">
                <div className="flex items-center gap-5">
                  <img
                    src={asset(detailSiswa?.foto)}
                    alt=""
                    className="w-[230px] h-[200px] rounded-full"
                  />
                </div>
              </div>
            </div>

            <div className="mt-7 flex justify-end gap-x-2">
              <Link to="/siswa">
                <Button variant="outline">Kembali</Button>
              </Link>
              <Button type="submit" disabled={formLoading}>
                {formLoading ? "Loading" : "Submit"}
              </Button>
            </div>
          </form>
        </Form>
      </CardContent>
    </Card>
  );
}

export default SiswaEdit;

Validation

function update(): array
{
    return [
        'nama' => 'required|string|min:2',
        'nisn' => 'required|string|min:2',
        'jenis_kelamin' => 'required|in:laki-laki,perempuan',
        'agama' => 'required|string|min:2',
        'alamat' => 'required|string|min:2',
        'no_telpon' => 'required|string|max:13',
        'foto' => 'mimes:jpg,jpeg,png',
        'tempat_lahir' => 'required|string|min:2',
        'tgl_lahir' => 'required|date',
    ];
}

Controller Laravel

public function update(SiswaRequest $request, Siswa $siswa): JsonResponse
{
    $fotosiswa = null;

    if ($request->hasFile('foto')) {
        Storage::disk('public')->delete($siswa->foto);
        $fotosiswa = $request->file('foto')->store('foto/siswa', 'public');
    }
    $siswa->update([
        'name' => $request->name,
        'nisn' => $request->nisn,
        'jenis_kelamin' => $request->jenis_kelamin,
        'agama' => $request->agama,
        'alamat' => $request->alamat,
        'no_telpon' => $request->no_telpon,
        'foto' => $fotosiswa ? $fotosiswa : $siswa->foto,
        'tempat_lahir' => $request->tempat_lahir,
        'tgl_lahir' => $request->tgl_lahir
    ]);

    if ($request->filled('roles')) {
        $siswa->syncRoles([$request->input('roles')]);
    }

    return response()->json($siswa);
}

The error after submit :
Validation Message : Failed to Upload

I tried to comment on validate image and it works properly


Solution

  • I already know what's happening. I tried to use the PATCH method for updating the data, but when I try using the POST method, it works fine.