I am trying to implement infinite scroll using useIntersectionObserver. Everything works fine, not until after I scroll, then submit a post, the error occurs.
Error
DOMException: Failed to execute 'replaceState' on 'History'
web.php
Route::get('/dashboard', [DashboardController::class, 'index'])->middleware(['auth', 'verified'])->name('dashboard');
Dashboard.vue
<template>
<Head title="Dashboard" />
<AuthenticatedLayout>
<div class="py-2">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="overflow-hidden">
<div class="py-6 text-gray-900 flex">
<section class="basis-2/5">
<h3>Profile</h3>
<h3>Logout</h3>
</section>
<section class="basis-3/5 px-3">
<Form />
<div v-if="posts.data.length" class="mt-12" id="posts">
<Post
v-for="post in posts.data"
:key="post.id"
:post="post"
/>
<div ref="bottom" class="-translate-y-32"></div>
</div>
</section>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
</template>
<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import { Head } from '@inertiajs/vue3';
import Form from '@/Components/Post/Form.vue';
import Post from '@/Components/Post.vue';
import { onMounted, ref } from 'vue';
import { useIntersectionObserver } from '@vueuse/core';
import axios from 'axios';
const props = defineProps({
posts: Object
});
const bottom = ref(null);
const { stop } = useIntersectionObserver(bottom, ([{ isIntersecting }]) => {
if (!isIntersecting) {
return;
}
axios.get(`${props.posts.meta.path}?cursor=${props.posts.meta.next_cursor}`)
.then((response) => {
props.posts.data = [...props.posts.data, ...response.data.data];
props.posts.meta = response.data.meta;
if (!response.data.meta.next_cursor) {
stop();
}
});
});
onMounted(() => {
document.addEventListener('click', function (event) {
if (event.target.matches('.post-body .mention')) {
if (parseInt(event.target.dataset.id) > 0) {
alert(event.target.outerText);
}
}
});
});
</script>
Form.vue
<template>
<div>
<form @submit.prevent="post">
<div>
<Tiptap v-model="form.body" placeholder="Got something to share?" />
<!-- <TextArea
id="body"
type="text"
class="mt-4 block w-full"
rows="3"
placeholder="Got something to share?"
v-model="form.body"
required
/> -->
<InputError class="mt-2" :message="form.errors.body" />
</div>
<div class="mt-3">
<PrimaryButton :disabled="form.processing || form.body.length < 1">Post</PrimaryButton>
</div>
</form>
</div>
</template>
<script setup>
import TextArea from '@/Components/TextArea.vue';
import InputError from '@/Components/InputError.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import { useForm } from '@inertiajs/vue3';
import Tiptap from '../Tiptap.vue';
const form = useForm('PostForm', {
body: ''
});
const post = () => {
form.post(route('posts.store'), {
preserveScroll: true,
preserveState: false,
onSuccess: (page) => {
form.reset();
console.log(page.props)
//page.props.posts.data = [[], ...page.props.posts.data];
},
onFinish: (visit) => {
console.log(visit)
}
});
};
</script>
I solved this issue by re-assigning the props
to a ref
, then use the assigned ref
instead of the props, then use JSON.parse
and JSON.stringify
like so:
const props = defineProps({
posts: Object
});
const posts = ref(JSON.parse(JSON.stringify(props.posts)));
axios code
axios.get(`${posts.value.meta.path}?cursor=${posts.value.meta.next_cursor}`)
.then((response) => {
posts.value.data = [...posts.value.data, ...response.data.data];
posts.value.meta = response.data.meta;
if (!response.data.meta.next_cursor) {
stop();
}
});
And on the Form.vue, I set preserveState
to false
, like so:
const post = () => form.post(route('posts.store'), {
preserveState: false,
onSuccess: () => form.reset()
});