这基本上是页面的根。它有 3 个组件,MainMap(客户端)和 ListCasas(服务器)。
最后的按钮更改 url searchParams 并更新 UI。现在,这段代码按预期运行(UI 方面),但在控制台中存在大量错误,因为它是客户端组件,而 ListCasas 应该是服务器。
我认为这也可能导致页面重新加载。但似乎错误从根本上归结为 useMediaQuery()。它要求客户端组件不抛出错误(useEffect,useState),所以我无法找到一种方法来使其保持服务器组件,但也知道窗口大小?
mapClientComponent.tsx 的代码
'use client';
import MainMap from '@/app/[lang]/components/main-map';
import ListCasas from '../../ui/list-casas';
import NavBar from '../components/nav';
import { Locale } from '@/i18next.config';
import { getDictionary } from '@/app/lib/dictionary';
import { getImoveis } from '@/app/lib/data';
import { Suspense, useState } from 'react';
import FiltersBar from './bar';
import { usePathname, useSearchParams } from 'next/navigation';
import { useRouter } from 'next/navigation';
import useMediaQuery from '@/hooks/mediaQuery';
// import dynamic from 'next/dynamic';
// const useMediaQuery = dynamic(() => import('@/hooks/mediaQuery'), {
// ssr: false,
// });
export default function MapClientComponent({
params,
searchParams,
mapData,
page,
}: {
params: { lang: Locale };
mapData: any;
page: any;
searchParams?: {
// query?: string;
lat?: string;
lng?: string;
zona?: string;
page?: string;
sorting?: string;
types?: string;
min_price?: string;
max_price?: string;
number_of_rooms?: string;
exact_matches?: string;
yt?: string;
vr?: string;
beach?: string;
pool?: string;
isMapVisible?: string;
};
}) {
const lg = useMediaQuery(1024);
const isVis = searchParams?.isMapVisible || 'false';
const isMapVisible = isVis === 'true'; // Convert to boolean
const mapShouldShow = lg ? isMapVisible : true;
const listShouldShow = lg ? !isMapVisible : true;
// console.log('lg', lg);
// console.log(isMapVisible, 'isvisible');
// console.log(mapShouldShow, 'mapShouldShow');
// console.log(listShouldShow, 'listShouldShow');
//logic for visibility button
const searchParamss = useSearchParams();
const pathName = usePathname();
const { replace } = useRouter();
const paramss = new URLSearchParams(searchParamss);
function handleVis() {
const newIsMapVisible =
searchParams?.isMapVisible === 'true' ? 'false' : 'true';
paramss.set('isMapVisible', newIsMapVisible.toString());
replace(`${pathName}?${paramss.toString()}`);
}
return (
<div className="flex h-[100vh] min-w-full pt-[120px]">
{mapShouldShow && (
<Suspense
key={isMapVisible.toString() + lg.toString()}
fallback={'map loading...'}
>
<MainMap
isMapVisible={isMapVisible}
houses={mapData.houses}
dictionary={page}
lang={params.lang}
defaultCenter={{ lat: searchParams?.lat, lng: searchParams?.lng }}
/>
</Suspense>
)}
{listShouldShow && (
<Suspense key={'listCasas'} fallback={'List loading...'}>
<ListCasas
params={{ lang: params.lang }}
searchParams={{
sorting: searchParams?.sorting,
page: searchParams?.page,
zona: searchParams?.zona,
tipos: searchParams?.types,
min_price: searchParams?.min_price,
max_price: searchParams?.max_price,
number_of_rooms: searchParams?.number_of_rooms,
exact_matches: searchParams?.exact_matches,
yt: searchParams?.yt,
vr: searchParams?.vr,
beach: searchParams?.beach,
pool: searchParams?.pool,
}}
/>
</Suspense>
)}
<button
className="absolute inset-x-0 bottom-6 flex justify-center text-center text-white"
onClick={() => handleVis()}
>
<div className="w-[150px] bg-black p-2">
{searchParams?.isMapVisible === 'true' ? 'Show List' : 'Show Map'}
</div>
</button>
</div>
);
}
mediaQuery.ts 代码:
import { useState, useEffect } from 'react';
const useMediaQuery = (width: number) => {
const [targetReached, setTargetReached] = useState(false);
const updateTarget = (e: MediaQueryListEvent) => {
setTargetReached(e.matches);
};
useEffect(() => {
const media = window.matchMedia(`(max-width: ${width}px)`);
media.addEventListener('change', updateTarget);
// Check on mount (callback is not called until a change occurs)
if (media.matches) {
setTargetReached(true);
}
return () => media.removeEventListener('change', updateTarget);
}, [width]);
return targetReached;
};
export default useMediaQuery;
UI 按预期工作,但控制台抛出错误:
CasaCard 是 ListCasas 的子项...
app-index.js:33 Warning: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding 'use client' to a module that was originally written for the server. at ListCasas
app-index.js:33 Warning: A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework. at ListCasas
Warning: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding
'使用客户端' to a module that was originally written for the server. at CasaCard
app-index.js:33 Warning: A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework. at CasaCard
这些错误会成为 SSR 的问题吗?或者导致某种意想不到的错误?我不确定 UI 如何能够加载水合错误?
我真的很希望能够删除“使用客户端”。使用CSS,这将非常容易,但由于在这种情况下地图需要大量资源,我不认为CSS是一个可行的解决方案......
我找到了一个替代方案,而不是在主页中调用 useMediaQuery() 并强制它成为客户端组件。
我能够将逻辑移至changeVisibility 按钮,该按钮已经是客户端。在那里,有一个 useEffect() ,它在页面加载时将重定向到当前页面,+ searchParams isMapVisible 和 isListVisible。
此方法有效并且没有错误,但想听听关于这是否是一个好方法的任何想法。
更改可见按钮:
'use client';
import useMediaQuery from '@/hooks/mediaQuery';
import { ListBulletIcon, MapIcon } from '@heroicons/react/24/outline';
import { usePathname, useSearchParams } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function ChangeVisButton(searchParams: {
isMapVisible?: string;
isListVisibile?: string;
}) {
const searchParamss = useSearchParams();
const pathName = usePathname();
const { replace } = useRouter();
const params = new URLSearchParams(searchParamss);
const lg = useMediaQuery(1024);
console.log('lg', lg);
function handleVis() {
const newIsMapVisible =
searchParams.isMapVisible === 'true' ? 'false' : 'true';
const newIsListVisible =
searchParams.isListVisibile === 'true' ? 'false' : 'true';
//Only does anything if screen size is SM, the button wont even show in LG
if (lg) {
params.set('isMapVisible', newIsMapVisible.toString());
params.set('isListVisible', newIsListVisible.toString());
}
replace(`${pathName}?${params.toString()}`);
}
// console.log(params.get('isMapVisible'), 'ismapvisible');
//If lg changes, alter the searchParams. Show both on lg screens, and only 1 in sm screens...
//This will load on page load too to set the correct visibility, and pass it to params...
useEffect(() => {
if (!lg) {
params.set('isMapVisible', 'true');
params.set('isListVisible', 'true');
} else if (lg && params.get('isMapVisible') === 'true') {
//TODO: In here, we should be setting the isMapVisible to false. So that the list is the default
//Its only if the user comes from LG to SM, that this triggers, NOT on-load
//But for some reason, setting isMap to false, will cause the onLoad to malfunction
params.set('isListVisible', 'false');
} else {
params.set('isMapVisible', 'false');
params.set('isListVisible', 'true');
}
replace(`${pathName}?${params.toString()}`);
}, [lg, params]);
return (
<button
className="absolute inset-x-0 bottom-6 flex justify-center text-center text-white lg:hidden"
onClick={() => handleVis()}
>
<div className="flex w-[145px] items-center justify-evenly rounded-lg bg-white p-2 text-black hover:bg-gray-200">
{searchParams.isMapVisible === 'true' ? (
<ListBulletIcon className="h-[22px] w-[22px]" />
) : (
<MapIcon className="h-[22px] w-[22px]" />
)}
{searchParams.isMapVisible === 'true' ? 'Show List' : 'Show Map'}
</div>
</button>
);
}
然后使用以下命令控制服务器组件的可见性:
{searchParams?.isMapVisible === 'true' && (
和
{searchParams?.isListVisible === 'true' && (