inertiajs / inertia

Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
https://inertiajs.com
MIT License
6.02k stars 405 forks source link

feat: Add `preserveUrl` option. #1869

Open xiCO2k opened 2 months ago

xiCO2k commented 2 months ago

This PR adds an option to preserve the current url.

This is specially useful when there is an infinite scroll pagination.

Before

To the infinite scrolls using Inertia we had to do something similar to this:

const allPosts = ref(props.posts.data);
const nextPageUrl = ref(props.posts.next_page_url || null);
const loading = ref(false);

const load = () => {
    if (nextPageUrl.value === null) {
        return;
    }

    loading.value = true;
    axios.get(nextPageUrl.value, {
        headers: {
            'X-Inertia': true,
            'X-Inertia-Partial-Component': 'Posts/Index',
            'X-Inertia-Partial-Data': 'posts',
            'X-Requested-With': 'XMLHttpRequest',
            'X-Inertia-Version': usePage().version,
        },
    }).then(({ data }) => {
        nextPageUrl.value = data.props.posts.next_page_url;
        allPosts.value.push(...data.props.posts.data);
        loading.value = false;
    }).catch(() => {
        loading.value = false;
        router.get(nextPageUrl.value);
    })
}

let observer;

onMounted(() => {
    observer = new IntersectionObserver(
        entries => entries.forEach(entry => entry.isIntersecting && load()),
    );

    observer.observe(document.querySelector('footer'));
});

After

This this addiction, we can take advantage of the preserveUrl option, and pass it on the router.get method, like this:

const allPosts = ref(props.posts.data);

const load = () => {
    if (props.posts.next_page_url === null) {
        return;
    }

    router.get(props.posts.next_page_url, {}, {
        preserveState: true,
        preserveScroll: true,
        preserveUrl: true,
        only: ['posts'],
        onSuccess: () => allPosts.value.push(...props.posts.data)
    })
}

onMounted(() => {
    observer = new IntersectionObserver(
        entries => entries.forEach(entry => entry.isIntersecting && load()),
    );

    observer.observe(document.querySelector('footer'));
});

Without the preserveUrl it adds the ?page=2 to the url and if the user refreshes the page it will just show the page=2 content instead of both pages.

Let me know what you guys think about this feature.

Thanks, Francisco.

james-em commented 2 months ago

@xiCO2k

Not sure if it's every getting merged because it's currently ambiguous if Inertia is still maintained, however as a workaround in the meantime I believe you could use the same After solution but simply restore the URL

// Just for reference of vars names
import { inertia, page, router } from '@inertiajs/svelte';

const currentUrl = $page.url;

// Taking you exemple here
router.get(props.posts.next_page_url, {}, {
        preserveState: true, // Already the case by default normally
        preserveScroll: true,
        only: ['posts'],
        onSuccess: () => { 
         allPosts.value.push(...props.posts.data);

         // Magic here
         window.history.replaceState({}, $page.title, currentUrl) 
        }
    })
xiCO2k commented 2 months ago

@james-em That is good hack, but I would love to have that supported by default.

hfoletto commented 2 months ago

I'd like to just thank you @xiCO2k for re-submitting this! I'm really looking forward to have this implemented in the package.