在呈现组件之前仅调用一次 Hook (Meteor/React/Apollo/Graphql)

Calling a Hook only once before the component is rendered (Meteor/React/Apollo/Graphql)

我正在尝试创建在您向下滑动时更新的内容提要。我的逻辑是我需要在呈现提要之前获取所需的数据,并且每次我向下滑动页面时点击第 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 钩子被触发了。这只会继续获取更多数据,因为组件将不断重新呈现。

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

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));

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

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));