inertia-laravel
inertia-laravel copied to clipboard
[Idea] View/Component Composers
One thing which would be helpful in my specific application would be having a feature such as the view composers in Laravel. Currently I'm building a simple solution to do this in my own project, but I'm wondering if there would be any interest of putting this in the core?
I'd be happy to create a pull request for this.
Let me know what you think!
What specifically do you mean—preassigning data to specific page components, similar to sharing (global) data?
Exactly that!
Currently I have a group of pages which require the same dataset. Since it's quite a lot of data I don't want to include this on every response using share() but bind it to a specific set of components.
Ideally I'd even have access to the props originally passed to the render() method.
@reinink the idea is:
replace a Laravel view with a inertia page and use everything from the core the way it is?
View::composer(
'Pages/Profile/*', 'App\Http\View\Composers\ProfileComposer'
);
Applies the Composer to all Inertia Components.
One option is to use the existing Inertia::share() functionality, and pass the current Inertia\Response as an argument.
Inertia::share('mydata', function (Inertia\Response $response) {
if ($response->component === 'Users\*') {
return [
// some data
];
}
});
Only issue there is this will always return the mydata key, even if it's null.
I've created a small demo version of my idea here rojtjo/inertia-laravel. Currently it only works using Closure based composers and without wildcard components.
Usage would be something like this:
Inertia::composer('Foo/Bar', function (Response $response) {
$response->with('foo', 'bar');
});
// Or bind it to multiple components
Inertia::composer(['Foo/Bar', 'Foo/Baz'], function (Response $response) {
$response->with('foo', 'bar');
});
@reinink , I can not get the correct result:
Inertia::share([
'something' => function (\Inertia\Response $response) {
return $response->component;
},
]);
It returns: 'Unresolvable dependency resolving [Parameter #0 [
Just adding a discussion I had on Twitter, related to this:
Brad:
Sorry to bug. I can't seem to figure this one out or figure an answer. Inertia - I have a Modal that needs access to a simple Eloquent Model. However, this modal can be clicked from all the views. Like a Global Modal. Should I use AppServiceProvider? I guess its only 1 query, so not a big deal.
In case it helps, I'm pasting a screenshot of the use case. In the Modal, I need access to AccountTypes but this should be available on All views (ie button on left side nav - create a new accout).
Me:
Hmm, great question.
I think if it's required on ALL views, I'd simply include this information on every request using the shared data functionality.
However, if it's only going to be shown on some pages, I might be inclined to simply load the required data via a classic xhr request when the modal opens.
It's honestly an interesting problem. It makes me think that it might be worthwhile to have some type of "view composer", that would allow you to only share data based on the component name.
Brad:
Thanks for the reply. Yah, I struggled with it for a bit, and then I opted to go for the AppServiceProvider option.
That's actually an interesting use case which I ran into myself recently. I opted to just include it everywhere for now even though it's not a clean as I'd hope it would be.
View composers can never really work the same as they do in Laravel though, mainly because Laravel will never know which components your 'page' uses. Which makes my earlier solution not suitable for these kind of issues.
My use case, which gave me this idea in the first place, was that I have a group of 'pages' which all require the same submenu. Normally I would just use Inertia::share() in the constructor of my controller but in this case the 'pages' were spread over multiple controllers.
I'm sure more complex use cases can and will arise, but the cases outlined in this thread so far (1. Account types, for a picker, 2. a navigational sub-menu) do not strike as cases that necessitate reaching into the database and sending back the data / models repeatedly... at least in the scope of a given user session; which is really just to corroborate that a "global" (or broad) share mechanism ala Laravel's view composers would be welcome...
And I have more experience with those, than with Inertia sharing patterns. To be able to use familiar laravel constructs and patterns and have it "just work" would be attractive, like this idea of hooking into view composers.
I did come across the need to reuse the same set of shareable data for different components. I will share the solution that I am using which is working for me and may find this useful to anyone else finding this issue.
I used the Larave's powerful macroable feature already included with Inertia.
Inertia::macro('composeWith', function(string $component, array $composeables = []) {
foreach($composeables as $composeClass) {
if(class_exists($composeClass)) {
$class = new ReflectionClass($composeClass);
if($class->implementsInterface(Shareable::class)) {
// the Shareable interface ensures that the method share is available
(new $composeClass)->share();
}
}
}
return Inertia::render($component);
});
and here is how I have used it in the controller
return Inertia::composeWith('Auth/Login', [
LoginView::class,
// add more shareable classes
]);
This way it is more controllable on to which commonly shareable set of classes to use and this also gives flexibility to use the same set of shareable classes with different set of components.
Also note that inside the share method of the shareable class, it just uses the share method provided in Inertia to share the data you need as normally you would do elsewhere.
I really like how clean it's usage looks! You could even replace (new $composeClass) with app($composeClass) to allow dependency injection.
You might have to watch out when using Laravel Octane though as the ResponseFactory is bound as a singleton. This would mean that all following requests would receive all the previously shared data which could contain sensitive data.
This could be solved by tweaking your macro a bit and pass the created response to the share method. Then you just use the with method on the response to add extra props.
Inertia::macro('composeWith', function (string $component, array $composeables = []) {
$response = Inertia::render($component);
foreach ($composeables as $composeClass) {
if (class_exists($composeClass) && $composeClass instanceof Shareable) {
app($composeClass)->share($response);
}
}
return $response;
});
