I have an issue which I’m trying to solve for some time:
how can I trigger this fetch: const { data: warrantyInfo } = warrantyApi.useGetWarrantyInfoQuery(materials[0].serialNumber);
only when I type something on serialNumber
inputs by using setTimeout(),1000
?
Currently is triggering on page load and on every type that the user does in the serialNumber
input.
I’ve tried some ways but doesn’t work because this is a hook and I can’t use it into another function or useEffect
for example.
import { useState } from 'react';
import { toast } from 'react-toastify';
import { styled } from '@mui/material/styles';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import IconButton from '@mui/material/IconButton';
import { isBefore, parseISO } from 'date-fns';
import { ArrowLeft, ArrowRight, ArrowRightLong, CheckOutline, Close, Table, Trash, Upload } from 'assets/icons';
import { Checkbox, FormControlLabel, Grid, InputAdornment, MenuItem, Select, TextField } from '@mui/material';
const ModalAddC = (props) => {
const { open, handleClose } = props;
const { t } = useTranslation();
const [failedSteps] = useState<Array<number>>([]);
const [activeStep, setActiveStep] = useState<number>(0);
const [fiveYearWarranty, setFiveYearWarranty] = useState<boolean>(false);
const [detailedInformation, setDetailedInformation] = useState<string>('' as string);
const [returnReason, setReturnReason] = useState<string>('' as string);
const [materialInputType, setMaterialInputType] = useState<boolean>(true);
const [materials, setMaterials] = useState<Array<{ serialNumber: string; quantity: number; materialDesignation: string }>>([
{ serialNumber: '', quantity: 0, materialDesignation: '' },
]);
const [fileIsUploaded, setFileIsUploaded] = useState<boolean>(false);
const [commissionName, setCommissionName] = useState<string>('' as string);
const [billNumber, setBillNumber] = useState<string>('' as string);
const { data: warrantyInfo } = warrantyApi.useGetWarrantyInfoQuery(materials[0].serialNumber);
const handleNavigation = (step: number) => {
if (step < activeStep) {
setActiveStep(step);
}
};
const handleNext = () => {
let errors = 0;
if(!commissionName.length){
errors = displayError(t('Commission name is required.'));
}
if(!billNumber.length){
errors = displayError(t('Bill number is required.'));
}
if(!detailedInformation.length){
errors = displayError(t('Detailed information is required.'));
}
if(!returnReason.length){
errors = displayError(t('Return reason is required.'));
}
if(errors){
return;
}
setActiveStep((current) => current + 1);
};
const handleSave = () => {
let errors = 0;
materials.forEach((material) => {
if(!material.serialNumber.length){
errors = displayError(t('Serial Number is required.'));
}
if(!material.quantity){
errors = displayError(t('Quantity is required.'));
}
if(!material.materialDesignation.length){
errors = displayError(t('Material Designation is required.'));
}
})
if(errors){
return;
}
handleClose();
};
const handleDecreaseAmount = (index: number) => {
if (materials[index].quantity > 0) {
setMaterials((current) => {
const newMaterials = [...current];
newMaterials[index].quantity = newMaterials[index].quantity - 1;
return newMaterials;
});
}
};
const handleIncreaseAmount = (index: number) => {
setMaterials((current) => {
const newMaterials = [...current];
newMaterials[index].quantity = newMaterials[index].quantity + 1;
return newMaterials;
});
};
const handleQuantityChange = (index: number, newValue: number) => {
setMaterials((current) => {
const newMaterials = [...current];
newMaterials[index].quantity = newValue;
return newMaterials;
});
};
const handleSerialNumberChange = (index: number, newValue: string) => {
setMaterials((current) => {
const newMaterials = [...current];
newMaterials[index].serialNumber = newValue;
return newMaterials;
});
};
const handleMaterialDesignationChange = (index: number, newValue: string) => {
setMaterials((current) => {
const newMaterials = [...current];
newMaterials[index].materialDesignation = newValue;
return newMaterials;
});
};
const handleCreateNewMaterial = () => {
setMaterials((current) => [...current, { serialNumber: '', quantity: 0, materialDesignation: '' }]);
};
const handleDeleteMaterial = (index: number) => {
if (materials.length > 1) {
setMaterials((current) => {
const newMaterials = [...current];
newMaterials.splice(index, 1);
return newMaterials;
});
}
};
const handleSubmitCommission = () => {
const isWarrantyInfoInPast = warrantyInfo
? isBefore(parseISO(warrantyInfo?.end_date), new Date())
: false;
if(isWarrantyInfoInPast) {
toast.error(t('According to our record, the warranty has expired! Feel free to contact us with any questions!'));
}
handleSave();
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const readUploadFile = (e: any) => {
e.preventDefault();
if (e.target.files) {
const reader = new FileReader();
reader.onload = (e) => {
const data = e.target!.result;
const workbook = xlsx.read(data, { type: 'array' });
const sheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[sheetName];
const json = xlsx.utils.sheet_to_json(worksheet);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newArray = json.map((item: any) => ({
serialNumber: item['Material Nummer'],
quantity: item['Anazhl'],
materialDesignation: '',
}));
setMaterials(newArray);
setFileIsUploaded(true);
};
reader.readAsArrayBuffer(e.target.files[0]);
}
};
const openFileInput = () => {
document.getElementById('hiddenFileInput')!.click();
};
return (
<BootstrapDialog onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} className="rp-modal-add-commission">
<BootstrapDialogTitle id="customized-dialog-title" onClose={handleClose}>
<ProgressBar
steps={[t('Commission'), t('Material')]}
failedSteps={failedSteps}
activeStep={activeStep}
setActiveStep={(step) => handleNavigation(step)}
/>
</BootstrapDialogTitle>
<DialogContent dividers>
<div className="slider" style={{ transform: `translateX(calc(-${activeStep * 80}vw + ${activeStep * 6}px))` }}>
<div className="step">
<span className="section-title">{t('Add commission')}</span>
<Grid container spacing={0}>
<Grid item className="custom-grid" xs={6} md={6} lg={6}>
<br></br>
<span className="input-label">{t('Commission name').toString() + '*'}</span>
<TextField
id="outlined-textfield"
label=""
variant="outlined"
className="custom-text-field"
InputLabelProps={{ shrink: false }}
value={commissionName}
onChange={(e) => setCommissionName(e.target.value)}
error={!commissionName.length}
helperText={!commissionName.length && t('Commission name is required.').toString()}
/>
<FormControlLabel
control={
<Checkbox
color="secondary"
checked={fiveYearWarranty}
onChange={() => setFiveYearWarranty(!fiveYearWarranty)}
/>
}
label={`${t('The product has an extended warranty')}(${t('5-years warranty')})`}
/>
<Grid container spacing={0}>
<Grid item className="custom-inside-grid" xs={6} md={6} lg={6}>
<span className="input-label">{t('Serial Number').toString() + (fiveYearWarranty ? '*' : '')}</span>
<TextField
id="outlined-textfield"
label=""
variant="outlined"
className="custom-text-field"
InputLabelProps={{ shrink: false }}
/>
</Grid>
<Grid item className="custom-inside-grid" xs={6} md={6} lg={6}>
<span className="input-label">{t('Bill Number').toString() + '*'}</span>
<TextField
id="outlined-textfield"
label=""
variant="outlined"
className="custom-text-field"
InputLabelProps={{ shrink: false }}
value={billNumber}
onChange={(e) => setBillNumber(e.target.value)}
error={!billNumber.length}
helperText={!billNumber.length && t('Bill number is required.').toString()}
/>
</Grid>
</Grid>
<FormControlLabel control={<Checkbox color="secondary" />} label={t('Replacement of this position is desired')} />
<span className="input-label">{t('Comment').toString()}</span>
<TextField
id="outlined-textfield"
label=""
variant="outlined"
className="custom-text-field"
InputLabelProps={{ shrink: false }}
multiline
rows={2}
/>
</Grid>
<Grid item className="custom-grid" xs={6} md={6} lg={6}>
<br></br>
<span className="input-label">{t('Detailed information').toString() + '*'}</span>
<Select
value={detailedInformation}
label=""
onChange={(e) => setDetailedInformation(e.target.value as string)}
color="secondary"
error={!detailedInformation.length}
displayEmpty>
<MenuItem value="" disabled>
<span className="placeholder">{t('Please select').toString()}</span>
</MenuItem>
<MenuItem value={'1'}>
<span className="menu-option">{t('Too many products')}</span>
</MenuItem>
<MenuItem value={'2'}>
<span className="menu-option">{t('Incorrect order')}</span>
</MenuItem>
<MenuItem value={'3'}>
<span className="menu-option">{t('Customer cancelled order')}</span>
</MenuItem>
<MenuItem value={'4'}>
<span className="menu-option">{t('Inventory cleanup')}</span>
</MenuItem>
<MenuItem value={'5'}>
<span className="menu-option">{t('Delivery time')}</span>
</MenuItem>
<MenuItem value={'6'}>
<span className="menu-option">{t('Delivery time too long')}</span>
</MenuItem>
<MenuItem value={'7'}>
<span className="menu-option">{t('Products too expensive / incorrect price')}</span>
</MenuItem>
</Select>
{!detailedInformation.length && <span className='detailed-information-err-text'>{t('Detailed information is required.')}</span>}
<br></br>
<span className="input-label">{t('Reason of return').toString() + '*'}</span>
<Select
value={returnReason}
label=""
onChange={(e) => setReturnReason(e.target.value as string)}
color="secondary"
error={!returnReason.length}
displayEmpty>
<MenuItem value="" disabled>
<span className="placeholder">{t('Please select').toString()}</span>
</MenuItem>
<MenuItem value={'1'}>
<span className="menu-option">{t('Like new, not needed by customer')}</span>
</MenuItem>
<MenuItem value={'2'}>
<span className="menu-option">{t('Defective')}</span>
</MenuItem>
<MenuItem value={'3'}>
<span className="menu-option">{t('Transport damage')}</span>
</MenuItem>
<MenuItem value={'4'}>
<span className="menu-option">{t('Wrong delivery')}</span>
</MenuItem>
<MenuItem value={'5'}>
<span className="menu-option">{t('Spare parts inventory reconciliation upon consultation')}</span>
</MenuItem>
</Select>
{!returnReason.length && <span className='return-reason-err-text'>{t('Return reason is required.')}</span>}
<br></br>
<span className="input-label">{t('Pictures/files for return').toString()}</span>
<div className="upload-container">
<Upload className="upload-icon" />
<span className="upload-main-text">{t('Drag or select files here')}</span>
<span className="upload-secondary-text">{t('max. 10MB in PDF, JPG, PNG format')}</span>
</div>
</Grid>
</Grid>
</div>
<div className="step">
<span className="section-title">{t('Add Material')}</span>
<Grid container spacing={0}>
<Grid item className="custom-grid" xs={12} md={12} lg={12}>
<Toggle
items={[<>{t('Manual input')}</>, <>{t('Excel upload')}</>]}
setToggle={(e) => {
setMaterialInputType(e);
setMaterials([{ serialNumber: '', quantity: 0, materialDesignation: '' }]);
setFileIsUploaded(false);
}}
value={materialInputType}
/>
</Grid>
</Grid>
{materialInputType ? (
<>
{materials.map((material, index) => (
<Grid key={'materrial-' + index} container spacing={0}>
<Grid item className="custom-grid" xs={4} md={4} lg={4}>
<span className="input-label">{t('Serial Number').toString()}*</span>
<TextField
id="outlined-textfield"
label=""
variant="outlined"
className="custom-text-field"
InputLabelProps={{ shrink: false }}
value={material.serialNumber}
onChange={(e) => handleSerialNumberChange(index, e.target.value)}
error={!material.serialNumber.length}
helperText={!material.serialNumber.length && t('Serial Number is required.').toString()}
/>
</Grid>
<Grid item className="custom-grid" xs={3} md={3} lg={3}>
<span className="input-label">{t('Quantity').toString() + '*'}</span>
<TextField
type="number"
value={material.quantity}
error={!material.quantity}
// onChange={(e) => setPackagestValue(parseInt(e.target.value))}
onChange={(e) => handleQuantityChange(index, parseInt(e.target.value))}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<IconButton onClick={() => handleDecreaseAmount(index)}>
<ArrowLeft />
</IconButton>
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => handleIncreaseAmount(index)}>
<ArrowRight />
</IconButton>
</InputAdornment>
),
}}
/>
{!material.quantity && <span className='material-qty-err-text'>{t('Quantity is required.')}</span>}
</Grid>
<Grid item className="custom-grid" xs={4} md={4} lg={4}>
<span className="input-label">{t('Material designation').toString()}*</span>
<TextField
id="outlined-textfield"
label=""
variant="outlined"
className="custom-text-field"
InputLabelProps={{ shrink: false }}
value={material.materialDesignation}
onChange={(e) => handleMaterialDesignationChange(index, e.target.value)}
error={!material.materialDesignation.length}
helperText={!material.materialDesignation.length && t('Material Designation is required.').toString()}
/>
</Grid>
<Grid item className="custom-grid justify-center" xs={1} md={1} lg={1}>
<Trash className="cursor-pointer" onClick={() => handleDeleteMaterial(index)} />
</Grid>
</Grid>
))}
<Grid container>
<Grid item xs={12} md={12} lg={12}>
<button className="add-material-button" onClick={() => handleCreateNewMaterial()}>
{t('Add more materials')}
</button>
</Grid>
</Grid>
</>
) : (
<>
{!fileIsUploaded ? (
<>
{materials.map((material, index) => (
<Grid key={'materrial-' + index} container spacing={0}>
<Grid item className="custom-grid" xs={4} md={4} lg={4}>
<span className="input-label">{t('Serial Number').toString()}*</span>
<TextField
id="outlined-textfield"
label=""
variant="outlined"
className="custom-text-field"
InputLabelProps={{ shrink: false }}
value={material.serialNumber}
onChange={(e) => handleSerialNumberChange(index, e.target.value)}
/>
</Grid>
<Grid item className="custom-grid" xs={3} md={3} lg={3}>
<span className="input-label">{t('Quantity').toString() + '*'}</span>
<TextField
type="number"
value={material.quantity}
// onChange={(e) => setPackagestValue(parseInt(e.target.value))}
onChange={(e) => handleQuantityChange(index, parseInt(e.target.value))}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<IconButton onClick={() => handleDecreaseAmount(index)}>
<ArrowLeft />
</IconButton>
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => handleIncreaseAmount(index)}>
<ArrowRight />
</IconButton>
</InputAdornment>
),
}}
/>
</Grid>
<Grid item className="custom-grid" xs={4} md={4} lg={4}>
<span className="input-label">{t('Material designation').toString()}*</span>
<TextField
id="outlined-textfield"
label=""
variant="outlined"
className="custom-text-field"
InputLabelProps={{ shrink: false }}
value={material.materialDesignation}
onChange={(e) => handleMaterialDesignationChange(index, e.target.value)}
/>
</Grid>
<Grid item className="custom-grid justify-center" xs={1} md={1} lg={1}>
<Trash className="cursor-pointer" onClick={() => handleDeleteMaterial(index)} />
</Grid>
</Grid>
))}
<Grid container>
<Grid item xs={12} md={12} lg={12}>
<button className="add-material-button" onClick={() => handleCreateNewMaterial()}>
{t('Add more materials')}
</button>
<button
className="add-material-button"
onClick={() => {
setFileIsUploaded(false);
setMaterials([{ serialNumber: '', quantity: 0, materialDesignation: '' }]);
}}>
{t('Replace excel file')}
</button>
</Grid>
</Grid>
</>
) : (
<>
<Grid container spacing={0}>
<Grid item className="custom-grid" xs={4} md={4} lg={4}>
<Table />
<br></br>
<span className="excel-upload-description">
{t('Create your Excel table in the form shown here, or simply use our Excel template.')}
</span>
<br></br>
<button className="add-material-button download-excel-template-button">
{t('Download Excel template')}
</button>
</Grid>
<Grid item xs={8} md={8} lg={8}>
<div className="upload-container">
<Upload className="upload-icon" onClick={openFileInput} />
<span className="upload-main-text">{t('Drag or select files here')}</span>
<span className="upload-secondary-text">{t('max. 10MB in xls, csv format')}</span>
</div>
<input
type="file"
id="hiddenFileInput"
style={{ display: 'none' }}
onChange={readUploadFile}
accept=".xls, .csv, .xlsx"
/>
</Grid>
</Grid>
</>
)}
</>
)}
<Grid container spacing={0}>
<Grid item className="custom-grid" xs={12} md={12} lg={12}></Grid>
</Grid>
</div>
</div>
</DialogContent>
<DialogActions>
{activeStep === 0 && (
<>
<button className="next-step-btn-negative" onClick={() => handleNext()}>
{t('Create and add material later')}
</button>
<button className="next-step-btn" onClick={() => handleNext()}>
{t('Next step')}
<ArrowRightLong className="icon" />
</button>
</>
)}
{activeStep === 1 && (
<button className="next-step-btn" onClick={() => handleSubmitCommission()}>
<CheckOutline className="icon-before" />
{t('Create commission')}
</button>
)}
</DialogActions>
</BootstrapDialog>
);
};
export default ModalAddC;
2
Answers
Try this technique.
The regular query hooks are intended to be called each time the component renders, e.g. from the state update, and the argument is passed to the underlying query endpoint. If you are wanting a bit more control over when the query is triggered then I’d suggest using the lazy query hook and debounce the returned trigger function.
useLazyQuery
Example implementation:
Use a
useEffect
hook to call a debouncedtrigger
function.You can import a debounce Higher Order Function from a library like lodash
You can implement your own simple
debounce
function