RemixのダイナミックルーティングでOutletを更新しても、フォームの入力欄がリセットされない？

# RemixでOutletを使ったらinputの中身が更新されない？
## ハマったこと
Remix(React Router v6)の`Outlet`とダイナミックルーティングを組み合わせたとき、ページを`DataLoader`でデータを取得しても、`input`や`textaria`の初期値が更新されない。

## 解決策
`input`や`textaria`にページ切り替え前後で異なる`key`を割り当てる。

## 具体的な状況
### ディレクトリ（routes配下のみ抽出）
```text:ディレクトリ構成図
app/routes
├── index.tsx
├── posts
│  ├── $slug.tsx
│  ├── admin
│  │  ├── $slug.tsx
│  │  ├── index.tsx
│  │  └── new.tsx
│  ├── admin.tsx
│  └── index.tsx
└── test.tsx
```

以下は今回話をする上で、重要なコンポーネントの概要です（相当簡略化しています）。

```tsx:簡略化したadmin.tsxの構造
// DataLoaderの返り値
{
    posts: [
        {
            post: '記事1',
            title: '記事のタイトル1',
            markdown: '内容'
        }
    ]
}


<>
    { /** 記事のリンクのリスト **/ }
    <ul>
        {posts.map((post) => (
            <li key={post.slug}>
                <Link to={post.slug}>
                    {post.title}
                </Link>
            </li>
        ))}
    </ul>
    <main>
        // 配下の$slug.tsxを表示するOutlet
        <Outlet />
    </main>
</>
```

```tsx:簡略化した$slug.tsxの構造
// DataLoaderの返り値
{
    post: {
        post: '記事1',
        title: '記事のタイトル1',
        markdown: '内容'
    }
}

<form method="put">
    <input
    type="text"
    name="title"
    defaultValue={post.title}
    />
    <input
    type="text"
    name="slug"
    label="Post Slug"
    defaultValue={post.slug}
    />
    <textarea
    name="markdown"
    label="Markdown"
    defaultValue={post.markdown}
    />
    <button type="submit">Create Post</button>
</form>
```

以下の画像は`http://localhost:3000/posts/admin/90s-mixtape`にアクセスした際の表示です。
左は記事のリストであり、admin.tsxで描写されます。
右は記事の編集フォームで、記事のリストから`$slug.tsx`に渡された`params`にも基づいて記事を検索し、現在の記事情報を表示します。

**記事を選択**
![スクリーンショット 2022-07-04 17.57.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591669/fc321bbc-126e-ee7d-6c03-270b0165077a.png)

**別の記事を選択**
![スクリーンショット 2022-07-04 18.00.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591669/e7c0d0d0-3bca-ed04-2838-4db827339ca9.png)

今回の問題は、一度記事を選択してから、別の記事に切り替えた際に発生しました。上記の画像からURLや記事のタイトルなどは変化しても、`textaria`の中身は変化がないことがわかります。`console.log`でDataloaderの出力が変化していることは確認しましたが、一部の要素が変化しません。`useState`を使って各`input`や`textaria`の状態を管理しても状況は変わらず…編集する記事を切り替えているのだから、全ての入力欄が変化してほしいところです。なお、`textaria`以外の入力欄も変更を加えると、それ以降変化しなくなりました。

## 勘違いしていたこと
ネステッドルーティングでは、URLが変更されると新しい`Outlet`要素がSSRで描画され、変更前の`Outlet`要素は一切捨てられるものだと考えていました。しかし、実際は`Suspend`などを利用し、Reactのルールに則っているため、基本的には前の`Outlet`の状態を引き継ぐようで、今回`input`や`textaria`が更新されない理由はここにありました。

## 状態をリセットして再描画したい要素にはkeyを明示的につける
以前の状態を切り捨て1から描画させたいときは、リストでなくても明示的に`key`を指定し、その変化でReactに全く別の要素になったことを示しましょう。以下のようにすることで、ページを切り替えDataLoaderの値が変化したときに`key`も変化するため、1から`input`や`textaria`を描写させることができます。

```tsx:修正した$slug.tsxの概要
// 記事のユニーク値+入力欄のname
<form method="put">
    <input
    // タイトル用のキー
    key={post.slug + "title"}
    type="text"
    name="title"
    defaultValue={post.title}
    />
    <input
    // slug用のキー
    key={post.slug + "slug"}
    type="text"
    name="slug"
    label="Post Slug"
    defaultValue={post.slug}
    />
    <textarea
    // Markdown用のキー
    key={post.slug + "markdown"}
    name="markdown"
    label="Markdown"
    defaultValue={post.markdown}
    />
    <button type="submit">Create Post</button>
</form>
```

![スクリーンショット 2022-07-04 18.39.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591669/9c08a4d7-13a7-5872-38ba-f96cc262a2e1.png)

![スクリーンショット 2022-07-04 18.40.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591669/88ade2ee-14fb-96ea-f303-26b190b931b5.png)

全ての入力欄が変化していることが読み取れます。

## 最後に
思わぬところでハマりだいぶ時間を使ってしまいました。RemixやNext.jsなどの便利なライブラリ、React18の新機能など色々新しいものを学ぶことも大事ですが、基礎的なところを疎かにしてはいけないですね…

## 参考資料
[React.jsの地味だけど重要なkeyについて](https://qiita.com/koba04/items/a4d23245d246c53cd49d)
[React.js and Dynamic Children - Why the Keys are Important](https://blog.arkency.com/2014/10/react-dot-js-and-dynamic-children-why-the-keys-are-important/)