【问题标题】:Calling a Hook only once before the component is rendered (Meteor/React/Apollo/Graphql)在渲染组件之前只调用一次 Hook (Meteor/React/Apollo/Graphql)
【发布时间】:2021-04-04 03:03:09
【问题描述】:

我正在尝试创建一个在您向下滑动时更新的内容提要。我的逻辑是我需要在呈现提要之前获取所需的数据,并且每次我在向下滑动页面时点击第 n 个元素时都会获取新数据。所以我想有一个布尔状态变量来检测第一次渲染,然后我将其设置为 false 以不再运行查询。

const Feed = (props) => {
    const [feedArray, setFeedArray] = useState([]);
    const [firstRender, setFirstRender] = useState(true);
    const FEED_QUERY = gql`
      query getFeedArray {
        getFeedArray
      }
    `;

    if (firstRender) {
      const { loading, error, data } = useQuery(FEED_QUERY);
      if (loading) return <Loading/>;
      if (error) return `Error! ${error}`;
      feedArray.push(...data.getFeedArray);
      setFirstRender(false);
    }

    return (
      <RenderredComponent />
    );
}
export default withApollo(withRouter(Feed));

显然,这会导致错误 (Unexpected Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.),因为 useQuery 挂钩仅在 if 语句中调用一次。

我尝试了不同的方法来执行此操作,因为我在渲染组件之前需要这些数据。我尝试使用useEffect()props.client.query() 但这不起作用,因为它允许gql 查询同步运行,因此仅使用第二次渲染上的数据更新DOM,并且我无法阻止基于loading 状态。我不能将 useQuery() 放在 useEffect() 中,因为它也会引发错误(钩子中的钩子)。

如果我将 useQuery 钩子放在 if 语句之外,它可以工作,但会在每次渲染时运行,这是不必要的额外调用。对于上下文 - 由于React Swipeable Views 的工作原理,当我向下滚动时,渲染经常发生。

所以简单地说 - 我怎样才能在组件被渲染之前只运行一次 useQuery 钩子?

感谢您的帮助,谢谢!

编辑:下面的屏幕截图说明正在发生的事情,

第一个屏幕截图显示了它的渲染时间,我将收到的数据打印到控制台。我硬编码了一个 5 元素数组,以便在此示例中重复返回。然后当我向上滑动时,组件正在重新渲染,虽然我不需要获取更多数据,但这是因为 useQuery 钩子被触发了。这只会继续获取更多数据,因为组件会不断地重新渲染。

另外 - 我需要将我的数据存储在 state 中,因为我将添加到它,并且我的组件没有其他方法可以记住上次获取的内容。

EDIT2:接受的答案有效。但是我对useQuery 的工作原理有一个误解,我应该在调用查询时在服务器上进行一些日志记录。尽管所有 DOM 更新,这都会调用一次且仅一次的查询。此外,它只将它添加到我的状态一次,因为在第一次成功渲染后,firstRender 状态设置为 false。不确定这是否理想,但如果我想在我的服务器上记录调用,我会这样做。

const Feed = (props) => {
    const [feedArray, setFeedArray] = useState([]);
    const [firstRender, setFirstRender] = useState(true);
    const FEED_QUERY = gql`
      query getFeedArray {
        getFeedArray
      }
    `;

    const { loading, error, data } = useQuery(FEED_QUERY);
    if (loading) return <Loading/>;
    if (error) return `Error! ${error}`;
    
    if (firstRender) {
      feedArray.push(...data.getFeedArray);
      setFirstRender(false);
    }

    return (
      <RenderredComponent />
    );
}
export default withApollo(withRouter(Feed));

【问题讨论】:

  • 挂钩规则(无条件挂钩)已损坏,您可以使用skip 选项阻止进一步调用...但无需将查询中的数据复制到状态
  • 可能是这个组件之外的其他问题,不仅重新渲染(什么原因?)而且还创建和删除了? (在滑动组件中没有key 渲染?)......即使fetchMore 也不需要状态数据,请参阅阿波罗分页文档
  • 我会说swipeable不是为了获取更多[用例],搜索其他虚拟列表渲染工具(内部渲染)[支持滑动]
  • @xadm 我想使用 framer-motion 进行页面滑动,但是由于滑动时的状态更改而出现冻结(不是 framer 运动的原生,而是因为我需要进行一些额外的状态更改)。见codesandbox.io/s/framer-motion-pages-shmd9。 Swipeable Views 对我来说效果更好,我将使用 onTransitionEnd 函数触发更多查询。
  • 另外为了更清晰 - 分页可能无法正常工作,因为我是随机获取数据,所以它不是按顺序排列的。

标签: javascript reactjs graphql apollo


【解决方案1】:

您不需要使用状态。只需使用查询数据

const Feed = props => {
  const FEED_QUERY = gql`
    query getFeedArray {
      getFeedArray
    }
  `;

  const { loading, error, data } = useQuery(FEED_QUERY);

  if (loading) return <Loading />;
  if (error) return `Error! ${error}`;

  return <RenderredComponent data={data.getFeedArray} />;
};
export default withApollo(withRouter(Feed));

您可以在任何地方使用data

更新

如果您出于某种原因想要使用状态,可以将其与useEffect 一起使用。将data.getFeedArray 添加到useEffect 依赖项中,以便在更改时设置feedArray 状态。

const Feed = props => {
  const [feedArray, setFeedArray] = useState([]);
  const FEED_QUERY = gql`
    query getFeedArray {
      getFeedArray
    }
  `;

  const { loading, error, data } = useQuery(FEED_QUERY);
  useEffect(() => {
    setFeedArray([...data.getFeedArray])
  }, [data.getFeedArray])

  if (loading) return <Loading />;
  if (error) return `Error! ${error}`;

  return <RenderredComponent data={feedArray} />;
};
export default withApollo(withRouter(Feed));

【讨论】:

  • 谢谢,但我不担心收到的数据,我担心查询会不断被调用,即使我不需要每次都调用它-使成为。我已经用截图更新了我的主要帖子,以说明我的意思。我需要将我的查询数据保存在状态中,因为我将对其进行添加,因此我需要它知道之前的数据。我只需要它在第一次渲染之前运行一次,然后我将在单独的函数中获取更多数据。
  • 我已经根据您的关注更新了我的答案。
  • 我不得不将它修改为 if (data) setFeedArray([...data.getFeedArray]) 并且 useEffect() 的第二个参数必须是 [data],因为在加载查询时 data 为空/未定义。我有一个问题 - useQuery() 只调用一次吗?当我在if (loading) 语句上方console.log(loading) 时,它仅在第一次渲染时返回true,随后它始终为false。
猜你喜欢
  • 2019-02-21
  • 1970-01-01
  • 2017-11-03
  • 1970-01-01
  • 2021-01-06
  • 1970-01-01
  • 2018-02-10
  • 1970-01-01
  • 2021-04-25
相关资源
最近更新 更多