我一直在尝试使用 Recoil 进行分页,但我被卡住了。似乎正在发生的是我订阅的组件在没有更新状态的情况下重新渲染。
在我的索引页面上,我使用 NextJS SSR 从 Firebase 获取初始负载。该有效负载作为道具传递给页面组件,然后作为数组存储在 Recoil atom 中。
内容使用JobFeed组件呈现,该组件接收 Recoil 选择器,该选择器返回状态的过滤版本。这一切都很好。
const Index = ({initialJobs}) => {
const [jobs, setJobs] = useRecoilState(jobState);
const jobList = useRecoilValue(filteredJobListState);
const fallback = <div>Loading</div>
useEffect(() => {
if (jobs.length < 1) {
setJobs(initialJobs)
} else {
setJobs(jobList)
}
}, [])
return (
<>
<Layout>
<Head>
<title>{SITE_TAGLINE} - {SITE_NAME}</title>
</Head>
<Masthead />
<Filters />
{
(jobList.length < 1) ? <div>Loading</div> :
<JobFeed jobs={jobList}/>
}
</Layout>
</>
)
}
Recoil 原子看起来像这样:
export const jobState = atom({
key: "jobState",
default: [],
});
过滤后的状态选择器如下所示:
export const filteredJobListState = selector({
key: 'filteredJobListState',
get: ({get}) => {
// Get current filters
const filters = get(filterState);
// Get current job state
const jobList = get(jobState);
// Construct queries to return correct state object
if (!filters || filters.category === "" || filters.state === "") {
return jobList;
} else {
return jobFilter(jobList, filters);
};
// Check key value pairs and return filtered state
function jobFilter(jobs, query) {
return jobs.filter(job => {
for (let key in query) {
if (job[key] === undefined || job[key] != query[key])
return false;
}
return true;
});
};
}
});
我遇到问题的地方是组件中的分页功能。
const JobFeed = ({ title, jobs }) => {
const [loading, setLoading] = useRecoilState(loadingState);
const [postsEnd, setPostsEnd] = useRecoilState(postsEndState);
const [jobList, setJobList] = useRecoilState(jobState);
const getMorePosts = async () => {
setLoading(true);
// Get last post from current batch to run paginated query
const last = jobList[jobList.length - 1];
// Convert timestap for query if the format is incompatible
const cursor = typeof last.createdAt === 'number' ? fromMillis(last.createdAt) : last.createdAt;
const query = database
.collection('jobs')
.orderBy('createdAt', 'desc')
.startAfter(cursor)
.limit(20);
// Run query to get additional posts
try {
const newJobs = (await query.get()).docs.map(postToJSON);
setJobList([...jobList, newJobs]);
// The result of the console.log is the previous job state
console.log(jobList)
// Turn off loader
setLoading(false);
// Check to see if more posts available
if (newJobs.length < 20) {
setPostsEnd(true)
}
} catch (error) {
console.log(error)
}
}
return (
<section className="py-4">
<Container>
<SectionHeading>{title}</SectionHeading>
{jobs.map((job) => {
return (
<JobCard
key={job.id}
company={job.companyName}
position={job.position}
city={job.city}
state={job.state}
createdAt={job.createdAt}
category={job.category}
apply={job.application}
logo={job.companyLogo}
/>
)
})}
<div className="border-t border-gray-100 my-8"></div>
{!loading && !postsEnd && <button className="btn-green" onClick={getMorePosts}>Load more</button>}
<Loader show={loading} />
{postsEnd && "You've reached the end!"}
</Container>
</section>
)
}
jobState 和 filterState 原子是经过过滤的JobState 选择器的依赖项。使用 useRecoilState 设置 filterState 会触发JobFeed组件的重新渲染,没有任何问题(但它是同步的)。
但是,当异步 getMorePosts 函数更新 jobState 时,组件重新渲染并抛出错误“TypeError:无法读取未定义的属性 '0'。”
当我在 getMorePosts 调用 setState后console.log jobState时,我收到了前一个状态的值。
我试过了:
- 将JobFeed组件包装在Suspense组件中,并检查 SSR。同样的问题。
- 重构事物以使用 getRecoilValueLoadable。同样的问题。
- 将 getMorePosts 逻辑放在选择器中。同样的问题。
- 将 getMorePosts 逻辑放入 useRecoilCallback。引发嵌套钩子错误。
我错过了什么?