codezero-be / laravel-localized-routes

⭐️ A convenient way to set up and use localized routes in a Laravel app.
MIT License
486 stars 45 forks source link

Problem using livewire #58

Open ivanbar opened 2 years ago

ivanbar commented 2 years ago

Hello, Ive problems using livewire. All works fine, but when I type the input, data refresh and dissapear data from views/livewire/filtro. When I click on url, data comes well. Ive to refresh url to view data. Ive tried displya blade without Front Controller, clear cache, clear routes

Http/Livewire/Filtro

`use Livewire\WithPagination;

class Filtro extends Component {

use WithPagination;

protected $paginationTheme = 'bootstrap';

protected $queryString = ['marca']; public $marca;

public function render()
{
    return view('livewire.filtro', [
        'productos' => Producto::where('deleted',0)
                            ->where('marca', 'like', '%'.$this->marca.'%' )->paginate(20)
    ]);
}

}`

routes/web `<?php

use Illuminate\Support\Facades\Route;

/* -------------------------------------------------------------------------- Web Routes

*/

// Localized Route::localized(function () {

Route::get('/anuncios', function () {
return view('front.anuncios');

});

Route::get('/', function () { return view('front.index'); });

Route::get('/index', [App\Http\Controllers\FrontController::class, 'index'])->name('front.index');

});

Auth::routes();

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Route::fallback(\CodeZero\LocalizedRoutes\Controller\FallbackController::class) ->middleware(\CodeZero\LocalizedRoutes\Middleware\SetLocale::class);`

views/livewire/filtro

`

Todoterrenos
{{ $productos->count() }} Resultados
Processing ...
You are now offline.
Ordenar
@if($productos->count()) @foreach($productos as $producto)
@if($producto->garantizado === 'Si')
Garantizado
@endif
...
{{ $producto->titulo }}
{{ number_format($producto->km,0,',','.') }} km
{{ number_format($producto->precio_contado,0,',','.' ) }} €
  • {{ $producto->municipio }}, {{ $producto->municipio_slug }}
@endforeach @else No hay productos @endif
{{ $productos->links() }}
` **App/Http/Comtrollers/FrontController** `first(); $num = $producto->visitas; $num ++; $producto->visitas = $num ; $producto->save(); return view('front.detalle')->with('producto',$producto); } } public function locale($lang) { App::setlocale($lang); session(['locale' => $lang]); return view('front.index'); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Remove the specified resource from storage. * * @param \App\Models\Front $front * @return \Illuminate\Http\Response */ public function destroy(Front $front) { // } } `
ivanvermeyen commented 2 years ago

Hi,

It's really hard to tell what's going on. I also have no experience with Livewire.

gwleuverink commented 1 year ago

Not sure if this is the problem, but I'm also experiencing some difficulties.

Livewire makes under the hood requests to the backend, to a dedicated /livewire/message endpoint. Because of this it seems like request()->route()->getAction('localized-routes-locale') doesn't return the locale of the route the request originated from.

mgussekloo commented 1 year ago

I have the same issue. After an update, {{ \Route::localizedUrl('nl') }} results in "http://site.test/livewire/message/slideshow" (where slideshow is the name of the Livewire component). I hope this gets fixed! Seems this is a known issue with Livewire, and a number of packages exist to deal with it, such as this one. I guess it would be nice if Localized Routes was aware of Livewire, or could be made aware of it in a way.

ivanvermeyen commented 1 year ago

Hello,

It seems you can configure the middleware in the Livewire config. By default it applies the web middleware group.

If you add the SetLocale middleware to the web group (right after the session middleware), does it then apply the expected locale? In theory it should find the locale in the session.

You can not use the option omit_url_prefix_for_locale though, since no prefix means default locale then.

Some additional findings...

The /livewire/message path is hardcoded. https://github.com/livewire/livewire/blob/e117c78f9a4b19edb294b5b576138fd1f896925a/js/connection/index.js#L41

The part that comes before that is the app_url, which is ultimately set in the Livewire config file. https://github.com/livewire/livewire/blob/65d311c63d2d1092ad5891fd0c1c1bdbed272cee/src/LivewireManager.php#L228 https://github.com/livewire/livewire/blob/65d311c63d2d1092ad5891fd0c1c1bdbed272cee/src/LivewireManager.php#L298

Unfortunately, you can not detect the locale in a config file. Perhaps you can update the window.livewire_app_url JS variable on runtime, to include the locale?

ivanvermeyen commented 1 year ago

Hi,

Sorry for the late followup. I did some testing, but I don't understand what's going on with the locale in Livewire. This doesn't seem to be documented.

I created a fresh Laravel test app with this package and Livewire included. If you like, you can download it at https://github.com/ivanvermeyen/test-app-localized-routes-livewire. The commits will show what I changed.

Locale Problem

For some reason, Livewire's locale is always set to the last locale in my supported_locales config. I have no clue why.

Initial page load:

Scherm­afbeelding 2023-04-05 om 12 49 32

When I click the button to update the Component:

Scherm­afbeelding 2023-04-05 om 12 49 52

Livewire requests should go through the web middleware by default, so the SetLocale middleware should run too.

Current Route / Request Problem

As mentioned before, Livewire makes ajax calls to a /livewire/message endpoint. Therefor, the current route and request doesn't match the "expected" original URL, which makes sense. Code that uses the current route or request won't work properly, like the Route::localizedUrl() macro.

I was able to fix this locally, but I don't know if this is a legit way.

First I need to inject the request (this is not on GitHub yet):

Scherm­afbeelding 2023-04-05 om 12 54 03

And then I inject a recreated version of the original request when we're in a Livewire requests, using a middleware.

public function handle(Request $request, Closure $next): Response
{
    if ($this->isLivewireRequest()) {
        $sessionRequest = Request::create(session('current-url'));

        $sessionRequest->setRouteResolver(function () use ($sessionRequest) {
            return Collection::make(Route::getRoutes())->first(function ($route) use ($sessionRequest) {
                return $route->matches($sessionRequest);
            });
        });

        app()->bind(LocalizedUrlGenerator::class, function () use ($sessionRequest) {
            return new LocalizedUrlGenerator($sessionRequest);
        });
    } else {
        session()->put('current-url', $request->fullUrl());
    }

    return $next($request);
}

protected function isLivewireRequest(): bool
{
    return class_exists(\Livewire\LivewireManager::class)
        && app(\Livewire\LivewireManager::class)->isLivewireRequest();
}

Now the macro works event when the Component gets updated:

Scherm­afbeelding 2023-04-05 om 13 55 27

I would need to do this for every class that uses the current route or request.

If anyone has a better understanding of how this all works, I'd love to hear it.

gwleuverink commented 1 year ago

Hi @ivanvermeyen thanks for the comprehensive writeup.

I've since written a temporary workaround that just works ™️. By overriding the packages UrlGenerator, detecting a Livewire request & grabbing the correct locale from the request referrer.

This is hacky as hell though, so I'd love to see a more robust solution.

I vaguely remember Caleb (Livewire's creator) talking about supporting dynamic url's for Livewire update requests in V3, which will release soon (hopefully). It might be easier to integrate with in V3.

ivanvermeyen commented 1 year ago

The problem seems to be caused by temporarily updating the App locale when generating the URLs for alternative locales. I do this to automatically fetch translated route parameters.

https://github.com/codezero-be/laravel-localized-routes/blob/2765567c0b99e4ed079630ad8d188bd65fae72ef/src/Illuminate/Routing/UrlGenerator.php#L30-L40

If I comment out the 2 calls to App::setLocale(), the locale is correct in Livewire and the {locale}/livewire/message/{name} endpoint is used for the ajax calls.

Will try to find out why this issue is occurring.

ivanvermeyen commented 1 year ago

Finally discovered the source of all evil.

In the code of the Route::localizedUrl() macro, I try to generate a URL using URL::route(). If that fails, I catch any exception and resolve the URL manually.

try {
    return URL::route($this->route->getName(), $parameters, $absolute, $locale);
} catch (InvalidArgumentException $e) {
    return '';
}

By catching the exception, the locale that was temporarily changed in the underlying UrlGenerator code, never gets restored! So I need to update the code in the UrlGenerator:

if ($locale !== null && $locale !== $currentLocale) {
    App::setLocale($locale);
}

try {
    $url = parent::route($resolvedName, $parameters, $absolute);
} finally {
    if ($locale !== null && $locale !== $currentLocale) {
        App::setLocale($currentLocale);
    }
}

I'll fix his first and then figure out how to handle the Request issues.

gwleuverink commented 1 year ago

Not sure if this helps, but you might need to add a persistent middleware to capture the locale url param

https://laravel-livewire.com/docs/2.x/security#persistent-middleware

Though it's been a while since first encountering this. If you need an extra set of eyes I'm happy to pair on it

ivanvermeyen commented 1 year ago

@gwleuverink Thanks! I also came across the persistent middleware. Although I think the web middleware group is automatically applied (locale is consistent now), people could potentially apply the middleware a different way for this package. So it's probably a good idea to use that.

ivanvermeyen commented 1 year ago

After a lot of thinking and tinkering, I've come up with a working prototype that handles the Livewire requests always being the ajax endpoint instead of the expected original URL.

I could run my current test suite on it by using the current URL for now, instead of the Livewire::originalUrl(). Mind the lines that are commented out.

I pushed a few changes beforehand, to inject the Request instance in my classes' constructor, and used that instead of the Request and Route facades.

In the SetLocale middleware (temporarily?) I can now check if it's a Livewire request, and then rebind my classes with the recreated Request. The main Laravel Request en current Route stays untouched.

You can see in the middleware that I had to jump through some hoops to make everything work. I'm not overly excited about the complexity, but at least for now, it works.

https://github.com/codezero-be/laravel-localized-routes/pull/93/files

If @gwleuverink or anyone would like to review, I'm open for any feedback. The middleware changes are on the support-livewire branch:

https://github.com/codezero-be/laravel-localized-routes/tree/support-livewire