I have parent and child components, and I pass a function from the parent to the child component as a prop. I call this prop function inside useEffect in the child component. ESLint warns me to add that function to the dependency array. However, when I add the prop function to the dependency array, it creates an infinite loop. I also tried using useCallback, but the ESLint warning persists, and adding the prop function to the dependency array still results in an infinite loop. While I can disable the warning, I want to understand the correct way to implement this scenario. Below is the useEffect code snippet, and I will upload both the parent and child code. Thank You
const ImageUploaderList: React.FC<Props> = ({ handleFileChange }) => {
const [images, setImages] = useState<ImageSet>(initialState)
useEffect(() => {
handleFileChange(getNonNullFiles(images))
}, [images])
Warning – React Hook useEffect has a missing dependency: ‘handleFileChange’. Either include it or remove the dependency array. If ‘handleFileChange’ changes too often, find the parent component that defines it and wrap that definition in useCallback.eslintreact-hooks/exhaustive-deps
parent component
import * as Yup from "yup";
import styles from './Create.Products.module.css'
import { Formik, Form, Field, ErrorMessage, FormikHelpers, FormikProps } from "formik";
import useCreateProduct from "../../../../hooks/products/useCreateProduct";
import { ProductFormInput } from "../../../../models/Product";
import ImageUploaderList from "./components/ImageUploaderList";
import { memo } from "react";
const CreateProduct = () => {
const { createProduct } = useCreateProduct()
const initialValues: ProductFormInput = {
description: "",
title: "",
images: [],
price: 0
};
const validationSchema = Yup.object({
title: Yup.string().required("Title is required"),
description: Yup.string().required("Description is required"),
images: Yup
.mixed()
.test("fileSize", "File Size is too large", (value: any) => {
if (value && value?.length > 0) {
for (let i = 0; i < value.length; i++) {
if (value[i].size > 5242880) {
return false;
}
}
}
return true;
})
.test("fileType", "Unsupported File Format", (value: any) => {
if (value && value.length > 0) {
for (let i = 0; i < value.length; i++) {
if (value[i].type !== "image/png" && value[i].type !== "image/jpg" && value[i].type !== "image/jpeg") {
return false;
}
}
}
return true;
}
), price: Yup.number()
.moreThan(0, 'Price must be greater than 0')
.lessThan(999999, 'Price must be less than 999999')
.required('Price is required'),
});
const onSubmit = async (values: ProductFormInput, onSubmitProps: FormikHelpers<ProductFormInput>) => {
try {
console.log(values);
//updateCurrentUser(values);
await createProduct(values)
} catch (error) {
console.log(error);
}
};
const handleImageChange = (formik: FormikProps<ProductFormInput>) => {
return (files: File[]) => {
formik.setFieldValue("images", files)
}
}
return (
<div className={`${styles.container}`}>
<div className="container d-flex justify-content-center">
<div className={`${styles.innerContainer}`}>
<h1 className={`${styles.title}`}>Add Product</h1>
<div className={`${styles.formContainer}`}>
<Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={onSubmit} enableReinitialize>
{(formik) => {
return (
<Form>
<div>
<div className={`${styles.inputContainer}`}>
<label htmlFor="email" className="">
Title
</label>
<Field type="text" id="email" name="title" placeholder="Enter your title here" className={formik.errors.title ? styles.error : ""} />
<ErrorMessage name="title">{(errorMsg) => <div className={`${styles.errorText}`}>{errorMsg}</div>}</ErrorMessage>
</div>
<div className={`${styles.inputContainer}`}>
<label htmlFor="description" className="">
Description
</label>
<Field type="text" as="textarea" rows={5} id="description" name="description" placeholder="Enter your email" className={formik.errors.title ? styles.error : ""} />
<ErrorMessage name="description">{(errorMsg) => <div className={`${styles.errorText}`}>{errorMsg}</div>}</ErrorMessage>
</div>
<div className={`${styles.inputContainer}`}>
<label htmlFor="price" className="">
Price
</label>
<Field type="text" id="price" name="price" placeholder="Enter price here" className={formik.errors.price ? styles.error : ""} />
<ErrorMessage name="price">{(errorMsg) => <div className={`${styles.errorText}`}>{errorMsg}</div>}</ErrorMessage>
</div>
<div className={`${styles.inputContainer}`}>
<label htmlFor="images">Photos</label>
<input
id="images"
name="images"
type="file"
multiple
onChange={(event) => {
formik.setFieldValue("images", event.currentTarget.files); console.log(formik.values.images);
}}
/>
<ImageUploaderList handleFileChange={handleImageChange(formik)} initialImages={formik.values.images} />
<ErrorMessage name="images">{(errorMsg) => <div className={`${styles.errorText}`}>{errorMsg}</div>}</ErrorMessage>
</div>
</div>
<div className={`${styles.buttonContainer}`}>
<button type="submit">Save</button>
</div>
</Form>)
}}
</Formik>
</div>
</div>
</div>
</div>
)
}
export default memo(CreateProduct)
child component
import React, { memo, useEffect, useState } from 'react';
import ImageUploader from './ImageUploader';
import styles from './ImageUploaderList.module.css';
interface Props {
initialImages: File[] | null
handleFileChange: (files: File[]) => void
}
interface ImageState {
file: File | null;
}
interface ImageSet {
image1: ImageState;
image2: ImageState;
image3: ImageState;
image4: ImageState;
image5: ImageState;
}
function getNonNullFiles(imageSet: ImageSet): File[] {
const nonNullFiles: File[] = [];
// Iterate through each property of ImageSet
for (const key in imageSet) {
if (imageSet.hasOwnProperty(key)) {
const imageState = imageSet[key as keyof ImageSet];
// Check if the file property is not null
if (imageState.file !== null) {
nonNullFiles.push(imageState.file);
}
}
}
return nonNullFiles;
}
const initialState: ImageSet = {
image1: { file: null },
image2: { file: null },
image3: { file: null },
image4: { file: null },
image5: { file: null }
}
const ImageUploaderList: React.FC<Props> = ({ handleFileChange }) => {
const [images, setImages] = useState<ImageSet>(initialState)
useEffect(() => {
handleFileChange(getNonNullFiles(images))
}, [images])
const handleSetImage = (imageKey: string) => {
return (image: File | null) => {
setImages(prevState => ({ ...prevState, [imageKey]: { file: image } }))
}
}
return (
<div className={`${styles.container} row gy-3`}>
<div className="col-6 col-md-4 col-lg-3">
<ImageUploader image={images['image1'].file} setImage={handleSetImage('image1')} />
</div>
<div className="col-6 col-md-4 col-lg-3">
<ImageUploader image={images['image2'].file} setImage={handleSetImage('image2')} />
</div>
<div className="col-6 col-md-4 col-lg-3">
<ImageUploader image={images['image3'].file} setImage={handleSetImage('image3')} />
</div>
<div className="col-6 col-md-4 col-lg-3">
<ImageUploader image={images['image4'].file} setImage={handleSetImage('image4')} />
</div>
<div className="col-6 col-md-4 col-lg-3">
<ImageUploader image={images['image5'].file} setImage={handleSetImage('image5')} />
</div>
</div>
);
};
export default memo(ImageUploaderList);
Lochana Test is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.