Search code examples
laravelvuejs3inertiajs

DOMException: Failed to execute 'replaceState' on 'History' when using form.post in inertia


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>

Solution

  • 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()
    });