useLazyQuery causing too many re-renders [Apollo/

2020-04-14 02:00发布

I'm building a discord/slack clone. I have Channels, Messages and users. As soon as my Chat component loads, channels get fetched with useQuery hook from Apollo.

By default when a users comes at the Chat component, he needs to click on a specific channels to see the info about the channel and also the messages.

In the smaller Channel.js component I write the channelid of the clicked Channel to the apollo-cache. This works perfect, I use the useQuery hooks @client in the Messages.js component to fetch the channelid from the cache and it's working perfect.

The problem shows up when I use the useLazyQuery hook for fetching the messages for a specific channel (the channel the user clicks on).

It causes a infinite re-render loop in React causing the app to crash.

I've tried working with the normal useQuery hook with the skip option. I then call the refetch() function when I need it. This 'works' in the sense of it not giving me infinite loop. But then the console.log() give me this error: [GraphQL error]: Message: Variable "$channelid" of required type "String!" was not provided. Path: undefined. This is very weird because my schema and variables are correct ??

The useLazyQuery does give me infinite loop as said before.

I'm really struggling with the conditionality of apollo/react hooks...

/// Channel.js component ///


const Channel = ({ id, channelName, channelDescription, authorName }) => {
  const chatContext = useContext(ChatContext);

  const client = useApolloClient();
  const { fetchChannelInfo, setCurrentChannel } = chatContext;

  const selectChannel = (e, id) => {
    fetchChannelInfo(true);
    const currentChannel = {
      channelid: id,
      channelName,
      channelDescription,
      authorName
    };
    setCurrentChannel(currentChannel);

    client.writeData({
      data: {
        channelid: id
      }
    });
    // console.log(currentChannel);
  };

  return (
    <ChannelNameAndLogo onClick={e => selectChannel(e, id)}>
      <ChannelLogo className='fab fa-slack-hash' />
      <ChannelName>{channelName}</ChannelName>
    </ChannelNameAndLogo>
  );
};

export default Channel;

/// Messages.js component ///


const FETCH_CHANNELID = gql`
  {
    channelid @client
  }
`;


const Messages = () => {
  const [messageContent, setMessageContent] = useState('');
  const chatContext = useContext(ChatContext);
  const { currentChannel } = chatContext;
  // const { data, loading, refetch } = useQuery(FETCH_MESSAGES, {
  //   skip: true
  // });

  const { data: channelidData, loading: channelidLoading } = useQuery(
    FETCH_CHANNELID
  );

  const [fetchMessages, { data, called, loading, error }] = useLazyQuery(
    FETCH_MESSAGES
  );

  //// useMutation is working
  const [
    createMessage,
    { data: MessageData, loading: MessageLoading }
  ] = useMutation(CREATE_MESSAGE);

  if (channelidLoading && !channelidData) {
    console.log('loading');
    setInterval(() => {
      console.log('loading ...');
    }, 1000);
  } else if (!channelidLoading && channelidData) {
    console.log('not loading anymore...');
    console.log(channelidData.channelid);
    fetchMessages({ variables: { channelid: channelidData.channelid } });
    console.log(data);
  }

I expect to have messages in data from the useLazyQuery ...But instead get this in the console.log():

react-dom.development.js:16408 Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop.

3条回答
Rolldiameter
2楼-- · 2020-04-14 02:26

You could also look at doing the following:

   import debounce from 'lodash.debounce';
   ...

   const [fetchMessages, { data, called, loading, error }] = useLazyQuery(
    FETCH_MESSAGES
  );
  const findMessageButChill = debounce(fetchMessages, 350);
  ...
  } else if (!channelidLoading && channelidData) {
    findMessageButChill({
       variables: { channelid: channelidData.channelid },
    });
  }


查看更多
Luminary・发光体
3楼-- · 2020-04-14 02:30

You could use the called variable return by useLazyQuery.

!called && fetchMessages({ variables: { channelid: channelidData.channelid } });

查看更多
乱世女痞
4楼-- · 2020-04-14 02:31

You call fetchMessages on every render. Try to put fetchMessages in a useEffect :

useEffect(() => {
    if (!channelidLoading && channelidData) {
        fetchMessages();
    }

}, [channelidLoading, channelidData]);

Like that the fetchMessages function only calls when channelidLoading or channelidData is changing.

查看更多
登录 后发表回答