Skip to content

Commit c060177

Browse files
authored
Support for Flash Data (#797)
* Support for flash data * Remove back feature * Update Middleware.php * Update ResponseFactoryTest.php * wip
1 parent 9d3d84d commit c060177

File tree

6 files changed

+196
-2
lines changed

6 files changed

+196
-2
lines changed

src/Inertia.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
* @method static \Inertia\MergeProp merge(mixed $value)
2424
* @method static \Inertia\MergeProp deepMerge(mixed $value)
2525
* @method static \Inertia\Response render(string $component, array<array-key, mixed>|\Illuminate\Contracts\Support\Arrayable<array-key, mixed>|\Inertia\ProvidesInertiaProperties $props = [])
26+
* @method static \Illuminate\Http\RedirectResponse back(int $status = 302, array<string, string> $headers = [], mixed $fallback = false)
2627
* @method static \Symfony\Component\HttpFoundation\Response location(string|\Symfony\Component\HttpFoundation\RedirectResponse $url)
28+
* @method static \Inertia\ResponseFactory flash(string|array<string, mixed> $key, mixed $value = null)
29+
* @method static array<string, mixed> getFlashed(?\Illuminate\Http\Request $request = null)
2730
* @method static void macro(string $name, object|callable $macro)
2831
* @method static void mixin(object $mixin, bool $replace = true)
2932
* @method static bool hasMacro(string $name)

src/Middleware.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,23 @@ public function handle(Request $request, Closure $next)
138138
$response->setStatusCode(303);
139139
}
140140

141+
if ($response->isRedirect()) {
142+
$this->reflash($request);
143+
}
144+
141145
return $response;
142146
}
143147

148+
/**
149+
* Reflash the session data for the next request.
150+
*/
151+
protected function reflash(Request $request): void
152+
{
153+
if ($flashed = Inertia::getFlashed($request)) {
154+
$request->session()->flash(SessionKey::FlashData->value, $flashed);
155+
}
156+
}
157+
144158
/**
145159
* Handle empty responses.
146160
*/

src/Response.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Inertia;
44

5+
use BackedEnum;
56
use Carbon\CarbonInterval;
67
use Closure;
78
use GuzzleHttp\Promise\PromiseInterface;
@@ -16,6 +17,7 @@
1617
use Illuminate\Support\Str;
1718
use Illuminate\Support\Traits\Macroable;
1819
use Inertia\Support\Header;
20+
use UnitEnum;
1921

2022
class Response implements Responsable
2123
{
@@ -100,7 +102,7 @@ public function __construct(
100102
$this->props = $props;
101103
$this->rootView = $rootView;
102104
$this->version = $version;
103-
$this->clearHistory = session()->pull('inertia.clear_history', false);
105+
$this->clearHistory = session()->pull(SessionKey::ClearHistory->value, false);
104106
$this->encryptHistory = $encryptHistory;
105107
$this->urlResolver = $urlResolver;
106108
}
@@ -168,6 +170,19 @@ public function cache(string|array $cacheFor): self
168170
return $this;
169171
}
170172

173+
/**
174+
* Add flash data to the response.
175+
*
176+
* @param \BackedEnum|\UnitEnum|string|array<string, mixed> $key
177+
* @return $this
178+
*/
179+
public function flash(BackedEnum|UnitEnum|string|array $key, mixed $value = null): self
180+
{
181+
Inertia::flash($key, $value);
182+
183+
return $this;
184+
}
185+
171186
/**
172187
* Create an HTTP response that represents the object.
173188
*
@@ -192,6 +207,7 @@ public function toResponse($request)
192207
$this->resolveCacheDirections($request),
193208
$this->resolveScrollProps($request),
194209
$this->resolveOnceProps($request),
210+
$this->resolveFlashData($request),
195211
);
196212

197213
if ($request->header(Header::INERTIA)) {
@@ -712,6 +728,18 @@ public function resolveOnceProps(Request $request): array
712728
return $onceProps->isNotEmpty() ? ['onceProps' => $onceProps->toArray()] : [];
713729
}
714730

731+
/**
732+
* Resolve flash data from the session.
733+
*
734+
* @return array<string, mixed>
735+
*/
736+
protected function resolveFlashData(Request $request): array
737+
{
738+
$flash = Inertia::getFlashed($request);
739+
740+
return $flash ? ['flash' => $flash] : [];
741+
}
742+
715743
/**
716744
* Determine if the request is an Inertia request.
717745
*/

src/ResponseFactory.php

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace Inertia;
44

5+
use BackedEnum;
56
use Closure;
67
use Illuminate\Contracts\Support\Arrayable;
8+
use Illuminate\Http\Request as HttpRequest;
79
use Illuminate\Support\Arr;
810
use Illuminate\Support\Facades\App;
911
use Illuminate\Support\Facades\Redirect;
@@ -12,8 +14,10 @@
1214
use Illuminate\Support\Traits\Macroable;
1315
use Inertia\Support\Header;
1416
use InvalidArgumentException;
17+
use Symfony\Component\HttpFoundation\RedirectResponse;
1518
use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirect;
1619
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
20+
use UnitEnum;
1721

1822
class ResponseFactory
1923
{
@@ -154,7 +158,7 @@ public function resolveUrlUsing(?Closure $urlResolver = null): void
154158
*/
155159
public function clearHistory(): void
156160
{
157-
session(['inertia.clear_history' => true]);
161+
session([SessionKey::ClearHistory->value => true]);
158162
}
159163

160164
/**
@@ -309,4 +313,55 @@ public function location($url): SymfonyResponse
309313

310314
return $url instanceof SymfonyRedirect ? $url : Redirect::away($url);
311315
}
316+
317+
/**
318+
* Flash data to be included with the next response. Unlike regular props,
319+
* flash data is not persisted in the browser's history state, making it
320+
* ideal for one-time notifications like toasts or highlights.
321+
*
322+
* @param \BackedEnum|\UnitEnum|string|array<string, mixed> $key
323+
*/
324+
public function flash(BackedEnum|UnitEnum|string|array $key, mixed $value = null): self
325+
{
326+
$flash = $key;
327+
328+
if (! is_array($key)) {
329+
$key = match (true) {
330+
$key instanceof BackedEnum => $key->value,
331+
$key instanceof UnitEnum => $key->name,
332+
default => $key,
333+
};
334+
335+
$flash = [$key => $value];
336+
}
337+
338+
session()->now(SessionKey::FlashData->value, [
339+
...$this->getFlashed(),
340+
...$flash,
341+
]);
342+
343+
return $this;
344+
}
345+
346+
/**
347+
* Create a new redirect response to the previous location.
348+
*
349+
* @param array<string, string> $headers
350+
*/
351+
public function back(int $status = 302, array $headers = [], mixed $fallback = false): RedirectResponse
352+
{
353+
return Redirect::back($status, $headers, $fallback);
354+
}
355+
356+
/**
357+
* Retrieve the flashed data from the session.
358+
*
359+
* @return array<string, mixed>
360+
*/
361+
public function getFlashed(?HttpRequest $request = null): array
362+
{
363+
$request ??= request();
364+
365+
return $request->hasSession() ? $request->session()->get(SessionKey::FlashData->value, []) : [];
366+
}
312367
}

src/SessionKey.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Inertia;
4+
5+
enum SessionKey: string
6+
{
7+
/*
8+
* Session key for clearing the Inertia history.
9+
*/
10+
case ClearHistory = 'inertia.clear_history';
11+
12+
/**
13+
* Session key for flash data.
14+
*/
15+
case FlashData = 'inertia.flash_data';
16+
}

tests/ResponseFactoryTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,4 +635,82 @@ public function test_once_prop_is_included_in_once_props_by_default(): void
635635
],
636636
]);
637637
}
638+
639+
public function test_flash_data_is_flashed_to_session_on_redirect(): void
640+
{
641+
Route::middleware([StartSession::class, ExampleMiddleware::class])->post('/flash-test', function () {
642+
return Inertia::flash(['message' => 'Success!'])->back();
643+
});
644+
645+
$response = $this->post('/flash-test', [], [
646+
'X-Inertia' => 'true',
647+
]);
648+
649+
$response->assertRedirect();
650+
$this->assertEquals(['message' => 'Success!'], session('inertia.flash_data'));
651+
}
652+
653+
public function test_render_with_flash_includes_flash_in_page(): void
654+
{
655+
Route::middleware([StartSession::class, ExampleMiddleware::class])->post('/flash-test', function () {
656+
return Inertia::flash('type', 'success')
657+
->render('User/Edit', ['user' => 'Jonathan'])
658+
->flash(['message' => 'User updated!']);
659+
});
660+
661+
$response = $this->post('/flash-test', [], [
662+
'X-Inertia' => 'true',
663+
]);
664+
665+
$response->assertSuccessful();
666+
$response->assertJson([
667+
'component' => 'User/Edit',
668+
'props' => [
669+
'user' => 'Jonathan',
670+
],
671+
'flash' => [
672+
'message' => 'User updated!',
673+
'type' => 'success',
674+
],
675+
]);
676+
677+
// Flash data should not persist in session after being included in response
678+
$this->assertNull(session('inertia.flash_data'));
679+
}
680+
681+
public function test_render_without_flash_does_not_include_flash_key(): void
682+
{
683+
Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/no-flash', function () {
684+
return Inertia::render('User/Edit', ['user' => 'Jonathan']);
685+
});
686+
687+
$response = $this->get('/no-flash', [
688+
'X-Inertia' => 'true',
689+
]);
690+
691+
$response->assertSuccessful();
692+
$response->assertJson([
693+
'component' => 'User/Edit',
694+
]);
695+
$response->assertJsonMissing(['flash']);
696+
}
697+
698+
public function test_multiple_flash_calls_are_merged(): void
699+
{
700+
Route::middleware([StartSession::class, ExampleMiddleware::class])->post('/create', function () {
701+
Inertia::flash('foo', 'value1');
702+
Inertia::flash('bar', 'value2');
703+
704+
return Inertia::render('User/Show');
705+
});
706+
707+
$response = $this->post('/create', [], ['X-Inertia' => 'true']);
708+
709+
$response->assertJson([
710+
'flash' => [
711+
'foo' => 'value1',
712+
'bar' => 'value2',
713+
],
714+
]);
715+
}
638716
}

0 commit comments

Comments
 (0)