useLazyQuery and useQuery onCompleted not running when variable doesn't change or is changed to null

Viewed 5056

What i want to do: I have a few category toggle buttons. I want to show only the data of the categories that has been checked.

My approach: The function for showing the filtered data checks the array of buttons, see which one is ticked, then reads the cache of the query.

const getFilteredData = () => {
    try {
      const freshFilteredData = []
      for (var i = 0; i < chipData.length; i ++){
          if (chipData[i].clicked){
            const itineraryData = client.readQuery({
              query: GET_ITINERARY,
              variables: {itineraryId : chipData[i].label}
            })
            const wantedData = itineraryData.getItinerary.dayPlans;
            freshFilteredData.push(wantedData)
          }
      }
      setFilteredData(freshFilteredData)
    } catch (err) {
      console.log(err)
    }
  }

However, cache can only be read when a query has been fired. So I use an if-else statement to decide when to call the getFilteredData function. If the button is just checked, then I call the query (when it's completed it will call the getFilteredData function), if the button is unchecked, I call the getFilteredData directly.

  const toggleClick = clickedChip => () => {
    const chipClone = [...chipData];
    const objectIndex = chipData.findIndex(
      chip => chip.key === clickedChip.key
    );
    chipClone[objectIndex].clicked = !chipData[objectIndex].clicked;
    setChipData(chipClone);

    if (chipClone[objectIndex].clicked) {
      getItinerary({variables: {itineraryId: clickedChip.label}});
    } else {
      console.log('fire2')
      getFilteredData();
    }
  };

And here's my lazy query:

const [getItinerary] = useLazyQuery(GET_ITINERARY, {
    onCompleted(data){
      console.log('fired');
      getFilteredData()
    }
  });

My problem: if I clicked on the same button thrice.

  1. click (runs query)
  2. unclicked (doesn't run query)
  3. clicked (doesn't run query, so doesn't run getFilteredData function, because query doesn't run onCompleted. And since it is clicked, it doesn't execute the 'else' statement either, so getFilteredData is not run at all)

That screws up the filtering.

I could get rid of the if-else statement and execute the getFilteredData function at two places:

const toggleClick = clickedChip => () => {
    const chipClone = [...chipData];
    const objectIndex = chipData.findIndex(
      chip => chip.key === clickedChip.key
    );
    chipClone[objectIndex].clicked = !chipData[objectIndex].clicked;
    setChipData(chipClone);

    getItinerary({variables: {itineraryId: clickedChip.label}});
    getFilteredData();
  }; 

This will cover all bases, but I was wondering if there's a more elegant way to do it?

1 Answers

the thing you have missed is apollo by defaults stores the result of all issued query in its local cache, any subsequent call to a query fetches the result from cache(unless you have changed the fetch policy).

instead of we manually checking whether its a first time call, delegate that responsibility to apollo.

My recommendation for the flow would be

1) maintain a state variable for the clicked chip label

2) change the above label in the onClick callback of the chip, that will fire the query

3) let apollo decide whether its a first time call or subsequent call

 const [chipLabel, setChipLabel] = useState(<Initial label here>) // pass the initial value 
//that query expects

    // pass the state variable above in the query
    const itineraryData = useQuery({
                  query: GET_ITINERARY,
                  variables: {itineraryId : chipLabel}
                })

    // all the toggle button should have a click handler like below
    onClick={() =>{//call the setChipLabel}}

Open the network tabs, click any filter button multiple times, only during the first click a n/w call will be issued. Apollo client is smart to read from the cache, if the data is already available, by default the fetch policy is "cache-first".

Related