0

I have a page which displays one record from the database. I'm using React, GraphQL, TypeScript and urql.

There are two buttons, "previous" and "next." I use the createdAt field as the cursor and I change the input using React's useState (see setVariables). The buttons look like this:

<IconButton
  isLoading={fetching}
  aria-label="Load next"
  icon={<ArrowBackIcon />}
  onClick={() => {
    setVariables({
      cursor: record.createdAt,
      direction: "next",
    });
  }}
/>

The data is fetched like this (using a custom generated hook from the GraphQL Code Generator):

const [{ data, fetching, error }] = useReadPronunciationQuery({
  pause: variables.id === -1,
  variables: { options: variables },
});

I initially set variables using useEffect like this:

useEffect(() => {
  if (router.isReady) {
    setVariables({
      id:
        typeof router.query.id === "string" ? parseInt(router.query.id) : -1, // Matches id in [id].tsx
    });
  }
}, [router.isReady]);

The query accepts either record ID or a cursor and direction. (-1 stands for invalid ID from the search bar.)

There are two types of records, Meaning and Pronunciation. Switching between previous and next works for Meaning, but not for Pronunciation.

UPDATE: I noticed that it actually doesn't work with all fields of the Meaning table either. So the problem is with the way I update the component.

Here's what happens with Pronunciation:

  • when I go from A to B, it works fine,
  • when I go back to A, it works fine,
  • but when I go to B again, the record data doesn't update for the entire page.

Importantly, when I put the record data inside a Heading element, for example, it does update.

When I go to B again, I know that the front end uses the cache; it doesn't send a request to the server. The data it selects is correct (I see it in the console), it just doesn't show.

The page has a table which has editable components inside like this:

<Tr key={record.id}>
  <Td>
    <EditableHeadwordP record={record} />
  </Td>
  <Td>
    <EditableTranscriptionP record={record} />
  </Td>
...

But I thought that if the table and the nested editable elements were the issue, I'd be having a problem with Meaning too, but I don't. These elements are virtually the same for both record types.

UPDATE: I have an issue with Meaning too.

To summarize:

  • the GraphQL mutations readMeaning and readPronunciation are basically the same,
  • so are both pages that display their results,
  • I assume the mutation works correctly (judging by the console.log's),
  • but the page doesn't update the record data inside the table and editable elements.

Any ideas? Thanks in advance!

vls9
  • 113
  • 10
  • May need a manual updater: https://formidable.com/open-source/urql/docs/graphcache/cache-updates/ – Shah Mar 22 '23 at 02:49
  • Particularly the part about manually updating (the first block with code samples) – Shah Mar 22 '23 at 02:57
  • Added update: I noticed that it actually doesn't work with all fields of the `Meaning` table either. So the problem is with the way I update the component. – vls9 Mar 22 '23 at 11:43
  • 1
    @Shah I fixed the urql warning, I think it's actually about how I update the component. For example I have one editable element/component (it displays and image) that uses `useEffect`, and it does update the data. The simpler elements don't. So I suspect it has something to do with the way I use `useState` and `useEffect` – vls9 Mar 22 '23 at 11:46
  • You can combine the useState and useEffect into a useMemo then to shorten the code but also memorize the data – Shah Mar 22 '23 at 15:55

1 Answers1

1

Answering my own question since I'm pretty sure I found the solution in this answer.

So to be updated correctly, child components must have a key attribute.

My output looked something like this:

<Layout>
  {!record ? null : (
    <VStack>
      <Flex key={record.id}>
        <Heading as="h2" size="xl">
          Meaning: {record.id}
        </Heading>
      </Flex>
      <TableContainer>
        ...
      </TableContainer>
    </VStack>
  )
...

So only the Flex inside the VStack had the key attribute. TableContainer was outside of its scope. I guess without key, React doesn't understand that the child component needs to be updated. This would explain why Heading was updating correctly.

So I needed to move key to VStack like this:

<VStack key={record.id}>

If anyone has a deeper explanation or further context, feel free to comment or edit this answer!

vls9
  • 113
  • 10
  • You could also change your setter of your useState to work async based on prev value Example: ‘’’’setValue(prev => prev.id = someOtherValue)’’’ – Shah Mar 22 '23 at 22:01
  • 1
    @Shah thank you for the suggestion, I'll read up on it! – vls9 Apr 09 '23 at 08:19