Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for inertia deferred props #939

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"require-dev" : {
"fakerphp/faker": "^1.14",
"friendsofphp/php-cs-fixer": "^3.0",
"inertiajs/inertia-laravel": "^1.2",
"inertiajs/inertia-laravel": "^2.0",
"larastan/larastan": "^2.7",
"livewire/livewire": "^3.0",
"mockery/mockery": "^1.6",
Expand Down
14 changes: 11 additions & 3 deletions docs/advanced-usage/use-with-inertia.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ return Inertia::render('Song', SongsData::from($song));
This package supports [lazy](https://spatie.be/docs/laravel-data/v4/as-a-resource/lazy-properties) properties, which can be manually included or excluded.

Inertia has a similar concept called [lazy data evaluation](https://inertiajs.com/partial-reloads#lazy-data-evaluation), where some properties wrapped in a closure only get evaluated and included in the response when explicitly asked.
Inertia v2 introduced the concept of [deferred props](https://inertiajs.com/deferred-props), which allows to defer the loading of certain data until after the initial page render.

This package can output specific properties as Inertia lazy props as such:
This package can output specific properties as Inertia lazy or deferred props as such:

```php
class SongData extends Data
{
public function __construct(
public Lazy|string $title,
public Lazy|string $artist,
public Lazy|string $lyrics,
) {
}

Expand All @@ -35,15 +37,17 @@ class SongData extends Data
return new self(
Lazy::inertia(fn() => $song->title),
Lazy::closure(fn() => $song->artist)
Lazy::inertiaDeferred(fn() => $song->lyrics)
);
}
}
```

We provide two kinds of lazy properties:
We provide three kinds of lazy properties:

- **Lazy::inertia()** Never included on first visit, optionally included on partial reloads
- **Lazy::closure()** Always included on first visit, optionally included on partial reloads
- **Lazy::inertiaDeferred()** Included when ready, optionally included on partial reloads

Now within your JavaScript code, you can include the properties as such:

Expand All @@ -60,7 +64,9 @@ We already saw earlier that the package can automatically make properties Lazy,
It is possible to rewrite the previous example as follows:

```php
use Spatie\LaravelData\Attributes\AutoClosureLazy;use Spatie\LaravelData\Attributes\AutoInertiaLazy;
use Spatie\LaravelData\Attributes\AutoClosureLazy;
use Spatie\LaravelData\Attributes\AutoInertiaLazy;
use Spatie\LaravelData\Attributes\AutoInertiaDeferred;

class SongData extends Data
{
Expand All @@ -69,6 +75,8 @@ class SongData extends Data
public Lazy|string $title,
#[AutoClosureLazy]
public Lazy|string $artist,
#[AutoInertiaDeferred]
public Lazy|string $lyrics,
) {
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/Attributes/AutoInertiaDeferred.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Spatie\LaravelData\Attributes;

use Attribute;
use Closure;
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Lazy\InertiaDeferred;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class AutoInertiaDeferred extends AutoLazy
{
public function build(Closure $castValue, mixed $payload, DataProperty $property, mixed $value): InertiaDeferred
{
return Lazy::inertiaDeferred(fn () => $castValue($value));
}
}
6 changes: 6 additions & 0 deletions src/Lazy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Spatie\LaravelData\Support\Lazy\ClosureLazy;
use Spatie\LaravelData\Support\Lazy\ConditionalLazy;
use Spatie\LaravelData\Support\Lazy\DefaultLazy;
use Spatie\LaravelData\Support\Lazy\InertiaDeferred;
use Spatie\LaravelData\Support\Lazy\InertiaLazy;
use Spatie\LaravelData\Support\Lazy\RelationalLazy;

Expand Down Expand Up @@ -37,6 +38,11 @@ public static function inertia(Closure $value): InertiaLazy
return new InertiaLazy($value);
}

public static function inertiaDeferred(mixed $value): InertiaDeferred
{
return new InertiaDeferred($value);
}

public static function closure(Closure $closure): ClosureLazy
{
return new ClosureLazy($closure);
Expand Down
23 changes: 23 additions & 0 deletions src/Support/Lazy/InertiaDeferred.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Spatie\LaravelData\Support\Lazy;

use Inertia\DeferProp;

class InertiaDeferred extends ConditionalLazy
{
public function __construct(
mixed $value,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mixed instead of DeferProps', since the $valueparameter in theAutoInertiaDeferred::build` method is mixed and needs to be passed to this function

) {
$callback = is_a($value, DeferProp::class)
? fn () => $value()
: fn () => new DeferProp($value);

parent::__construct(fn () => true, $callback);
}

public function resolve(): DeferProp
{
return new DeferProp($this->value);
}
}
46 changes: 46 additions & 0 deletions tests/CreationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
use Illuminate\Support\Enumerable;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\ValidationException;
use Inertia\DeferProp;
use Inertia\Inertia;
use Inertia\LazyProp;

use function Pest\Laravel\postJson;

use Spatie\LaravelData\Attributes\AutoClosureLazy;
use Spatie\LaravelData\Attributes\AutoInertiaDeferred;
use Spatie\LaravelData\Attributes\AutoInertiaLazy;
use Spatie\LaravelData\Attributes\AutoLazy;
use Spatie\LaravelData\Attributes\AutoWhenLoadedLazy;
Expand All @@ -38,6 +41,7 @@
use Spatie\LaravelData\Support\Creation\CreationContext;
use Spatie\LaravelData\Support\DataClass;
use Spatie\LaravelData\Support\Lazy\ClosureLazy;
use Spatie\LaravelData\Support\Lazy\InertiaDeferred;
use Spatie\LaravelData\Support\Lazy\InertiaLazy;
use Spatie\LaravelData\Tests\Fakes\Castables\SimpleCastable;
use Spatie\LaravelData\Tests\Fakes\Casts\ConfidentialDataCast;
Expand Down Expand Up @@ -1452,3 +1456,45 @@ class TestAutoLazyClassAttributeData extends Data
->toHaveCount(2)
->each()->toBeInstanceOf(FakeNestedModelData::class);
});

it('can create a data object with deferred properties', function () {
$dataClass = new class () extends Data {
public InertiaDeferred $deferred;

public function __construct()
{
}
};

$data = $dataClass::from([
"deferred" => Lazy::inertiaDeferred(Inertia::defer(fn () => 'Deferred Value')),
]);

expect($data->deferred)->toBeInstanceOf(InertiaDeferred::class);
expect($data->deferred->resolve()())->toBe('Deferred Value');
});

it('can use auto deferred to construct a deferred property', function () {
$dataClass = new class () extends Data {
#[AutoInertiaDeferred]
public InertiaDeferred $string;
};

$data = $dataClass::from(['string' => Lazy::inertiaDeferred(Inertia::defer(fn () => 'Deferred Value'))]);

expect($data->string)->toBeInstanceOf(InertiaDeferred::class);
expect($data->toArray()['string'])->toBeInstanceOf(DeferProp::class);
});

it('can use class level auto deferred to construct a deferred property', function () {
#[AutoInertiaDeferred]
class AutoDeferredData extends Data
{
public InertiaDeferred $deferred;
};

$data = AutoDeferredData::from(['string' => Inertia::defer(fn () => 'Deferred Value')]);

expect($data->deferred)->toBeInstanceOf(InertiaDeferred::class);
expect($data->toArray()['string'])->toBeInstanceOf(DeferProp::class);
})->todo();
21 changes: 21 additions & 0 deletions tests/Resolvers/VisibleDataFieldsResolverTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Inertia\DeferProp;
use Inertia\Inertia;
use Inertia\LazyProp;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Attributes\Hidden;
Expand All @@ -10,6 +12,7 @@
use Spatie\LaravelData\Resolvers\VisibleDataFieldsResolver;
use Spatie\LaravelData\Support\DataConfig;
use Spatie\LaravelData\Support\Lazy\ClosureLazy;
use Spatie\LaravelData\Support\Lazy\InertiaDeferred;
use Spatie\LaravelData\Support\Lazy\InertiaLazy;
use Spatie\LaravelData\Support\Partials\Partial;
use Spatie\LaravelData\Support\Partials\PartialsCollection;
Expand Down Expand Up @@ -215,6 +218,24 @@ public static function create(string $name): static
expect($dataClass::create('Freek')->toArray()['name'])->toBeInstanceOf(LazyProp::class);
});

it('always transforms deferred inertia data to inertia deferred props', function () {
$dataClass = new class () extends Data {
public function __construct(
public string|InertiaDeferred|null $name = null
) {
}

public static function create(string $name): static
{
return new self(
Lazy::inertiaDeferred(Inertia::defer(fn () => $name))
);
}
};

expect($dataClass::create('Freek')->toArray()['name'])->toBeInstanceOf(DeferProp::class);
});

it('always transforms closure lazy into closures for inertia', function () {
$dataClass = new class () extends Data {
public function __construct(
Expand Down
Loading