react で FOUC に抗う

前提

何がしたいか

印刷専用ページを設けてページ遷移後に window.print() で強制的に印刷ダイアログを表示させたい。react helmet asyncで head タグ内に印刷用 css (link もしくは style)を差し込んだところ、css がドキュメントに当たる前に window.print() が実行され FOUC が発生し css が当たっていないものが表示される。

React.useEffect(() => {

	window.print()

}, [])

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

<Helmet>

// いづれも不可

	<style>{printStyle}</style>

	<link href={printStyleUrl} rel='stylesheet' type='text/css' />

</Helmet>

link タグに onLoad があるのでは?

残念ながら onLoad は img タグのみ対応している模様(DefinitelyTyped/types/react/index.d.ts)。

react の礼儀作法と考えると変に vitejs の機能を使うよりも一つの css ファイルの中で screen と print に分けて書くのがセオリーと捉えるべきかと思う。

逃げの手段①

React.createElement ではなく document.createElement を使えば onLoad が使えるのでソース読み込み後に window.print() のトリガーを引くことが出きる。document を管理するフレームワークに対して使うべき手段でないのは明白。

逃げの手段②

vitejs の機能を使う手段だ。inlineで import すればそのまま使えるし url でインポートすれば fetch で取ってきて使える。

import printStyle from './print.css?inline'

// import printStyle from './print.css?url'

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

const [g, s] = React.useState(false)

if(!g){

	const css = new CSSStyleSheet()

	css.replaceSync(printStyle)

	document.adoptedStyleSheets = [css]  //ココ

	s(() => true)

}

const afterPrint = React.useCallback(async (print :Event) => {

	print.preventDefault()

	document.adoptedStyleSheets = []  //ココ

}, [])



React.useEffect(() => {

	window.addEventListener('afterprint', afterPrint)

	window.print()

	return () => {

		window.removeEventListener('afterprint', afterPrint)

	}

}, [])

正攻法

import style from './style.css?url'



export async function loader(){

	const styleFile = await fetch(style)

	const css = new CSSStyleSheet()

	css.replaceSync(await styleFile.text())

	document.adoptedStyleSheets = [css]

}

vite のファイルインポートと react router の loader を使えば遅延読み込みなしでコントロールできる。