Skip to content

[2.x] Add initial Shared Prop Support for Initial Page Load #760

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

Open
wants to merge 4 commits into
base: 2.x
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
8 changes: 8 additions & 0 deletions src/FirstLoad.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Inertia;

interface FirstLoad
{
//
}
20 changes: 20 additions & 0 deletions src/InitialProp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Inertia;

use Illuminate\Support\Facades\App;

class InitialProp implements FirstLoad
{
protected $callback;

public function __construct(callable $callback)
{
$this->callback = $callback;
}

public function __invoke()
{
return App::call($this->callback);
}
}
25 changes: 24 additions & 1 deletion src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function __construct(
string $rootView = 'app',
string $version = '',
bool $encryptHistory = false,
?Closure $urlResolver = null
?Closure $urlResolver = null,
) {
$this->component = $component;
$this->props = $props instanceof Arrayable ? $props->toArray() : $props;
Expand Down Expand Up @@ -126,6 +126,7 @@ public function toResponse($request)
$this->resolveMergeProps($request),
$this->resolveDeferredProps($request),
$this->resolveCacheDirections($request),
$this->resolveInitialProps($request),
);

if ($request->header(Header::INERTIA)) {
Expand All @@ -141,6 +142,7 @@ public function toResponse($request)
public function resolveProperties(Request $request, array $props): array
{
$props = $this->resolvePartialProperties($props, $request);
$props = $this->filterInitialProps($props);
$props = $this->resolveArrayableProperties($props, $request);
$props = $this->resolveAlways($props);
$props = $this->resolvePropertyInstances($props, $request);
Expand Down Expand Up @@ -296,6 +298,14 @@ public function resolvePropertyInstances(array $props, Request $request): array
return $props;
}

/**
* Filter initial properties from the props.
*/
public function filterInitialProps(array $props): array
{
return array_filter($props, static fn ($prop) => ! $prop instanceof InitialProp);
}

/**
* Resolve the cache directions for the response.
*/
Expand Down Expand Up @@ -376,6 +386,19 @@ public function resolveDeferredProps(Request $request): array
return $deferredProps->isNotEmpty() ? ['deferredProps' => $deferredProps->toArray()] : [];
}

public function resolveInitialProps(Request $request): array
{
if ($request->header(Header::INERTIA)) {
return [];
}

$initialProps = collect($this->props)
->filter(fn ($prop) => $prop instanceof InitialProp)
->mapWithKeys(fn ($value, $key) => [$key => App::call($value)]);

return $initialProps->isNotEmpty() ? ['initialProps' => $initialProps->toArray()] : [];
}

/**
* Determine if the request is a partial request.
*/
Expand Down
10 changes: 7 additions & 3 deletions src/ResponseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ class ResponseFactory
/** @var Closure|null */
protected $urlResolver;

/***
* @param string $name The name of the root view
* @return void
/**
* @param string $name The name of the root view
*/
public function setRootView(string $name): void
{
Expand Down Expand Up @@ -128,6 +127,11 @@ public function optional(callable $callback): OptionalProp
return new OptionalProp($callback);
}

public function initial(callable $callback): InitialProp
{
return new InitialProp($callback);
}

public function defer(callable $callback, string $group = 'default'): DeferProp
{
return new DeferProp($callback, $group);
Expand Down
73 changes: 73 additions & 0 deletions tests/InitialPropTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Inertia\Tests;

use Illuminate\Http\Request;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use Inertia\InitialProp;
use Inertia\Tests\Stubs\ExampleMiddleware;

class InitialPropTest extends TestCase
{
public function test_initial_props_accessibility()
{
Inertia::share([
'initial' => Inertia::initial(fn () => true),
'appName' => Inertia::initial(fn () => 'test'),
]);

$this->prepareMockEndpoint();

$response = $this->withoutExceptionHandling()->get('/');

$response->assertSuccessful();
$this->assertSame(
'<div id="app" data-page="{&quot;component&quot;:&quot;User\/Edit&quot;,&quot;props&quot;:{&quot;errors&quot;:{}},&quot;url&quot;:&quot;\/&quot;,&quot;version&quot;:&quot;&quot;,&quot;clearHistory&quot;:false,&quot;encryptHistory&quot;:false,&quot;initialProps&quot;:{&quot;initial&quot;:true,&quot;appName&quot;:&quot;test&quot;}}"></div>',
$response->content(),
);
}

public function test_initial_props_are_not_accessible()
{
Inertia::share([
'initial' => Inertia::initial(fn () => true),
'appName' => Inertia::initial(fn () => 'test'),
]);

$this->prepareMockEndpoint();

$response = $this->withoutExceptionHandling()->get('/', [
'X-Inertia' => 'true',
]);

$response->assertSuccessful();
$response->assertJsonMissingPath('initialProps');
}

public function test_can_invoke(): void
{
$initialProp = new InitialProp(function () {
return 'A initial value';
});

$this->assertSame('A initial value', $initialProp());
}

public function test_can_resolve_bindings_when_invoked(): void
{
$initialProp = new InitialProp(function (Request $request) {
return $request;
});

$this->assertInstanceOf(Request::class, $initialProp());
}

private function prepareMockEndpoint(): \Illuminate\Routing\Route
{
return Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () {
return Inertia::render('User/Edit');
});
}
}
11 changes: 11 additions & 0 deletions tests/ResponseFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Inertia\ComponentNotFoundException;
use Inertia\DeferProp;
use Inertia\Inertia;
use Inertia\InitialProp;
use Inertia\LazyProp;
use Inertia\MergeProp;
use Inertia\OptionalProp;
Expand Down Expand Up @@ -342,6 +343,16 @@ public function test_can_create_optional_prop(): void
$this->assertInstanceOf(OptionalProp::class, $optionalProp);
}

public function test_can_create_initial_prop(): void
{
$factory = new ResponseFactory;
$initialProp = $factory->initial(function () {
return 'An initial value';
});

$this->assertInstanceOf(InitialProp::class, $initialProp);
}

public function test_can_create_always_prop(): void
{
$factory = new ResponseFactory;
Expand Down