Skip to content

[2.x] Add initial props resolver for Initial Page Load #771

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 2 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
1 change: 1 addition & 0 deletions src/Inertia.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* @method static void version(\Closure|string|null $version)
* @method static string getVersion()
* @method static void resolveUrlUsing(\Closure|null $urlResolver = null)
* @method static void resolveInitialProps(\Closure|null $initialPropsResolver = null)
* @method static \Inertia\OptionalProp optional(callable $callback)
* @method static \Inertia\LazyProp lazy(callable $callback)
* @method static \Inertia\DeferProp defer(callable $callback, string $group = 'default')
Expand Down
14 changes: 14 additions & 0 deletions src/Middleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ public function urlResolver()
return null;
}

/**
* Defines the initial props that are shared by default.
*
* @return \Closure|null
*/
public function initialPropsResolver()
{
return null;
}

/**
* Handle the incoming request.
*
Expand All @@ -93,6 +103,10 @@ public function handle(Request $request, Closure $next)
Inertia::resolveUrlUsing($urlResolver);
}

if ($initialPropsResolver = $this->initialPropsResolver()) {
Inertia::resolveInitialPropsUsing($initialPropsResolver);
}

$response = $next($request);
$response->headers->set('Vary', Header::INERTIA);

Expand Down
30 changes: 29 additions & 1 deletion src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ class Response implements Responsable
*/
protected ?Closure $urlResolver = null;

/**
* The initial props resolver callback.
*/
protected ?Closure $initialPropsResolver = null;

/**
* Create a new Inertia response instance.
*
Expand All @@ -92,7 +97,8 @@ public function __construct(
string $rootView = 'app',
string $version = '',
bool $encryptHistory = false,
?Closure $urlResolver = null
?Closure $urlResolver = null,
?Closure $initialPropsResolver = null,
) {
$this->component = $component;
$this->props = $props;
Expand All @@ -101,6 +107,7 @@ public function __construct(
$this->clearHistory = session()->pull('inertia.clear_history', false);
$this->encryptHistory = $encryptHistory;
$this->urlResolver = $urlResolver;
$this->initialPropsResolver = $initialPropsResolver;
}

/**
Expand Down Expand Up @@ -188,6 +195,7 @@ public function toResponse($request)
$this->resolveMergeProps($request),
$this->resolveDeferredProps($request),
$this->resolveCacheDirections($request),
$this->resolveInitialProps($request),
);

if ($request->header(Header::INERTIA)) {
Expand Down Expand Up @@ -508,6 +516,26 @@ public function resolveDeferredProps(Request $request): array
return $deferredProps->isNotEmpty() ? ['deferredProps' => $deferredProps->toArray()] : [];
}

/**
* Resolve initial props.
*
* @return array<string, mixed>
*/
public function resolveInitialProps(Request $request): array
{
if ($request->header(Header::INERTIA)) {
return [];
}

if ($this->initialPropsResolver === null) {
return [];
}

$initialProps = App::call($this->initialPropsResolver, ['request' => $request]);

return empty($initialProps) ? [] : ['initialProps' => $initialProps];
}

/**
* Determine if the request is a partial request.
*/
Expand Down
16 changes: 16 additions & 0 deletions src/ResponseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ class ResponseFactory
*/
protected $urlResolver;

/**
* The initial props resolver callback.
*
* @var Closure|null
*/
protected $initialPropsResolver;

/**
* Set the root view template for Inertia responses. This template
* serves as the HTML wrapper that contains the Inertia root element
Expand Down Expand Up @@ -149,6 +156,14 @@ public function resolveUrlUsing(?Closure $urlResolver = null): void
$this->urlResolver = $urlResolver;
}

/**
* Set the initial props resolver.
*/
public function resolveInitialPropsUsing(?Closure $initialPropsResolver = null): void
{
$this->initialPropsResolver = $initialPropsResolver;
}

/**
* Clear the browser history on the next visit.
*/
Expand Down Expand Up @@ -262,6 +277,7 @@ public function render(string $component, $props = []): Response
$this->getVersion(),
$this->encryptHistory ?? config('inertia.history.encrypt', false),
$this->urlResolver,
$this->initialPropsResolver,
);
}

Expand Down
44 changes: 44 additions & 0 deletions tests/InitialPropTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Inertia\Tests;

use Illuminate\Session\Middleware\StartSession;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use Inertia\Tests\Stubs\CustomInitialPropsResolverMiddleware;
use Inertia\Tests\Stubs\ExampleMiddleware;

class InitialPropTest extends TestCase
{
public function test_initial_props_accessibility(): void
{
$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(): void
{
$this->prepareMockEndpoint();

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

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

private function prepareMockEndpoint(): \Illuminate\Routing\Route
{
return Route::middleware([StartSession::class, ExampleMiddleware::class, CustomInitialPropsResolverMiddleware::class])->get('/', function () {
return Inertia::render('User/Edit');
});
}
}
24 changes: 24 additions & 0 deletions tests/Stubs/CustomInitialPropsResolverMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Inertia\Tests\Stubs;

use Illuminate\Http\Request;
use Illuminate\Routing\ResponseFactory;
use Inertia\Middleware;
use PHPUnit\Framework\Assert;

class CustomInitialPropsResolverMiddleware extends Middleware
{
public function initialPropsResolver(): callable
{
return function ($request, ResponseFactory $otherDependency) {
Assert::assertInstanceOf(Request::class, $request);
Assert::assertInstanceOf(ResponseFactory::class, $otherDependency);

return [
'initial' => true,
'appName' => 'test',
];
};
}
}