Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Tiskiel's avatar

How can I use haseErrors from useForm in a function ?

Hello everyone,

I'm trying to use the hasErrors from the useForm of Inertia.js to apply a condition to close my sheet (I'm using Shadcn for the components).

But as I can see, useForm are asynchronous and I can't use hasErrors directly in my handleSubmit function.

I was thinking of using useEffect but I didn't see how to do that.

Here is my code :

import MarkerSummaryTable from '@/Components/MarkerSummaryTable';
import { IMarkerSheetState, useMarkerSheetContext } from '@/Sheets/MarkerSheet/useMarkerSheetContext';
import { MarkerData, OperationData, ValidateMarkerFormRequestData } from '@/types/generated';
import { useForm } from '@inertiajs/react';
import MarkerOperationSelector from '@/Sheets/MarkerSheet/Components/MarkerOperationSelector';
import { FormEvent, useEffect, useState } from 'react';
import { Button } from '@/Components/UI/button';
import FormGroup from '@/Components/FormGroup';
import TextInput from '@/Components/TextInput';
import { useLocations } from '@/hooks/useLocations';
import SelectInput from '@/Components/SelectInput';
import { useMarkerOperations } from '@/hooks/useMarkerOperations';
import SplitButton from '@/Components/SplitButton';

interface IMarkerSheetValidationTab {
  marker: MarkerData;
  state: IMarkerSheetState;
  reload: (id: number) => void;
}

type ITrackerNumberByQuantity = {
  quantity: number;
  tracking_numbers: string[];
};

export function MarkerSheetValidationTab({ marker, state, reload }: IMarkerSheetValidationTab) {
  const { close } = useMarkerSheetContext();
  const { data: locations, processing: processingLocations } = useLocations();
  const { data: operations, processing: processingMarkerOperations } = useMarkerOperations(marker.id);
  const processing = processingLocations || processingMarkerOperations;
  const [operation, setOperation] = useState<OperationData | null>(null);

  const validationForm = useForm<ValidateMarkerFormRequestData>({
    marker_id: marker.id,
    location_id: null,
    operation_id: state.operationId ?? -1,
    quantity: 1,
    tracking_numbers: [],
  });

  useEffect(() => {
    const currentOperation = operations.find(operation => operation.id === validationForm.data.operation_id) ?? null;

    setOperation(currentOperation);

    if (currentOperation) {
      validationForm.setData('quantity', currentOperation?.processing_markers_count ?? 1);
    }
  }, [validationForm.data.operation_id, operations]);

  const saveResponseToLocalStorage = (response: string) => {
    localStorage.setItem('continueAfterValidation', response);
  };

  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    validationForm.post(e.currentTarget.action);

    if (localStorage.getItem('continueAfterValidation') === 'true') {
      validationForm.reset();
      reload(marker.id);
      saveResponseToLocalStorage('false');
    }

    if (validationForm.hasErrors) {
      close();
    }
  };

  return (
    <div className="flex h-full w-full flex-col gap-2">
      <div className={'px-1'}>
        <MarkerSummaryTable marker={marker} />
      </div>
      {processing ? (
        <div className={'flex flex-1 items-center justify-center'}>Chargement...</div>
      ) : (
        <>
          <div className={'px-1'}>
            <MarkerOperationSelector
              operations={operations ?? []}
              value={validationForm.data.operation_id}
              onValueChange={operationId => {
                validationForm.setData({
                  ...validationForm.data,
                  operation_id: operationId,
                });
              }}
            />
          </div>
          <form
            id="form-validation"
            action={route('workflow.markers.validation')}
            method="POST"
            onSubmit={handleSubmit}
            className={'flex flex-col gap-4 px-1'}
          >
            <div className="grid grid-cols-12 gap-2">
              <FormGroup label="Quantité" labelProps={{ htmlFor: 'quantity' }} className="col-span-3">
                <TextInput
                  type="number"
                  name="quantity"
                  id="quantity"
                  min="1"
                  max={operation?.processing_markers_count ?? 0}
                  className="block w-full"
                  value={validationForm.data.quantity}
                  onChange={e => validationForm.setData('quantity', e.target.valueAsNumber)}
                />
              </FormGroup>
              <FormGroup label="Numéro de lot" labelProps={{ htmlFor: 'tracking_codes' }} className="col-span-9">
                <TextInput
                  type="text"
                  name="tracking_codes"
                  id="tracking_codes"
                  className="block w-full"
                  value={validationForm.data.tracking_numbers?.join(', ')}
                  onChange={e => validationForm.setData('tracking_numbers', e.target.value.split(', '))}
                />
              </FormGroup>
              {validationForm.errors.tracking_numbers && (
                <div className="col-span-12 text-right text-red-500">{validationForm.errors.tracking_numbers}</div>
              )}
            </div>
            <FormGroup label="Localisation" labelProps={{ htmlFor: 'location_id' }}>
              <SelectInput
                className={'w-full'}
                onChange={e =>
                  validationForm.setData('location_id', e.currentTarget.value ? parseInt(e.currentTarget.value) : null)
                }
                defaultValue={validationForm.data.location_id?.toString()}
              >
                <option value="">Sélectionnez l'endroit où vous déposez le repère</option>
                {locations.map(location => (
                  <option key={location.id} value={location.id.toString()}>
                    {location.label}
                  </option>
                ))}
              </SelectInput>
            </FormGroup>
          </form>
          <div className={'mt-auto flex items-center justify-end gap-4 border-t bg-zinc-50 px-1 py-2'}>
            <SplitButton
              asChild
              menu={[
                {
                  key: 'validate-and-continue',
                  label: 'Valider et continuer',
                  props: {
                    children: (
                      <Button
                        form={'form-validation'}
                        type={'submit'}
                        size={'lg'}
                        processing={validationForm.processing}
                        onClick={() => saveResponseToLocalStorage('true')}
                        variant={'ghost'}
                      >
                        Valider et continuer
                      </Button>
                    ),
                  },
                },
                {
                  key: 'cancel',
                  label: 'Annuler',
                  props: {
                    children: (
                      <Button onClick={() => validationForm.reset()} size={'lg'} variant={'ghost'}>
                        Annuler
                      </Button>
                    ),
                  },
                },
              ]}
            >
              <Button form={'form-validation'} type={'submit'} processing={validationForm.processing} variant="outline">
                Valider
              </Button>
            </SplitButton>
          </div>
        </>
      )}
    </div>
  );
}

Thank you in advanced for your help

0 likes
0 replies

Please or to participate in this conversation.