Next.js (React)でモーダルやページ内でステップを表示する際に別の内容を表示しようとする際に、リロードやブラウザバックにより、その状態を保持したり、ユーザが思ったような挙動をしたい場合、location.replaceメソッドを使って、ページ遷移をせず、その状態をブラウザに保持することができます。
例えば以下のようにモーダルが開いているようなUIの場合、通常はjs内の変数(state)でモーダルの状態を管理することになるため、リロードした場合はモーダルが閉じてしまいます。
なので、この場合に例えば localhost:3000?id=2
のようにURLパラメータをつけることでリロードした際にURLに id=*
がある場合、モーダルを表示するというような設計をしたいと思います。
また、stepperUIのように、同一ページ内でナビゲートするようなUIであってもパラメータで現在のページ位置を保持するようになってれば、ユーザがブラウザの戻るを押してもページ自体はそのままで、ナビゲーションだけを戻すことが可能になります。
replaceを使う場合は以下のような要件になります。
Next.jsでは useRouter
(および withRouter
)でhistoryやURLのパラメータの取得・操作ができます。Reactのみ場合でもreact-routerなどを使えば同様なことができると思います。
ページの実装例は以下になります。(※ UIのライブラリとして chakra-ui を使っています)
import { NextPage } from 'next';
import { useRouter } from 'next/router';
import * as React from 'react';
import {
Box, Container, HStack, Modal, ModalContent, ModalOverlay, SimpleGrid
} from '@chakra-ui/react';
interface ParamsProps {}
const PARAMS_KEY = "id";
/**
* with params
*/
const Index: NextPage<ParamsProps> = () => {
const { query, replace } = useRouter();
const [selectedId, setSelectedId] = React.useState("");
// パラメータにid=xがある場合、モーダルをデフォルトで表示する
React.useEffect(() => {
if (query && query[PARAMS_KEY]) {
const id = query[PARAMS_KEY] as string;
setSelectedId(id);
}
}, [query]);
// 選択時、urlを更新する
React.useEffect(() => {
if (selectedId) {
replace(`${location.pathname}?${PARAMS_KEY}=${selectedId}`);
} else {
replace(location.pathname);
}
}, [selectedId]);
const handleCloseOverlay = React.useCallback(() => {
setSelectedId("");
}, []);
const handleSelectId = React.useCallback((id) => {
setSelectedId(id);
}, []);
return (
<Container>
<Modal
blockScrollOnMount={false}
isOpen={!!selectedId}
onClose={handleCloseOverlay}
>
<ModalOverlay />
<ModalContent p={16}>Selected id is {selectedId}</ModalContent>
</Modal>
<HStack column={3} spacing={4} px={4}>
{[1, 2, 3].map((id) => (
<Box
onClick={() => {
handleSelectId(id.toString());
}}
key={id}
>
<span>ID:{id}</span>
</Box>
))}
</HStack>
</Container>
);
};
export default Index;
解説など
React.useEffect(() => {
if (query && query[PARAMS_KEY]) {
const id = query[PARAMS_KEY] as string;
setSelectedId(id);
}
}, [query]);
このuseEffectの部分で今のURL上にパラメータがあるかどうかを判定します。URLパラメータがある場合はその値をstateに設定します。(実際にはパラメータは配列になることがあったりするので、もう少し厳密なvalidationが必要になります)
React.useEffect(() => {
if (selectedId) {
replace(`${location.pathname}?${PARAMS_KEY}=${selectedId}`);
} else {
replace(location.pathname);
}
}, [selectedId]);
stateが更新された場合このuseEffectが動作して、replaceメソッドを使いhistoryを更新します。
selectIdがunsetされた場合は現在のパス(例えば "/"
とか)に変更します。
モーダルを表示するかどうか(今回はchakra-uiのModalを利用しています)は、stateにidがあるかどうかで判定します。
<Modal
blockScrollOnMount={false}
isOpen={!!selectedId}
onClose={handleCloseOverlay}
>
pushを使う場合は以下のような要件になります。
(詳細は端折りますが、)replaceとの差分としては以下のようになります。
React.useEffect(() => {
if (query) {
if (query[PARAMS_KEY]) {
const id = query[PARAMS_KEY] as string;
setSelectedId(id);
} else {
setSelectedId("");
}
}
}, [query]);
// ナビゲートを実行する場合
const handleGoNext = React.useCallback((id) => {
push(`${location.pathname}?${PARAMS_KEY}=${id}`);
}, []);
// 初期状態にする場合など
const handleCloseOverlay = React.useCallback(() => {
push(location.pathname);
}, []);
表示する部分ではselectedIdの値が何になっているかで、表示するものを変更します。