Blazor - URL パラメータ違いの同じルート URL に遷移すると、表示が更新されない??

## 「前の記事」「次の記事」のリンクを追加してみた

先日、Blazor WebAssembly で Advent Calendar アプリを作る記事が Blazor Advent Calendar 2024 に投稿されました。

https://qiita.com/MNakae_IG/items/98e004a21e42c20baa02

これをちょっと改造しまして、以下のように、「前の記事」「次の記事」のリンクを、記事ページの右上に設けてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/ae07bb6c-5702-3a27-9dea-2b08672568ca.png)

実装は次のようにしました。

まず、記事ページの Razor コンポーネント中、`@code` ブロック内に、「前の記事」「次の記事」それぞれの URL を保持するフィールド変数を追加します。

```js:Pages/Post.razor
...
@code {
    ...
    private string? prevPostUrl;
    
    private string? nextPostUrl;
    ...
```

そして、`OnInitializedAsync` ライフサイクルメソッド内で、URL パラメータに渡された日付を元に、「前の記事」「次の記事」それぞれの URL を生成して、先のフィールド変数に格納します。

```js:Pages/Post.razor
...
@code {
    ...
    protected override async Task OnInitializedAsync()
    {
        // 前後の記事へのリンクを生成
        prevPostUrl = Day > 1 ? $"/post/{Day - 1}" : null;
        nextPostUrl = Day < 25 ? $"/post/{Day + 1}" : null;
        ...
```

あとは、これら「前の記事」「次の記事」それぞれの URL を保持するフィールド変数を、`<a>` 要素の `href` 属性にバインドして完成です。

```html:Pages/Post.razor
...
<SectionContent SectionName="AppBar">
    <div class="quick-nav-link">
        <a href="@prevPostUrl">前の記事</a>
        <span>|</span>
        <a href="@nextPostUrl">次の記事</a>
    </div>
</SectionContent>
...
```

## 表示が更新されない??

早速に動作確認してみます。1日目の記事 (/post/1) を開いた状態で、「次の記事」のリンク (/post/2) をクリックしてみると...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/a3012d74-ff06-5798-e5ba-cb2f7f48a968.png)

おやおや、ブラウザのアドレスバーに表示されている URL は、ちゃんと 2 日目の記事を指しているにもかかわらず、記事ページ本体は **1 日目の記事を表示したまま変わりません。** 開発者コンソールなどで確認するもエラーは起きていないようです。しかも、ページを再読み込みすると 2 日目の記事がちゃんと表示されます。いったい何がいけないのでしょうか。

## URL パラメータが変わるだけなら Razor コンポーネントは作り直されない

実は Blazor では、URL パラメータ違いの同じルート URL に遷移した場合、そのルート URL にマッチした Razor コンポーネントのインスタンスは作り直されないのです。代わりに、その Razor コンポーネントのインスタンスの、URL パラメータをバインドしているプロパティ値が書き換わるだけなのです。

例えば、"/" から "/post/1" にページ遷移して、記事ページがはじめて表示されるとき、そのタイミングで `Post` Razor コンポーネントのインスタンスが生成され、`Day` プロパティには 1 が設定されます。

```js:Pages/Post.razor
@page "/post/{day:int}"
...
@code {
    ...
    [Parameter]
    public int Day { get; set; } // 👈 1 が格納される
    ...
```

その上で、`OnInitializedAsync` をはじめとしたライフサイクルメソッドが次々と呼び出され、DOM ツリーへの描画が実施されます。

さてここで、URL "/post/1" から "/post/2" に遷移するときなのですが、このときは、記事ページをレンダリングした `Post` Razor コンポーネントのインスタンスは同じインスタンスが使われたまま、`Day` プロパティが 2 に設定された上で、再描画が行なわれます。

しかしこのとき、**`OnInitializedAsync` は呼び出されません。** 何となれば、その Razor コンポーネントのインスタンスは既に "初期化済み" だから、です。

```js:Pages/Post.razor
@page "/post/{day:int}"
...
@code {
    ...
    [Parameter]
    public int Day { get; set; } // 👈 2 が格納される...が、

    // 👇 OnInitializedAsync は呼ばれない!
    protected override async Task OnInitializedAsync() {
        // (ここで記事ソースの取得や整形などを行なっている)
        ...
```

これが「前の記事」「次の記事」リンクをクリックしても、記事の表示が変わらない理由です。

ではこのようなシナリオにおいてはどう対処するのがよいのでしょうか。

## `OnParameterSetAsync` を使おう

Blazor のライフサイクルメソッドのひとつに、`OnParameterSetAsync` というメソッドがあります。これは、その Razor コンポーネントのパラメータが設定されるときに呼び出されます。

つまり、URL "/post/`" から "/post/2" への遷移によって、記事ページコンポーネントの `Day` プロパティが 1 から 2 に設定されるときにも、この `OnParameterSetAsync` ライフサイクルメソッドが呼び出されます。

ですので、これまで `OnInitalizedAsync` で行なってきた、記事ソースの取得や整形などの処理を、`OnParameterSetAsync` で行なうようにプログラムを修正するとよいです。

```js:Pages/Post.razor
@page "/post/{day:int}"
...
@code {
    ...
    [Parameter]
    public int Day { get; set; } // 👈 2 が格納されるときに...

    // 👇 OnParametersSetAsync は呼ばれる!
    protected override async Task OnParametersSetAsync() {
        await base.OnParametersSetAsync();
        ...
```

なお、基底クラスの `OnParametersSetAsync` を呼び出したあとでないと、`[Parameter]` 属性を付与したプロパティへのパラメータ値の設定・反映は行なわれませんので、その点はご注意ください。


これで解決です。ちゃんと「前の記事」「次の記事」のリンクが動作するようになりました。

なお今回は変動しうるパラメータが `Day` プロパティのみということもあって、雑に、`OnIntializedAsync` で行なっていた処理をまるっと `OnParameterSetAsync` に引っ越しして済ませましたが、本来は、どのパラメータに変更があったのかとか、そのパラメータ値の変更によってどんな処理を行なうか、など、よく検討の上、適切に実装する必要があるはずです。場合によっては、`ShouldRender` ライフサイクルメソッドもオーバーライドして、再描画回数の低減を行なう必要も出てくるかもしれません。

以上、おつかれさまでした。