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.25k stars 421 forks source link

Redirect with hashtag from server gets stripped in InertiaJS #729

Closed Cannonb4ll closed 2 years ago

Cannonb4ll commented 3 years ago

Versions:

Describe the problem:

I have a Laravel app, where I want to redirect with a hashtag (e.g. example.com/page#hash) but the hashtag gets stripped.

It seems this is the line preventing hashtags to come along:

https://github.com/inertiajs/inertia/blob/master/packages/inertia/src/router.ts#L275 https://github.com/inertiajs/inertia/blob/211f49b98928b0f5f366387b6fbf99a65e4a07be/packages/inertia/src/url.ts#L42

Is it possible to allow hashtags? Might be cool if this would be configurable, like:

this.$inertia.post('/url', {foo: bar}, {allowFragments: true});

Steps to reproduce:

In a Laravel app, do:

return redirect()->route('my-page', '#hash');

Or:

return redirect()->route('my-page')->withFragment('hash');

And see results in InertiaJS, in default Laravel + Blade it works fine, the hashtag remains in the URL.

aidan-casey commented 3 years ago

Inertia's Laravel adapter uses the Laravel getRequestUri() method for the URL included in the response. By default, it does not look like PHP maintains the URL fragment in the server variable, but Symfony's HTTP Foundation (which Laravel uses under the hood) also especially strips out the URL fragment here.

It seems like we would have a difficult time actually getting the hash into the URL sent back to the client. I'm also not sure how worth it is, since we can use traditional routing, etc. with Inertia.

Cannonb4ll commented 3 years ago

@aidan-casey If you use a default laravel app with blade, my code works just like you expect it to work.

How would you explain that?

aidan-casey commented 3 years ago

In a traditional Laravel + Blade app, the server returns a redirect response with a Location header. The browser then sends the request to the URL specified in the Location header (which would include the URL fragment). There's nothing funky going on here and it is working as the browser should.

In the case of Inertia, it's not being made aware of the Location URL (with the hash) because it is utilizing Axios under the hood which is just following the redirect without making Inertia aware of the fragment in the destination URL. Inertia is then updating the current URL and browser history with the URL in the JSON Inertia response from Laravel (which doesn't include the fragment).

In order to get this working, Inertia technically needs to intercept the redirect response, store the hash fragment client side, make the request to the destination, and finally append the hash back onto the URL once done as it does with normal visit requests without a redirect.

Edit: @RobertBoes did some further digging on this and actually found we wouldn't even be able to intercept the redirect client-side to read the Location header. This is according to specs for XMLHttpRequest and fetch. This essentially leaves us without much ability to solve the underlying issue.

dima-bzz commented 2 years ago

@claudiodekker Hello. I wanted to ask about the behavior when there is an anchor in the url. There is a page http://localhost/admin/settings#main and it has a settings form. When I save the form this.form.post('http://localhost/admin/settings/main'), the anchor is removed from the page url. That is how it should be?

aidan-casey commented 2 years ago

@dima-bzz - That is correct.

dima-bzz commented 2 years ago

@aidan-casey So I can't save the anchor?

claudiodekker commented 2 years ago

Not right now, that's what the issue is about ;)

sarukomine commented 1 year ago

You may try to use watchEffect and usePage to make it happen. Send hashtag via shared data to frontend, and frontend catch it, then add hashtag in the URL. It works to me, it can instantly jump to specific sections of the page~

return redirect()->route('my-page')->with('hashtag', 'i-am-hashtag');
<?php

namespace App\Http\Middleware;

// ...

class HandleInertiaRequests extends Middleware
{
    // ...

    public function share(Request $request): array
    {
        return collect(parent::share($request))->merge([
            'hashtag' => $request->session()->get('hashtag'),
        ])->toArray();
    }
}
<script setup>
import { watchEffect } from 'vue';
import { usePage } from '@inertiajs/vue3';

watchEffect(() => {
    const page = usePage();

    if (page.props.hashtag) {
        window.location.hash = `#${page.props.hashtag}`;
    }
});
</script>
dasundev commented 1 year ago

@sarukomine Many Thanks!

ChileNetwork commented 4 months ago

Yo solucione asi mi tema...

<script setup>
import {  router } from '@inertiajs/vue3';
const props = defineProps({
    url_unique_guide: {type: String}
})
const viewAllGuideTours = () => {
    router.visit('/partner-profile/'+props.url_unique_guide+'#guide-tours',{
        onFinish:() => {
            window.location.hash = `#guide-tours`;
        }
    })
}
</script>
<template>

 <button type="button" @click="viewAllGuideTours">
      <font-awesome-icon icon="eye"/> View All Tours
  </button>

<div class="w-full mt-8 mb-2 bg-gray-100" style="background-image:url('/images/hp-tours-bg.jpg');no-repeat;width:100%;">
          <a name="guide-tours"></a>
                        <div class="flex flex-col text-center">
                            <div class="px-2 py-6 mx-auto text-4xl font-semibold tracking-wider text-center text-gray-800">
                                Tours By {{unique_guide.g_name}} {{unique_guide.g_lastname?unique_guide.g_lastname.charAt(0).toUpperCase()+'.':''}}
        </div>
        <div class="w-1/3 mx-auto border-2 border-b border-corpo"></div>
        <div class="px-4 mx-auto my-4 text-xl text-gray-800 ">
            See all of the tours offered by this guide.
        </div>
        <div class="md:grid md:grid-cols-2 md:gap-4 lg:grid lg:grid-cols-3 lg:gap-4 xs:mx-6">
            <div v-for="(tour,i) in tours" :key="i" class="mx-3 my-2 bg-white shadow-inner">
                <tour-card :item=tour :ind=tour.id></tour-card>
            </div>
        </div>
    </div>
</div>
</template>
joao-antonio-gomes commented 3 months ago

Any perspective to add this behavior to inertia?