1

我一直在尝试使用 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}&nbsp;-&nbsp;{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 调用 setStateconsole.log jobState时,我收到了前一个状态的值。

我试过了:

  1. JobFeed组件包装在Suspense组件中,并检查 SSR。同样的问题。
  2. 重构事物以使用 getRecoilValueLoadable。同样的问题。
  3. 将 getMorePosts 逻辑放在选择器中。同样的问题。
  4. 将 getMorePosts 逻辑放入 useRecoilCallback。引发嵌套钩子错误。

我错过了什么?

4

0 回答 0