I'm inserting base64 images into an editor by assigning their base64 string to reactive object state.image and pointing a watcher at it. Problem is that when I want to insert the same image twice, the watcher doesn't fire.
watcher:
const renderImage = (editor) => {
const unwatch = watch(() => state.image, () => {
console.log('watcher fires!')
editor.chain().focus().setImage({ src: state.image }).run()
unwatch()
state.image = null
})
}
As you can see I'm trying to kill the watcher with unwatch() and in addition set state.image = null but it still doesn't fire when the same image is loaded.
The @click event is triggered in this element:
<button @click="$refs.image.click(); renderImage(editor)"></button>
<input type="file" ref="image" style="display: none" @change="getImageUrl">
and the dataUrl of the image is created in getImageUrl:
const getImageUrl = (event) => {
const files = event.target.files;
if (!files.length)
return;
const reader = new FileReader();
reader.onload = (event) => {
state.image = event.target.result;
};
reader.readAsDataURL(files[0]);
}
Both elements seem to work as desired.
How do I make the watcher fire?
The question assumes the watch callback isn't firing, but in this case, it's actually the <input> that isn't firing its event.
The <input> only fires the change/input event when its value actually changes. When selecting the same file, the value hasn't changed, and thus no change-event occurs to call the getImageUrl() handler, which would've triggered the watch when the file gets loaded.
To resolve the issue, reset the <input>'s value after copying the selected file's path:
⋮
const image = ref() // template ref on input
const getImageUrl = (event) => {
const files = event.target.files;
if (!files.length)
return;
const fileToRead = files[0]; // 1️⃣ copy input's value
image.value.value = null; // 2️⃣ and reset the input
const reader = new FileReader();
reader.onload = (event) => {
state.image = event.target.result;
};
reader.readAsDataURL(fileToRead);
}
renderImage() is actually just using watch to essentially create an async callback for FileReader's load event. It seems this could be simplified by moving the image rendering code into that load-event handler directly:
const getImageUrl = (event) => {
const files = event.target.files;
if (!files.length)
return;
const fileToRead = files[0];
image.value.value = null;
const reader = new FileReader();
reader.onload = (event) => {
// 👇 render image
editor.chain().focus().setImage({ src: event.target.result }).run();
};
reader.onerror = reject;
reader.readAsDataURL(fileToRead);
}
Then you could remove renderImage():
<!-- <button @click="$refs.image.click(); renderImage(editor)"></button> --> <!-- no longer needed -->
<button @click="$refs.image.click()"></button>
...and also state.image:
const state = reactive({
// image: '' // no longer needed
})