Wątek przeniesiony 2023-07-31 09:57 z JavaScript przez Riddle.

Jak dodać EventListener w ReactJS dla komponentu funkcyjnego

0

Hej,

wiem ze w React, zeby dodac EventListener do komponentu funkcyjnego musimy to zrobic za pomoca useEffect. Problem w tym ze mam wewnatrz komponentu mam funkcje do ktorej przekazuej plik File i wyswietlam go za pomoca FileReadera. Zeby to zrobic dodaje EventListener na event load ale robie to wewnatrz tej funkcji i zastanawiam sie jak to przepisac zeby bylo zgodnie z ReactJS

let showImagePreviewAsync = (
    file: File,
    resolve: () => void,
    reject: (error: string) => void
) => {
    const previewElement = imagePreviewRef.current!;

    const addImageButton = addImageButtonRef.current;

    hideValidationError();
    const validationResult = validateImage(file);
    switch (validationResult) {
        case ValidationResult.FileTooLarge:
            showValidationError(_t("image_upload.upload_error_file_too_big"));
            reject("file too large");
            return;
        case ValidationResult.InvalidFileType:
            showValidationError(_t("image_upload.upload_error_unsupported_format"));
            reject("invalid filetype");
            return;
    }

    resetImagePreview();

    const reader = new FileReader();
    reader.addEventListener(
        "load",
        () => {
            const _image = new Image();
            _image.className = "hmx1 w-auto";
            _image.title = file.name;
            _image.src = reader.result as string;
            _image.alt = _t("image_upload.uploaded_image_preview_alt");
            previewElement.appendChild(_image);
            previewElement.classList.remove("d-none");
            image = file;
            addImageButton!.disabled = false;
            resolve();
        },
        false
    );
    reader.readAsDataURL(file);
};
0
Kokos123 napisał(a):

Hej, wiem ze w ReactJS, zeby dodac EventListener do komponentu funkcyjnego musimy to zrobic za pomoca useEffect.

To zdanie jest mega dziwne i w sumie nie ma sensu XD W sensie, że jeśli masz komponent funkcyjny, to handlery zdarzeń przypisujesz do niego tak:

const Foo = ({ onClick }) => <div onClick={onClick}>kotek</div>; // komponent funkcyjny
const Bar = () => <Foo onClick={......tutaj nasz handler.....} />; // przekazujemy onClick do <Foo />

Tym niemniej patrząc na kod, to myślę, że mogłeś mieć coś innego na myśli.

Chyba chciałeś napisać coś w stylu  - Jeśli używamy komponentu funkcyjnego w React, i chcemy użyć w nim imperatywnego API takiego jak FileReader, do którego chcemy przypisać listener za pomocą metody addEventListener, to powinniśmy umieścić ten kod w hooku useEffect ?

To wtedy okej.

Zeby to zrobic dodaje EventListener na event load ale robie to wewnatrz tej funkcji i zastanawiam sie jak to przepisac zeby bylo zgodnie z ReactJS

A jak wygląda twój komponent w React, w którym próbujesz tego użyć?

0

W skrocie historia wyglada tak ze mam przycisk ktory pokazuje Modal. Robie to nastepujaco:

type ImageButtonProps = {
	view: EditorView;
	editor: Editor;
};

export function ImageButton({ view, editor }: ImageButtonProps) {
	let [isModalVisible, setIsModalVisible] = useState(false);
	return (
		<>
			<button
				onClick={() => {
					setIsModalVisible(true);
					showModal(document.querySelector("#modal-base") as HTMLElement);
				}}
			>
				Image
			</button>
			{}
			{isModalVisible && (
				<ImageUploadModal
					view={view}
					onClose={() => setIsModalVisible(false)}
					uploadOptions={{ handler: defaultImageUploadHandler }}
					editor={editor}
				/>
			)}
		</>
	);
}


Czyli do Modala przkeazuje funkcje onClose ktora powinna go zamknac. Sam modal wyglada tak jak nizej gdzie zwracany jest przez Portal uploadContainer ktory w szczegolnosci zawiera input uploadField. I to wlasnie ten input jest problematyczny. Jego zadaniem jest otworzyc okno do wyboru pliku i w momencie w ktorym go klikam, ma on zuploadowac plik i wyswietlic jego preview. Robie to poprzez dodanie event listenera na uploadField poprzez useEffect nastepujaco

		uploadFieldRef.current?.addEventListener("change", () => {
			handleFileSelection(view);
		});

Odpala sie on prawidlowo ale z jakiegos powodu dzieje sie to dwukrotnie :( Wiec dwa razy wykonywana jest metoda handleFileSelection. Wydawalo mi sie ze problem moze byc wlasnie w tym FileReaderze ale wyglada na to ze nie. To co robi funkcja handleFileSelection(view) to wywoluje showImagePreviewAsync i tutaj wlasnie ten FileReader wchodzi w gre ktory wydaje mi sie teraz jest dobrze napisany:

let showImagePreviewAsync = (
		file: File,
		resolve: () => void,
		reject: (error: string) => void
	) => {
		// const previewElement = imagePreviewRef.current!;

		hideValidationError();
		const validationResult = validateImage(file);
		switch (validationResult) {
			case ValidationResult.FileTooLarge:
				showValidationError(_t("image_upload.upload_error_file_too_big"));
				reject("file too large");
				return;
			case ValidationResult.InvalidFileType:
				showValidationError(_t("image_upload.upload_error_unsupported_format"));
				reject("invalid filetype");
				return;
		}

		resetImagePreview();
		console.log("Rendering");

		let reader = new FileReader();
		reader.onloadend = (readerEvent: ProgressEvent<FileReader>) => {
			if (readerEvent?.target?.result) {
				const previewElement = imagePreviewRef.current!;
				const addImageButton = addImageButtonRef.current;
				const _image = new Image();
				_image.className = "hmx1 w-auto";
				_image.title = file.name;
				_image.src = reader.result as string;
				_image.alt = _t("image_upload.uploaded_image_preview_alt");
				previewElement.appendChild(_image);
				previewElement.classList.remove("d-none");
				image = file;
				addImageButton!.disabled = false;
				resolve();
			}
		};
		reader.readAsDataURL(file);
	};

Takze bylbym wdzieczny gdyby ktos zerknal dlaczego ten event sie wywoluje dwa razy. Ponizej pelny kod.

type ImageButtonProps = {
	view: EditorView;
	editor: Editor;
};

export function ImageButton({ view, editor }: ImageButtonProps) {
	let [isModalVisible, setIsModalVisible] = useState(false);
	return (
		<>
			<button
				onClick={() => {
					setIsModalVisible(true);
					showModal(document.querySelector("#modal-base") as HTMLElement);
				}}
			>
				Image
			</button>
			{}
			{isModalVisible && (
				<ImageUploadModal
					view={view}
					onClose={() => setIsModalVisible(false)}
					uploadOptions={{ handler: defaultImageUploadHandler }}
					editor={editor}
				/>
			)}
		</>
	);
}

/**
 * Async image upload callback that is passed the uploaded file and returns a resolvable path to the image
 * @param {File} file The uploaded image file or user entered image url
 * @returns {string} The resolvable path to where the file was uploaded
 */
type ImageUploadHandlerCallback = (file: File) => Promise<string>;

/**
 * Image upload options
 */
export interface ImageUploadOptions {
	/**
	 * A function handling file uploads. Will receive the file to upload
	 * as the `file` parameter and needs to return a resolved promise with the URL of the uploaded file
	 */
	handler?: ImageUploadHandlerCallback;
	/**
	 * The html to insert into the image uploader to designate the image storage provider
	 * NOTE: this is injected as-is and can potentially be a XSS hazard!
	 */
	brandingHtml?: string;
	/**
	 * The html to insert into the image uploader to alert users of the uploaded image content policy
	 * NOTE: this is injected as-is and can potentially be a XSS hazard!
	 */
	contentPolicyHtml?: string;
	/**
	 * If provided, will insert the html into a warning notice at the top of the image uploader
	 * NOTE: this is injected as-is and can potentially be a XSS hazard!
	 */
	warningNoticeHtml?: string;
	/**
	 * If true, wraps all images in links that point to the uploaded image url
	 */
	wrapImagesInLinks?: boolean;
	/**
	 * If true, all uploaded images will embedded as links to the image, rather than the image itself
	 * NOTE: this is only supported for images that are uploaded via the image uploader
	 */
	embedImagesAsLinks?: boolean;
	/**
	 * If true, allow users to add images via an external url
	 */
	allowExternalUrls?: boolean;
}

/**
 * Default image upload callback that posts to `/image/upload`,
 * expecting a json response like `{ UploadedImage: "https://www.example.com/path/to/file" }`
 * and returns `UploadedImage`'s value
 * @param file The file to upload
 */
export async function defaultImageUploadHandler(file: File): Promise<string> {
	const formData = new FormData();
	// formData.append("file", file);
	formData.append("key", "422a4e8bc4b4a8a67ee14c6ad3c0d69e");
	formData.append("image", file);

	const response = await fetch("https://api.imgbb.com/1/upload", {
		method: "POST",
		cache: "no-cache",
		body: formData,
	});

	if (!response.ok) {
		throw Error(
			`Failed to upload image: ${response.status} - ${response.statusText}`
		);
	}

	const json = await response.json(); //as { UploadedImage: string };
	return json.data.url;
}

enum ValidationResult {
	Ok,
	FileTooLarge,
	InvalidFileType,
}

let hide = () => {
	hideModal(document.querySelector("#modal-base") as HTMLElement);
	return true;
};

let show = () => {
	showModal(document.querySelector("#modal-base") as HTMLElement);
	return true;
};

type Props = {
	view: EditorView;
	uploadOptions: ImageUploadOptions;
	// validateLink: (str: string) => boolean;
	onClose: any;
	editor: Editor;
};

export function ImageUploadModal({
	view,
	uploadOptions,
	onClose,
	editor,
}: Props) {
	// let uploadOptions: ImageUploadOptions | null;
	let image: File | null;
	// let isVisible: boolean;
	// TODO do external image urls need to support a different link validator?
	// let validateLink: (str: string) => boolean;
	// let addTransactionDispatcher: addTransactionDispatcher;

	let uploadFieldRef = useRef<HTMLInputElement>(null);
	let externalUrlRef = useRef<HTMLInputElement>(null);
	let imagePreviewRef = useRef<HTMLDivElement>(null);
	let ctaContainerRef = useRef<HTMLDivElement>(null);
	let addImageButtonRef = useRef<HTMLButtonElement>(null);
	let cancelButtonRef = useRef<HTMLButtonElement>(null);
	let externalUrlInputContainerRef = useRef<HTMLDivElement>(null);
	let warningRef = useRef<HTMLDivElement>(null);
	let externalUrlTriggerRef = useRef<HTMLButtonElement>(null);
	let externalUrlTriggerContainerRef = useRef<HTMLElement>(null);

	let validationElementRef = useRef<HTMLElement>(null);
	let ref = useRef<HTMLDivElement>(null);

	let uploadField = (
		<input
			ref={uploadFieldRef}
			type="file"
			className="js-image-uploader-input v-visible-sr"
			accept="image/*"
			multiple={false}
			id="fileUpload"
		></input>
	);

	let uploadContainer = (
		<div className="s-modal--dialog">
			<div ref={ref} className="mt6 bc-black-400 js-image-uploader">
				<h1>Image</h1>
				<div
					ref={warningRef}
					className="s-notice s-notice__warning m12 mb0 js-warning-notice-html d-none"
					role="status"
				></div>
				<div
					ref={ctaContainerRef}
					className="fs-body2 p12 pb0 js-cta-container"
				>
					<label
						htmlFor={uploadFieldRef.current?.id}
						className="d-inline-flex f:outline-ring s-link js-browse-button"
						aria-controls="image-preview"
					>
						Browse
						{uploadField}
					</label>
					, drag & drop
					<span
						ref={externalUrlTriggerContainerRef}
						className="js-external-url-trigger-container d-none"
					>
						,{" "}
						<button
							ref={externalUrlTriggerRef}
							type="button"
							className="s-btn s-btn__link js-external-url-trigger"
						>
							enter a link
						</button>
					</span>
					, or paste an image{" "}
					<span className="fc-light fs-caption">Max size 2 MiB</span>
				</div>

				<div
					ref={externalUrlInputContainerRef}
					className="js-external-url-input-container p12 d-none"
				>
					<div className="d-flex fd-row ai-center sm:fd-column sm:ai-start">
						<label
							className="d-block s-label ws-nowrap mr4"
							htmlFor="external-url-input"
						>
							External url
						</label>
						<input
							ref={externalUrlRef}
							id="external-url-input"
							type="text"
							className="s-input js-external-url-input"
							placeholder="https://example.com/img.png"
						/>
					</div>
				</div>

				<div
					ref={imagePreviewRef}
					id="image-preview"
					className="js-image-preview wmx100 pt12 px12 d-none"
				></div>
				<aside
					ref={validationElementRef}
					className="s-notice s-notice__warning d-none m8 js-validation-message"
					role="status"
					aria-hidden="true"
				></aside>

				<div className="d-flex jc-space-between ai-center p12 sm:fd-column sm:ai-start sm:g16">
					<div>
						<button
							ref={addImageButtonRef}
							className="s-btn s-btn__primary ws-nowrap mr8 js-add-image"
							type="button"
							disabled
						>
							Add image
						</button>
						<button
							ref={cancelButtonRef}
							className="s-btn ws-nowrap js-cancel-button"
							type="button"
							onClick={onClose}
						>
							Cancel
						</button>
					</div>
					<div className="d-flex fd-column fs-caption fc-black-300 s-anchors s-anchors__muted">
						<div className="js-branding-html">
							{uploadOptions?.brandingHtml}
						</div>
						<div className="js-content-policy-html">
							{uploadOptions?.contentPolicyHtml}
						</div>
					</div>
				</div>
			</div>
		</div>
	);

	useEffect(() => {
		uploadFieldRef.current?.addEventListener("change", () => {
			handleFileSelection(view);
		});

		ref.current?.addEventListener("dragenter", highlightDropArea);
		ref.current?.addEventListener("dragover", highlightDropArea);

		// we need this handler on top of the plugin's handleDrop() to make
		// sure we're handling drop events on the upload container itself properly
		ref.current?.addEventListener("drop", (event: DragEvent) => {
			unhighlightDropArea(event);
			handleDrop(event, view);
		});

		// we need this handler on top of the plugin's handlePaste() to make
		// sure we're handling paste events on the upload container itself properly
		ref.current?.addEventListener("paste", (event: ClipboardEvent) => {
			handlePaste(event, view);
		});

		ref.current?.addEventListener("dragleave", (event: DragEvent) =>
			unhighlightDropArea(event)
		);

		cancelButtonRef.current?.addEventListener("click", () => {
			hide();
		});

		addImageButtonRef.current?.addEventListener("click", (e: Event) => {
			void handleUploadTrigger(e, image!, view);
			onClose();
		});

		if (uploadOptions?.warningNoticeHtml) {
			const warning = warningRef.current!;
			warning.classList.remove("d-none");

			// XSS "safe": this html is passed in via the editor options; it is not our job to sanitize it
			// eslint-disable-next-line no-unsanitized/property
			warning.innerHTML = uploadOptions?.warningNoticeHtml;
		}

		if (uploadOptions.allowExternalUrls) {
			externalUrlTriggerContainerRef.current?.classList.remove("d-none");
			externalUrlTriggerContainerRef.current?.addEventListener("click", () => {
				toggleExternalUrlInput(true);
			});

			externalUrlRef.current?.addEventListener("input", (e) => {
				validateExternalUrl((e.target as HTMLInputElement).value);
			});
		}

		return () => {
			uploadFieldRef.current?.removeEventListener("change", () => {
				handleFileSelection(view);
			});
		};
	});

	async function handleUploadTrigger(
		event: Event,
		file: File,
		view: EditorView
	): Promise<void> {
		const externalUrl = externalUrlRef.current?.value;
		const urlIsValue = externalUrl && validateLink(externalUrl);

		if (!file && !urlIsValue) {
			return;
		}

		let resume: (resume: boolean) => void;
		const resumePromise = new Promise((resolve) => {
			resume = (r) => resolve(r);
		});

		// const canceled = !dispatchEditorEvent(view.dom, "image-upload", {
		// 	file: file || externalUrl,
		// 	resume,
		// });
		let canceled = false;

		if (canceled) {
			const id = {};
			addImagePlaceholder(view, id);
			const resume = await resumePromise;
			removeImagePlaceholder(view, id);

			if (resume) {
				void startImageUpload(view, file || externalUrl);
			}
		} else {
			void startImageUpload(view, file || externalUrl);
		}

		resetUploader();
		hide();
	}

	let handlePaste = (event: ClipboardEvent, view: EditorView): void => {
		resetImagePreview();
		const files = event.clipboardData?.files;
		if (view.state.selection.$from.parent.inlineContent && files?.length) {
			void showImagePreview(files[0]);
		}
	};

	let highlightDropArea = (event: DragEvent): void => {
		ref.current?.classList.add("bs-ring");
		ref.current?.classList.add("bc-blue-300");
		event.preventDefault();
		event.stopPropagation();
	};

	let unhighlightDropArea = (event: DragEvent): void => {
		ref.current?.classList.remove("bs-ring");
		ref.current?.classList.remove("bc-blue-300");
		event.preventDefault();
		event.stopPropagation();
	};

	let resetImagePreview = (): void => {
		imagePreviewRef.current!.innerHTML = "";
		image = null;
		addImageButtonRef.current!.disabled = true;
	};

	let handleDrop = (event: DragEvent, view: EditorView): void => {
		resetImagePreview();
		const files = event.dataTransfer?.files;
		if (view.state.selection.$from.parent.inlineContent && files?.length) {
			void showImagePreview(files[0]);
		}
	};

	let showImagePreview = (file: File): Promise<void> => {
		const promise = new Promise<void>((resolve, reject) =>
			showImagePreviewAsync(file, resolve, reject)
		);

		return promise;
	};

	let showImagePreviewAsync = (
		file: File,
		resolve: () => void,
		reject: (error: string) => void
	) => {
		// const previewElement = imagePreviewRef.current!;

		hideValidationError();
		const validationResult = validateImage(file);
		switch (validationResult) {
			case ValidationResult.FileTooLarge:
				showValidationError(_t("image_upload.upload_error_file_too_big"));
				reject("file too large");
				return;
			case ValidationResult.InvalidFileType:
				showValidationError(_t("image_upload.upload_error_unsupported_format"));
				reject("invalid filetype");
				return;
		}

		resetImagePreview();
		console.log("Rendering");

		let reader = new FileReader();
		reader.onloadend = (readerEvent: ProgressEvent<FileReader>) => {
			if (readerEvent?.target?.result) {
				const previewElement = imagePreviewRef.current!;
				const addImageButton = addImageButtonRef.current;
				const _image = new Image();
				_image.className = "hmx1 w-auto";
				_image.title = file.name;
				_image.src = reader.result as string;
				_image.alt = _t("image_upload.uploaded_image_preview_alt");
				previewElement.appendChild(_image);
				previewElement.classList.remove("d-none");
				image = file;
				addImageButton!.disabled = false;
				resolve();
			}
		};
		reader.readAsDataURL(file);
	};

	let hideValidationError = (): void => {
		const validationElement = validationElementRef.current!;
		validationElement.classList.add("d-none");
		validationElement.classList.remove("s-notice__warning");
		validationElement.classList.remove("s-notice__danger");
		validationElement.innerHTML = "";
	};

	let showValidationError = (errorMessage: string, level = "warning"): void => {
		uploadFieldRef.current!.value = "";
		const validationElement = validationElementRef.current!;

		if (level === "warning") {
			validationElement.classList.remove("s-notice__danger");
			validationElement.classList.add("s-notice__warning");
		} else {
			validationElement.classList.remove("s-notice__warning");
			validationElement.classList.add("s-notice__danger");
		}

		validationElement.classList.remove("d-none");
		validationElement.textContent = errorMessage;
	};

	let validateImage = (image: File): ValidationResult => {
		const validTypes = ["image/jpeg", "image/png", "image/gif"];
		const sizeLimit = 0x200000; // 2 MiB

		if (validTypes.indexOf(image.type) === -1) {
			return ValidationResult.InvalidFileType;
		}

		if (image.size >= sizeLimit) {
			return ValidationResult.FileTooLarge;
		}

		return ValidationResult.Ok;
	};

	function handleFileSelection(view: EditorView): void {
		resetImagePreview();
		const files = uploadFieldRef.current?.files!;
		if (view.state.selection.$from.parent.inlineContent && files.length) {
			void showImagePreview(files[0]);
		}
	}

	function toggleExternalUrlInput(show: boolean): void {
		const cta = ctaContainerRef.current!;
		const container = externalUrlInputContainerRef.current!;

		cta.classList.toggle("d-none", show);
		container.classList.toggle("d-none", !show);

		externalUrlRef.current!.value = "";
	}

	function validateExternalUrl(url: string): void {
		resetImagePreview();
		const addImageButton = addImageButtonRef.current!;

		if (!validateLink(url)) {
			showValidationError(
				_t("image_upload.external_url_validation_error"),
				"danger"
			);
			addImageButton.disabled = true;
		} else {
			hideValidationError();
			addImageButton.disabled = false;
		}
	}

	function resetUploader(): void {
		resetImagePreview();
		toggleExternalUrlInput(false);
		hideValidationError();

		uploadFieldRef.current!.value = "";
	}

	function addImagePlaceholder(view: EditorView, id: unknown): void {
		const tr = view.state.tr;
		if (!tr.selection.empty) tr.deleteSelection();
		// this.key.setMeta(tr, {
		// 	add: { id, pos: tr.selection.from },
		// 	// explicitly clear out any pasted/dropped file on upload
		// 	file: null,
		// 	shouldShow: false,
		// });
		view.dispatch(tr);
	}

	function removeImagePlaceholder(
		view: EditorView,
		id: unknown,
		transaction?: Transaction
	): void {
		let tr = transaction || view.state.tr;
		// tr = this.key.setMeta(tr, {
		// 	remove: { id },
		// 	file: null,
		// 	shouldShow: false,
		// });

		view.dispatch(tr);
	}

	function startImageUpload(
		view: EditorView,
		file: File | string
	): Promise<void> | undefined {
		// A fresh object to act as the ID for this upload
		const id = {};
		// addImagePlaceholder(view, id);

		if (!uploadOptions?.handler) {
			// purposefully log an error to the dev console
			// don't use our internal `log` implementation, it only logs on dev builds
			// eslint-disable-next-line no-console
			console.error(
				"No upload handler registered. Ensure you set a proper handler on the editor's options.imageUploadHandler"
			);
			return;
		}

		return uploadOptions.handler(file as File).then(
			(url) => {
				// // ON SUCCESS
				// // find where we inserted our placeholder so the content insert knows where to go
				// const decos = this.key.getState(view.state).decorations;
				// const found = decos.find(null, null, (spec: NodeSpec) => spec.id == id);
				// const pos = found.length ? found[0].from : null;
				// // If the content around the placeholder has been deleted, drop the image
				// if (pos === null) return;
				// // get the transaction from the dispatcher
				// const tr = this.addTransactionDispatcher(view.state, url, pos);
				// removeImagePlaceholder(view, id, tr);
				view.focus();

				let image = {
					"media-type": "img" as "img",
					src: url,
					alt: "",
					title: "",
				};
				editor.commands.setMedia(image);
			},
			() => {
				// ON ERROR
				// reshow the image uploader along with an error message
				show();
				removeImagePlaceholder(view, id, view.state.tr);
				showValidationError(_t("image_upload.upload_error_generic"), "error");
			}
		);
	}

	return ReactDOM.createPortal(
		uploadContainer,
		document.getElementById("link-editor")!
	);
	// TODO:
	// 1. escape HTML for brandingHTML, contentPolicyHtml
}

/**
 * Hides the image uploader
 * @param view The current editor view
 */
export function hideImageUploader(view: EditorView): void {
	hide();
}

/** Shows the image uploader
 * @param view The current editor view
 * @param file The file to upload
 */
export function showImageUploader(view: EditorView, file?: File): void {
	show();
}

/**
 * Checks if the image-upload functionality is enabled
 * @param state The current editor state
 */
export function imageUploaderEnabled(state: EditorState): boolean {
	return true;
}

0

Hmm a w ten sposób nie możesz przypisać handlera?

<input type="file" onChange={event => showImagePreviewAsync(event.target.files[0], ...)} ... /> 
0
Karaczan napisał(a):

Hmm a w ten sposób nie możesz przypisać handlera?

    <input type="file" onChange={event => showImagePreviewAsync(event.target.files[0], ...)} ... /> 

Zakomentowalem fragment

	// uploadFieldRef.current?.addEventListener("change", () => {
		// 	handleFileSelection(view);
		// });

i zamiast tego dodalem do inputa podobnie jak pokazales tylko dalem

onChange={() => handleFileSelection(view)}

i teraz wywoluje sie faktycznie tylko raz :D Pytanie dlaczego?

1

w wersji z addEventListener/removeEventListener tak naprawdę nie kasujesz wcale listenera.

najpierw podajesz funkcję w addEventListener:

uploadFieldRef.current?.addEventListener("change", () => {
  handleFileSelection(view);
});

czyli to coś jakbyś miał coś takiego:

const foo = () => {
  handleFileSelection(view);
});

uploadFieldRef.current?.addEventListener("change", foo)

natomiast potem zamiast użyć tej samej funkcji w `removeEventListener, to robisz coś takiego w cleanup:

   uploadFieldRef.current?.removeEventListener("change", () => {
	   handleFileSelection(view);
   });

co jest równoważne tak jakbyś tworzył sobie funkcję na boku nową:

const bar = () => {
  handleFileSelection(view);
});

uploadFieldRef.current?.removeEventListener("change", bar);

czyli próbujesz usunąć listenera z nowej funkcji, zamiast użyć tej samej. Dlatego pewnie ci się 2 razy odpala, bo jeśli listener nigdy nie zostaje usunięty, to jak 2 razy się odpali hook, to będziesz miał dwa listenery podłączone.

A to powinno wyglądać tak:

const foo = () => {
  handleFileSelection(view);
});

uploadFieldRef.current?.addEventListener("change", foo)
//..........
uploadFieldRef.current?.removeEventListener("change", foo);

Oczywiście zamiast głupiego foo lepiej nazwać opisowo np. handleFileSelectionChange.

0
LukeJL napisał(a):

w wersji z addEventListener/removeEventListener tak naprawdę nie kasujesz wcale listenera.

najpierw podajesz funkcję w addEventListener:

uploadFieldRef.current?.addEventListener("change", () => {
  handleFileSelection(view);
});

czyli to coś jakbyś miał coś takiego:

const foo = () => {
  handleFileSelection(view);
});

uploadFieldRef.current?.addEventListener("change", foo)

natomiast potem zamiast użyć tej samej funkcji w `removeEventListener, to robisz coś takiego w cleanup:

   uploadFieldRef.current?.removeEventListener("change", () => {
	   handleFileSelection(view);
   });

co jest równoważne tak jakbyś tworzył sobie funkcję na boku nową:

const bar = () => {
  handleFileSelection(view);
});

uploadFieldRef.current?.removeEventListener("change", bar);

czyli próbujesz usunąć listenera z nowej funkcji, zamiast użyć tej samej. Dlatego pewnie ci się 2 razy odpala, bo jeśli listener nigdy nie zostaje usunięty, to jak 2 razy się odpali hook, to będziesz miał dwa listenery podłączone.

A to powinno wyglądać tak:

const foo = () => {
  handleFileSelection(view);
});

uploadFieldRef.current?.addEventListener("change", foo)
//..........
uploadFieldRef.current?.removeEventListener("change", foo);

Oczywiście zamiast głupiego foo lepiej nazwać opisowo np. handleFileSelectionChange.

Bomba dzieki wielkie! Wychodzi na to ze chyba prosciej i szybciej uzywac listenerow bezposrednio w komponencie uzywajac onClick itp zamiast uzywac useEffect

0
Kokos123 napisał(a):

Bomba dzieki wielkie! Wychodzi na to ze chyba prosciej i szybciej uzywac listenerow bezposrednio w komponencie uzywajac onClick itp zamiast uzywac useEffect

Bo na tym polega sposób użycia Reacta. Natomiast jeśli masz potrzebę podpiąć się pod coś, czego nie ma w React (albo w sposób, który ciężko zintegrować z React), to masz możliwość odpalenia useEffect() i zrobienia tych zmian, które chcesz.

1 użytkowników online, w tym zalogowanych: 0, gości: 1