laravel breeze(+inertia.js, react)に通知機能を与える(1)準備
今一つ知られてるのか知られていないのかよくわからん機能にlaravelの通知作成支援機能がある。それで作ってみるわけだがinertia.jsとreactの場合ちょっとlayout系を操作するにはクセがあるので、まずそこをシミュレートしようということになる。
通知ベルを作る
react-iconが入ってるという前提。ここではVscを使っているがまあ何でもいい、好きなやつでどーぞ。
import { VscBell } from 'react-icons/vsc';
export default function Authenticated({ user, header, children }) {
//
return (
//
<div className="hidden sm:flex sm:items-center sm:ml-6">
<div className="mr-3 relative">
{/* Notification Bell Icon */}
<span className="inline-flex rounded-md">
<button
type="button"
className="inline-flex items-center p-2 ml-3 border border-transparent rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150"
>
<VscBell className="h-6 w-6" />
</button>
</span>
</div>
<div className="relative">
<Dropdown>
<Dropdown.Trigger>
ただし、この箇所はsm(small device)では表示されない。これは後で調整しよう。とりあえずPCでは見えているはずだ
propsを渡す
layoutにpropsを渡す場合は実はapp/Http/Middleware/HandleInertiaRequests.php を参照する必要がある。
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'auth' => [
'user' => $request->user(),
],
'ziggy' => function () use ($request) {
return array_merge((new Ziggy)->toArray(), [
'location' => $request->url(),
]);
},
// 'notificationsCount' => $request->user()->unreadNotifications->count(), // 未読通知の数
'notificationsCount' => 5,
]);
}
ここではnotificationsCountに実際のlaravelの通知支援を書いているんだけど、まだ実装していない。従って適当な数字を渡してテストしてみよう。たとえば5とする
これを取得し、表示してみよう。
import { useState } from 'react';
import { Link, usePage } from '@inertiajs/react';
import Avatar from 'react-avatar';
import ApplicationLogo from '@/Components/ApplicationLogo';
import Dropdown from '@/Components/Dropdown';
import NavLink from '@/Components/NavLink';
import ResponsiveNavLink from '@/Components/ResponsiveNavLink';
import { VscBell } from 'react-icons/vsc';
export default function Authenticated({ user, header, children }) {
const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false);
const notificationsCount = usePage().props.notificationsCount;
// ...
<button
type="button"
className="inline-flex items-center p-2 ml-3 border border-transparent rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150"
>
<VscBell className="h-6 w-6" />
{notificationsCount && (<b>{notificationsCount}</b>)}
</button>
通知の数が変わる問題
ここで、たとえばなんか適当なpostを受け付けるrouteを作ってみよう。
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::post('/dashboard', function () {
return redirect()->back();
})->middleware(['auth', 'verified'])->name('dashboard');
ここでDashboardにボタンを付けて疑似的にpostを発生させる。まあ別にこれはrouter.postでもいいけど何となくformを書いた
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, useForm } from '@inertiajs/react';
import PrimaryButton from '@/Components/PrimaryButton';
export default function Dashboard({ auth }) {
const { post, processing} = useForm();
const submit = (e) => {
e.preventDefault();
post(route('dashboard'));
};
return (
<AuthenticatedLayout
user={auth.user}
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Dashboard</h2>}
>
<Head title="Dashboard" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div className="p-6 text-gray-900">You're logged in!</div>
<form onSubmit={submit}>
<PrimaryButton className="ml-4" disabled={processing}>
Click
</PrimaryButton>
</form>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
じゃあこのClickを押したときに擬似的に通知というか数を増やすためにセッションの値をインクリメンタルしてみよう。
Route::post('/dashboard', function () {
$notificationsCount = $request->session()->get('notificationsCount', 0);
$request->session()->put('notificationsCount', $notificationsCount + 1);
return redirect()->back();
})->middleware(['auth', 'verified'])->name('dashboard');
この値を、とりあえずDashboardで確認しておく。
use Illuminate\Http\Request;
Route::get('/dashboard', function (Request $request) {
$count = $request->session()->get('notificationsCount', 0);
return Inertia::render('Dashboard', ['count' => $count]);
})->middleware(['auth', 'verified'])->name('dashboard');
Route::post('/dashboard', function (Request $request) {
$notificationsCount = $request->session()->get('notificationsCount', 0);
$request->session()->put('notificationsCount', $notificationsCount + 1);
return redirect()->back();
})->middleware(['auth', 'verified'])->name('dashboard');
viewに渡しているので、一応出しておこう。
export default function Dashboard({ auth, notificationsCount }) {
<p>{notificationsCount}</p>
で出している。やりすぎな感じだとこんな
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, useForm } from '@inertiajs/react';
import PrimaryButton from '@/Components/PrimaryButton';
export default function Dashboard({ auth, count }) {
const { post, processing} = useForm();
const submit = (e) => {
e.preventDefault();
post(route('dashboard'));
};
return (
<AuthenticatedLayout
user={auth.user}
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Dashboard</h2>}
>
<Head title="Dashboard" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div className="p-6 text-gray-900">You're logged in!</div>
<div className="flex m-4">
<span className="px-2 py-1 text-sm font-bold leading-none text-red-100 bg-red-600 rounded-full">
{count}
</span>
</div>
<form onSubmit={submit}>
<PrimaryButton className="ml-4" disabled={processing}>
Click
</PrimaryButton>
</form>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
このアニgifじゃ初期値が10だったけど、とにかくこのようにclickすると数字がincrementされていく。この値を通知の数として得てみよう
通知の数としての利用
当然、ここでもsessionから値を取り出す必要がある。これは
Route::get('/dashboard', function (Request $request) {
$count = $request->session()->get('notificationsCount', 0);
ここで行ったような処理だ
ここではsessionから
app/Http/Middleware/HandleInertiaRequests.php
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'auth' => [
'user' => $request->user(),
],
'ziggy' => function () use ($request) {
return array_merge((new Ziggy)->toArray(), [
'location' => $request->url(),
]);
},
// 'notificationsCount' => $request->user()->unreadNotifications->count(), // 未読通知の数
'notificationsCount' => $request->session()->get('notificationsCount', 0),
]);
}
このように取得してpropsに与えている。
画面を縮小するとこのbellがみえねえからw今この部分だけしか出してないんだが、clickボタンに連動して数字が上がっていってるとおもってくださいな(ヘンなアニgifがないほうがわかりやすいかな?あるいはちゃんとレスポンシブにしろってことか…)
デザインの調整
increment表示の上限を設けて+で表示
まあ、通知の数をガンガン上げていってもいいっちゃいいんだけどある程度のところまで来たら10+とか100+とかにするのが今っぽい。
ってわけで関数を作って
const formatNotificationCount = (count) => {
return count > 10 ? '10+' : count.toString();
};
適用する
<button
type="button"
className="inline-flex items-center p-2 ml-3 border border-transparent rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150"
>
<VscBell className="h-6 w-6" />
{notificationsCount && (<b>{formatNotificationCount(notificationsCount)}</b>)}
</button>
cssの調整
bellの隣に雑然と数字があるだけだと今っぽくないので…
<button
type="button"
className="inline-flex items-center p-2 ml-3 border border-transparent rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150"
>
<VscBell className="h-6 w-6" />
<span className="absolute -top-1 -right-1 inline-flex items-center justify-center h-6 w-6 text-xs font-bold text-white bg-red-500 rounded-full border-2 border-white">
{formatNotificationCount(notificationsCount)}
</span>
</button>
ポーリングの挑戦
まあこれはいずれ後でやりますか
次回以降
フロントエンドは「まあまあ」固まったのでbackendをやっていく。フロントが固まったといってもまだbellの部分だけなので通知エリアがdropdownするようなものすらないが、これは流石にリアルなエントリがあった方がわかりやすいかとは思うので。
この記事が気に入ったらサポートをしてみませんか?