Recoil JS 组件在没有状态更新的情况下重新渲染

时间:2021-02-22 02:58:32

标签: javascript reactjs next.js recoiljs

问题已解决:归结为状态更新时缺少传播运算符?。

如果您找到此线程并且正在寻找包含过滤的 Recoil 分页实现,那么我用来定位问题的有效 CodeSandbox 示例位于此处:https://codesandbox.io/s/muddy-snow-bguux?file=/pages/index.js

我一直在尝试使用 Recoil 进行分页,但被卡住了。似乎正在发生的事情是我订阅的组件正在重新渲染而没有更新的状态。

在我的索引页面上,我使用 NextJS SSR 从 Firebase 获取初始负载。该负载作为道具传递给页面组件,然后作为数组存储在 Recoil 原子中。

内容使用 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>
    </>
  )
}

反冲原子看起来像这样:

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 原子是 filtersJobState 选择器的依赖项。使用 useRecoilState 设置 filterState 会触发 JobFeed 组件的重新渲染,没有任何问题(但它是同步的)。

但是,当 async getMorePosts 函数更新 jobState 时,组件会重新渲染并抛出错误“TypeError: Cannot read property '0' of undefined。”

当我从 getMorePosts 调用 setState 之后,在 console.log jobState 之后,我会收到来自前一个状态的值。

我试过了:

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

我错过了什么?

0 个答案:

没有答案