My Custom useScrollPosition Hook isn't Working in Next JS, When I'm Trying to Fetch the Next Page of Results

huangapple go评论79阅读模式
英文:

My Custom useScrollPosition Hook isn't Working in Next JS, When I'm Trying to Fetch the Next Page of Results

问题

以下是翻译好的部分:

详情

我正在使用 tRPC 并使用 useInfiniteQuery 来创建 Next.js 版 Twitter 克隆中的无限滚动效果。我相信它正在正常工作。我已经进行了测试,可以获取下一页的推文。

然而,我已经制作了一个自定义钩子,用于检查用户当前在页面上的位置,但状态似乎根本没有更新。它只会在页面加载时获取滚动位置,然后停止更新。

Feed 组件的代码

// 依赖项
import Link from 'next/link';
import { useState, useEffect } from 'react';

// API
import { api } from '~/utils/api';

// 组件
import { LoadingSpinner } from '~/components/LoadingSpinner';
import { TweetView } from '~/components/TweetView';

function useScrollPosition() {
  const [scrollPosition, setScrollPosition] = useState(0);

  const handleScroll = () => {
    const height =
      document.documentElement.scrollHeight -
      document.documentElement.clientHeight;
    const winScroll =
      document.body.scrollTop || document.documentElement.scrollTop;

    const scrolled = (winScroll / height) * 100;
    setScrollPosition(scrolled);
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return scrollPosition;
}

// Feed 限制
const LIMIT = 10;

export const Feed = () => {
  const {
    data,
    isLoading: tweetsLoading,
    hasNextPage,
    fetchNextPage,
    isFetching,
  } = api.tweet.getAll.useInfiniteQuery(
    {
      limit: LIMIT,
    },
    {
      getNextPageParam: (lastPage) => lastPage.nextCursor,
    }
  );

  const scrollPosition = useScrollPosition();

  const tweets = data?.pages.flatMap((page) => page.tweetsWithUsers) ?? [];
  console.log(scrollPosition);

  useEffect(() => {
    const nextTweetPage = async () => {
      // 如果滚动位置在 x 处,则获取下一页的数据
      if (scrollPosition > 90 && hasNextPage && !isFetching) {
        await fetchNextPage();
      }
    };
    nextTweetPage().catch((e) => console.log(e));
  }, [scrollPosition, hasNextPage, isFetching, fetchNextPage]);

  if (tweetsLoading) {
    return (
      <div className="mt-4 flex h-screen items-center justify-center">
        <LoadingSpinner size={40} />
      </div>
    );
  }

  if (!data) {
    return (
      <div className="flex flex-col items-center justify-center gap-6 p-6 text-center">
        <h3>{`嗯... 获取这些推特出现问题,请尝试刷新?`}</h3>
        <Link
          href="/"
          className="rounded-full bg-bright-pink py-2.5 px-3.5 text-base font-bold text-white shadow-sm hover-bg-pink-700 focus-visible-outline focus-visible-outline-2 focus-visible-outline-offset-2 focus-visible-outline-bright-pink"
        >
          主页
        </Link>
      </div>
    );
  }

  return (
    <div className="h-full">
      {tweets.map((fullTweet) => (
        <TweetView
          tweetData={{ ...fullTweet }}
          key={fullTweet.tweet.id}
          input={{ limit: LIMIT }}
        />
      ))}
    </div>
  );
};

我已经做的

我已经尝试找到其他解决方案,比如使用 useCallback,并查看堆栈溢出上的其他类似问题,但没有任何解决方案有效。

我已经在这个问题上卡住一段时间了,如果这个问题不解决,我就无法继续项目。

英文:

Details

I'm using tRPC and using useInfiniteQuery to create the infinite scroll effect in a nextjs twitter clone. I believe that is working fine. I've tested it and I can fetch the next page of tweets.

However I've made a custom hook that will check where the users current position is on the page and the state doesn't appear to be updating at all. It will get the scroll position on page load and that's it.

Code for Feed Component

// Dependencies
import Link from &#39;next/link&#39;;
import { useState, useEffect } from &#39;react&#39;;

// API
import { api } from &#39;~/utils/api&#39;;

// Components
import { LoadingSpinner } from &#39;~/components/LoadingSpinner&#39;;
import { TweetView } from &#39;~/components/TweetView&#39;;

function useScrollPosition() {
  const [scrollPosition, setScrollPosition] = useState(0);

  const handleScroll = () =&gt; {
    const height =
      document.documentElement.scrollHeight -
      document.documentElement.clientHeight;
    const winScroll =
      document.body.scrollTop || document.documentElement.scrollTop;

    const scrolled = (winScroll / height) * 100;
    setScrollPosition(scrolled);
  };

  useEffect(() =&gt; {
    window.addEventListener(&#39;scroll&#39;, handleScroll);

    return () =&gt; {
      window.removeEventListener(&#39;scroll&#39;, handleScroll);
    };
  }, []);

  return scrollPosition;
}

// Feed Limit
const LIMIT = 10;

export const Feed = () =&gt; {
  const {
    data,
    isLoading: tweetsLoading,
    hasNextPage,
    fetchNextPage,
    isFetching,
  } = api.tweet.getAll.useInfiniteQuery(
    {
      limit: LIMIT,
    },
    {
      getNextPageParam: (lastPage) =&gt; lastPage.nextCursor,
    }
  );

  const scrollPosition = useScrollPosition();

  const tweets = data?.pages.flatMap((page) =&gt; page.tweetsWithUsers) ?? [];
  console.log(scrollPosition);

  useEffect(() =&gt; {
    const nextTweetPage = async () =&gt; {
      // Get the next page of data if the scroll position is at x
      if (scrollPosition &gt; 90 &amp;&amp; hasNextPage &amp;&amp; !isFetching) {
        await fetchNextPage();
      }
    };
    nextTweetPage().catch((e) =&gt; console.log(e));
  }, [scrollPosition, hasNextPage, isFetching, fetchNextPage]);

  if (tweetsLoading) {
    return (
      &lt;div className=&quot;mt-4 flex h-screen items-center justify-center&quot;&gt;
        &lt;LoadingSpinner size={40} /&gt;
      &lt;/div&gt;
    );
  }

  if (!data) {
    return (
      &lt;div className=&quot;flex flex-col items-center justify-center gap-6 p-6 text-center&quot;&gt;
        &lt;h3&gt;{`Hmmm... Something went wrong getting these twoots, try refreshing?`}&lt;/h3&gt;
        &lt;Link
          href=&quot;/&quot;
          className=&quot;rounded-full bg-bright-pink py-2.5 px-3.5 text-base font-bold text-white shadow-sm hover:bg-pink-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-bright-pink&quot;
        &gt;
          Home
        &lt;/Link&gt;
      &lt;/div&gt;
    );
  }

  return (
    &lt;div className=&quot;h-full&quot;&gt;
      {tweets.map((fullTweet) =&gt; (
        &lt;TweetView
          tweetData={{ ...fullTweet }}
          key={fullTweet.tweet.id}
          input={{ limit: LIMIT }}
        /&gt;
      ))}
    &lt;/div&gt;
  );
};

What I've Done

I've tried finding other solutions, like useCallback, and looking at other similar questions on stack overflow but none of the solutions are working.

Been stumped on this for a while and I can't move forward on the project without this working.

答案1

得分: 0

当前临时解决方案

我今天需要确实让这个工作。我使用了一个名为react-intersection-observer的包。创建了一个div,它将在动态流的底部渲染。一旦这个带有交集引用的div进入视野,下一页将被获取。

我有点恼火,无论出于什么原因,我都无法让hooks正常工作。但这样做完全可以胜任。

如果有人遇到困难,以下是代码。

动态流组件

// 依赖项
import Link from 'next/link';
import { useEffect } from 'react';
import { useInView } from 'react-intersection-observer';

// API
import { api } from '~/utils/api';

// 组件
import { LoadingSpinner } from '~/components/LoadingSpinner';
import { TweetView } from '~/components/TweetView';

// 动态流限制
const LIMIT = 10;

export const Feed = () => {
  const {
    data,
    isLoading: tweetsLoading,
    hasNextPage,
    fetchNextPage,
    isFetching,
  } = api.tweet.getAll.useInfiniteQuery(
    {
      limit: LIMIT,
    },
    {
      getNextPageParam: (lastPage) => lastPage.nextCursor,
    }
  );
  const { ref, inView } = useInView({ threshold: 0 });

  const tweets = data?.pages.flatMap((page) => page.tweetsWithUsers) ?? [];

  useEffect(() => {
    const nextTweetPage = async () => {
      // 如果进入视野,且有下一页且未在获取数据时,获取下一页数据
      if (inView && hasNextPage && !isFetching) {
        await fetchNextPage();
      }
    };
    nextTweetPage().catch((e) => console.log(e));
  }, [inView, hasNextPage, isFetching, fetchNextPage]);

  if (tweetsLoading) {
    return (
      <div className="mt-4 flex h-screen items-center justify-center">
        <LoadingSpinner size={40} />
      </div>
    );
  }

  if (!data) {
    return (
      <div className="flex flex-col items-center justify-center gap-6 p-6 text-center">
        <h3>{`哎呀...获取这些推文出了些问题,尝试刷新一下?`}</h3>
        <Link
          href="/"
          className="rounded-full bg-bright-pink py-2.5 px-3.5 text-base font-bold text-white shadow-sm hover-bg-pink-700 focus-visible-outline focus-visible-outline-2 focus-visible-outline-offset-2 focus-visible-outline-bright-pink"
        >
          主页
        </Link>
      </div>
    );
  }

  return (
    <>
      <div className="h-full">
        {tweets.map((fullTweet) => (
          <TweetView
            tweetData={{ ...fullTweet }}
            key={fullTweet.tweet.id}
            input={{ limit: LIMIT }}
          />
        ))}
      </div>
      <div ref={ref}></div>
      {isFetching && (
        <div className="flex h-auto w-full items-center justify-center p-4">
          <LoadingSpinner size={40} />
        </div>
      )}
    </>
  );
};
英文:

Current Hacky Solution

I needed to get this working today realistically. I used a package called react-intersection-observer. created a div that will render at the bottom of the feed. Once that div with the intersection ref comes into view the next page will be fetched.

I'm a bit annoyed that I can't get the hooks to work for whatever reason. But this does the job just fine.

Here's the code if anyone else is struggling.

Feed Component

// Dependencies
import Link from &#39;next/link&#39;;
import { useEffect } from &#39;react&#39;;
import { useInView } from &#39;react-intersection-observer&#39;;

// API
import { api } from &#39;~/utils/api&#39;;

// Components
import { LoadingSpinner } from &#39;~/components/LoadingSpinner&#39;;
import { TweetView } from &#39;~/components/TweetView&#39;;

// Feed Limit
const LIMIT = 10;

export const Feed = () =&gt; {
  const {
    data,
    isLoading: tweetsLoading,
    hasNextPage,
    fetchNextPage,
    isFetching,
  } = api.tweet.getAll.useInfiniteQuery(
    {
      limit: LIMIT,
    },
    {
      getNextPageParam: (lastPage) =&gt; lastPage.nextCursor,
    }
  );
  const { ref, inView } = useInView({ threshold: 0 });

  const tweets = data?.pages.flatMap((page) =&gt; page.tweetsWithUsers) ?? [];

  useEffect(() =&gt; {
    const nextTweetPage = async () =&gt; {
      // Get the next page of data if the intersection comes into view
      if (inView &amp;&amp; hasNextPage &amp;&amp; !isFetching) {
        await fetchNextPage();
      }
    };
    nextTweetPage().catch((e) =&gt; console.log(e));
  }, [inView, hasNextPage, isFetching, fetchNextPage]);

  if (tweetsLoading) {
    return (
      &lt;div className=&quot;mt-4 flex h-screen items-center justify-center&quot;&gt;
        &lt;LoadingSpinner size={40} /&gt;
      &lt;/div&gt;
    );
  }

  if (!data) {
    return (
      &lt;div className=&quot;flex flex-col items-center justify-center gap-6 p-6 text-center&quot;&gt;
        &lt;h3&gt;{`Hmmm... Something went wrong getting these twoots, try refreshing?`}&lt;/h3&gt;
        &lt;Link
          href=&quot;/&quot;
          className=&quot;rounded-full bg-bright-pink py-2.5 px-3.5 text-base font-bold text-white shadow-sm hover:bg-pink-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-bright-pink&quot;
        &gt;
          Home
        &lt;/Link&gt;
      &lt;/div&gt;
    );
  }

  return (
    &lt;&gt;
      &lt;div className=&quot;h-full&quot;&gt;
        {tweets.map((fullTweet) =&gt; (
          &lt;TweetView
            tweetData={{ ...fullTweet }}
            key={fullTweet.tweet.id}
            input={{ limit: LIMIT }}
          /&gt;
        ))}
      &lt;/div&gt;
      &lt;div ref={ref}&gt;&lt;/div&gt;
      {isFetching &amp;&amp; (
        &lt;div className=&quot;flex h-auto w-full items-center justify-center p-4&quot;&gt;
          &lt;LoadingSpinner size={40} /&gt;
        &lt;/div&gt;
      )}
    &lt;/&gt;
  );
};

huangapple
  • 本文由 发表于 2023年4月7日 01:20:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75952180.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定