【Remix】loaderで取得した環境変数をclientLoaderおよびclientActionに渡したい
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFS4iOlsDPNhkBtKjU8Cc1bOm9RqIA-4clGM39S0Z2rdVXLa8TsvVSuLA0fbj3yAp-hS404p5s0rJNm8CfnQJyxIzPUJejGdieQE1wb2DwlhNGgtzaBPhvbJ0kIAZhazNK6ODnATmGX8I/s800/food_pizza_takuhai.png
(クラインアントに渡す環境変数は公開しても問題ないものを前提としています)
【前提】
code: loader.tsx
export const loader = () => {
return json({
publicAppValue: process.env.PUBLIC_APP_VALUE
})
}
code: component.tsx
export const loader = () => {
return json({
publicAppValue: process.env.PUBLIC_APP_VALUE
})
}
...
export default function Hoge {
const data = useLoaderData<typeof loader>()
return (
<>
<p> {data?.publicAppValue} is PUBLIC_APP_VALUE </p>
</>
)
}
【clientLoader】
clientLoaderの引数であるserverLoaderを利用します。serverLoaderはloaderに対してデータ取得を実行する非同期関数です。これを利用して、loaderで取得した環境変数をclientLoaderに渡すことができます。(ハイドレーション時に取得したい場合は、clientloader.hydrateを忘れずに設定してください) code:client.tsx
export const loader = () => {
return json({
publicAppValue: process.env.PUBLIC_APP_VALUE
})
}
export const clinetLoader = async({ serverLoader }: ClientLoaderFunctionArgs) => {
const { publicAppValue } = await serverLoader<typeof loader>()
return json({ publicAppValue })
}
clientLoader.hydrate = true
...
【clientAction】
Remixのデータフローに則って環境変数を渡します。RemixのデータフローはLoaders => Component => Actions の繰り返しで成立しています。なのでloaderで取得した環境変数をclientActionに渡す際にはComponentを経由する必要性があります。そこで、inputタグのhidden型を使用して、(client)Loaders => Component => (client)Actionsの順に環境変数を渡します。 code:client.tsx
// Loaders
export const loader = () => {
return json({
publicAppValue: process.env.PUBLIC_APP_VALUE
})
}
export const clinetLoader = async({ serverLoader }: ClientLoaderFunctionArgs) => {
const { publicAppValue } = await serverLoader<typeof loader>()
return json({ publicAppValue })
}
clientLoader.hydrate = true
// Component
export default function SignIn {
const data = useLoaderData<typeof clientLoader>()
return (
<>
<Form method='post'>
<input type='email' name='email' />
<input type='password' name='password' />
<input type='hidden' name='publicAppValue' value={data?.publicAppValue}>
<button type='submit'>Sign in</button>
</Form>
</>
)
}
// Action
export const clientAction = async ({ request }: ClientActionFunctionArgs) => {
const data = Object.fromEntries(await request.formData())
console.log(data) // {email: '...', password: '...', publicAppValue: '...'}
...
}
(今回のようにstaticな値をvalueとして利用するhidden型に関しては開発者ツールを使わない限りユーザー入力による変更(クエリパラメータやフォームへの入力に対する影響)がないので、HTML escapeは利用していません。)
【余談】
Remixのデータフローに則ると、React Hooksによる状態管理の開放が起きるので、clientLoaderに関してはuseEffect、clientActionに関してはsubmit時のハンドラー内でuseLoaderDataを使いたくなってしまいます。
Remixにおいて、純粋なReactの状態管理が必要なシーンは入力や監視対象に対する副作用が発生したときのみ
になりそうなので、このあたりの使い分けに最近は頭を悩ませています。