Next.js应用集成搜索功能,Zustand传参到搜索结果页异常问题求助
错误点汇总
getStaticProps是Next.js服务端执行的生命周期函数,运行阶段完全无法访问客户端的Zustand状态,你在其中调用useStore获取关键词的逻辑根本无法执行。- Zustand store直接定义在
SearchBar.js内且未导出,SearchResults.js无法导入访问store内的状态。 - 搜索跳转时未携带关键词参数,就算不使用服务端渲染,刷新搜索结果页后状态也会丢失,也无法支持搜索页链接分享。
SearchResults.js存在依赖缺失:未导入useState、styles、Footer、Image等依赖,直接运行会报错。- 视频过滤逻辑无效:
.filter((vid) => vid.snippet.title.toLowerCase())没有实际过滤规则,非空字符串永远返回真值,相当于没有执行过滤。
正确实现方案
这里推荐优先使用URL查询参数传递关键词,既支持页面刷新、链接分享,也能规避服务端生命周期拿不到客户端状态的问题。
步骤1:调整路由跳转逻辑,携带搜索参数
修改SearchBar.js的点击跳转逻辑,不需要依赖Zustand也能实现:
// SearchBar.js import { useRouter } from 'next/router'; import Image from 'next/image'; import { useState } from 'react'; import styles from '../styles/Header.module.scss'; import loupe from '../public/images/loupe.png'; const SearchBar = () => { const router = useRouter(); const [keyword, setKeyword] = useState(''); const handleClick = (e) => { e.preventDefault(); if (!keyword.trim()) return; // 跳转时携带搜索参数 router.push(`/searchResults?q=${encodeURIComponent(keyword.trim())}`); }; return ( <div className={styles.searchBar}> <input type='text' value={keyword} placeholder='Suche...' onChange={(e) => setKeyword(e.target.value)} /> <button className={styles.searchBtn} type='submit' onClick={handleClick}> <Image src={loupe} alt='' /> </button> </div> ); }; export default SearchBar;
如果需要全局存储搜索关键词,可单独抽离Zustand store,避免重复定义:
// store/useSearchStore.js import create from 'zustand'; const useSearchStore = create((set) => ({ keyword: '', setKeyword: (keyword) => set({ keyword }), })); export default useSearchStore;
在需要使用状态的组件导入即可,注意不要在getStaticProps/getServerSideProps这类服务端生命周期中调用。
步骤2:修改SearchResults.js,读取URL参数请求数据
使用getServerSideProps在服务端读取URL参数请求接口,也可以根据需求换成客户端用SWR/React Query请求:
// SearchResults.js import { fetchData } from '../lib/utils'; import { useState } from 'react'; import Image from 'next/image'; import Moment from 'react-moment'; import { Modal } from 'react-responsive-modal'; import ReactPlayer from 'react-player/youtube'; import Footer from '../components/Footer'; // 按实际文件路径调整 import styles from '../styles/SearchResults.module.scss'; // 按实际文件路径调整 export default function SearchResults({ videos, keyword }) { const [modalIsOpen, setModalIsOpen] = useState(false); const [modalData, setModalData] = useState(null); const videoURL = modalData ? `https://www.youtube.com/watch?v=${modalData}` : ''; // 修正排序和过滤逻辑 const sortedVids = videos .filter(vid => vid.snippet.title !== 'Private video') .sort((a, b) => new Date(b.snippet.videoPublishedAt) - new Date(a.snippet.videoPublishedAt)) // 如需前端过滤标题包含关键词的内容可以取消注释下一行 // .filter(vid => vid.snippet.title.toLowerCase().includes(keyword.toLowerCase())) return ( <> <div className={`${styles.playlist_container} ${styles.search}`}> <div className={styles.main_container}> <h1>Search results for: {keyword}</h1> {sortedVids.map((vid, id) => { return ( <div className={styles.item_container} key={id}> <div className={styles.clip_container}> <Image className={styles.thumbnails} src={vid.snippet.thumbnails.medium.url} layout='fill' objectFit='cover' alt={vid.snippet.title} /> <button className={styles.playBtn} onClick={() => { setModalData(vid.snippet.resourceId.videoId); setModalIsOpen(true); }} > <Image src='/images/play.svg' width='60' height='60' alt='播放' /> </button> </div> <div className={styles.details_container}> <h3>{vid.snippet.title}</h3> </div> </div> ); })} </div> </div> <div> <Modal open={modalIsOpen} onClose={() => setModalIsOpen(false)} center classNames={{ overlay: 'customOverlay', modal: 'customModal', overlayAnimationIn: 'customEnterOverlayAnimation', overlayAnimationOut: 'customLeaveOverlayAnimation', modalAnimationIn: 'customEnterModalAnimation', modalAnimationOut: 'customLeaveModalAnimation', }} animationDuration={800} > {videoURL && <ReactPlayer playing={true} url={videoURL} width='100%' height='calc(100vh - 100px)' config={{ youtube: { playerVars: { autoplay: 1, controls: 1, }, }, }} />} </Modal> </div> <Footer /> </> ); } export async function getServerSideProps(context) { // 从URL参数读取关键词 const keyword = context.query.q || ''; if (!keyword) { return { props: { videos: [], keyword: '' } } } const { YOUTUBE_KEY } = process.env; const uploadsURL = `https://youtube.googleapis.com/youtube/v3/search?part=snippet&channelId=UCbqKKcML7P4b4BDhaqdh_DA&maxResults=50&key=${YOUTUBE_KEY}&q=${encodeURIComponent(keyword)}`; const uploadsData = await fetchData(uploadsURL); return { props: { videos: uploadsData.items || [], keyword }, }; }
内容的提问来源于stack exchange,提问作者Ydonut13
相关产品推荐
相关产品推荐

