Urql: cache.writeFragment does not rerender component

Created on 6 Mar 2020  Â·  26Comments  Â·  Source: FormidableLabs/urql

When updating cached data with cache write fragment

I dont see any component using that data beeing refreshed.
I can see the records beeing updated using console.log(cache)

Is this intended?

bug 🐛

Most helpful comment

All 26 comments

If at the same update i execute along with writeFragment an updateQuery (In an irrelevant query) then everything works as expected.

Hiya, can you give us more details on this please like a reproduction or some code snippets. Also have you checked your console for any warnings from Graphcache?

Hi

No warnings / errors on console.

See below some pseudo code for what i am trying to achive.

When update balance is pressed the new users balances is not shown. Even though there is an update function in graphcache

Also as said on the previous message the record i updated (I inspect it via console.log(Cache) after upgrade fragment)

const BalancesFragment = gql`
  fragment BalancesFragment on User {
    balances {
      currency
      value
    }
  }
`;

const UserQuery = gql`
  query User($userId: ID!) {
    user(id: $userId) {
      id
      name
      ...BalancesFragment
    }
  }
`;

const UpdateBalance = gql`
  mutation SendBalance($input: SendBalanceInput!) {
    sendBalance(input: $input) {
      userId
      balances {
        currency
        value
      }
    }
  }
`;

export const User = ({ userId }) => {
  const [{ data }] = useQuery({ query: UserQuery, variables: { userId } });
  const [, updateBalances] = useMutation(UpdateBalance);
  return (
    <>
      <h1>{data.name}</h1>
      {data.balances.map(balance => (
        <span>{`${balance.currency}:${balance.value}`}</span>
      ))}
      <button
        onClick={() =>
          updateBalances({
            input: { userId, balances: [{ currency: "EUR", value: 10 }] }
          })
        }
      >
        Update balance
      </button>
    </>
  );
};

const setupUrql = () => {
  return createClient({
    url: "A url",
    exchanges: [
      dedupExchange,
      cacheExchange({
        updates: {
          Mutation: {
            sendBalance: (result, args, cache) => {
              const {
                sendBalance: { userId, balances }
              } = result;
              cache.writeFragment(BalancesFragment, {
                id: userId,
                __typename: "User",
                balances
              });
            }
          }
        }
      }),
      fetchExchange
    ]
  });
};

Forgot something.Dont know if it makes much difference

      cacheExchange({
        keys: {
          Balance: () => null,
        }
      })

That’s indeed a little odd. I’ll have to investigate this further. This might be happening due to how formatting is being skipped for fragments right now, but I’m not quite sure

One more thing as said earlier if at the same update any other update is made using updateQuery then all is fine

Before I jump onto investigating this, can you maybe give me a little pointer please? I’m curious whether this only affects the new release of @urql/[email protected] or 2.1.1 as well

Both

@zenios I've written a test for this now and I can't reproduce this behaviour unfortunately. It's a slightly simplified case, but covers the exact same methods and code paths that you're describing. Feel free to take a look and modify it :+1: https://github.com/FormidableLabs/urql/pull/578

I can totally see this failing if the data doesn't match what you're expecting, so maybe it's worth double checking whether this isn't something on your data first. But I'd at least expect an Invalid key warning, since I can't reproduce a totally failing behaviour yet.

The major difference from my example is that in my case i am updating an
array while in your case its only a single element.

I will try to create a reproducer and let you know

On Sat, 7 Mar 2020, 10:59 Phil PlĂŒckthun, notifications@github.com wrote:

@zenios https://github.com/zenios I've written a test for this now and
I can't reproduce this behaviour unfortunately. It's a slightly simplified
case, but covers the exact same methods and code paths that you're
describing. Feel free to take a look and modify it 👍 #578
https://github.com/FormidableLabs/urql/pull/578

I can totally see this failing if the data doesn't match what you're
expecting, so maybe it's worth double checking whether this isn't something
on your data first. But I'd at least expect an Invalid key warning, since
I can't reproduce a totally failing behaviour yet.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/FormidableLabs/urql/issues/574?email_source=notifications&email_token=AAAV3YJVK6FYWWYPUDLQFETRGIEI5A5CNFSM4LDH5TU2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEODTPVQ#issuecomment-596064214,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAAV3YIAX6WZTTNWDJJYMKDRGIEI5ANCNFSM4LDH5TUQ
.

@zenios in theory that won’t matter, since either way we’re getting to the Balance array via a User. We keep track of updates by “dependencies” these are a list of entity keys that a query has read or a result has written. So assuming that we should see that:

  • the initial user query has a dependency on the user, e.g. dependencies = Set { ‘User:xyz’, ... }
  • the balances will be embedded but they’ll take on a key that looks something like User:xyz.balances...
  • the mutation writes to the user, so User:xyz will update (it doesn’t matter if this is automatic or manual via writeFragment or another method)
  • this causes the original query which depended on the same user to update. We just need _some_ overlap of keys, so the balances wouldn’t even matter in this example

Hope this helps 👍 I’m sure we can figure out what’s going on here. Even if this is expected behaviour, maybe there’s a new useful warning we could add as a result of this. Or maybe we do find this to be a bug 🐛😅 Either way, it’s still a little fuzzy for me

So i went down the rabit hole trying to find what is happening.

I am still investigation but i find something that looks rather odd. Actually its a race condition

The race condition is here

https://github.com/FormidableLabs/urql/blob/cc372aed9fe1cc2036a540e37ac78f65dfe6ed8e/exchanges/graphcache/src/operations/write.ts#L63

Between startWrite and clearDataState

I found out that if two queries are executed such as

useQuery({query:QUERY1);
useQuery({query:QUERY2);

There is a possibility that the dependencies calculated of one query will include dependencies of the second query.

I am not sure if this is intended behaviour.

That’s highly odd! This is all synchronous.
But this may be expected due to our new commutative reordering. The problem is, reordering lead to _more_ dependencies on one query, but that would lead to a potentially additional update, which we think is fine, it should never lead to a missing update 😅

Actually its not all synchronous. Inside clearDataState you have two defer calls.

Odd or not i can see it

In some cases write dependencies are

query 1 = 8
query 2 = 5

And other cases

query 2 = 5
query 1 = 13

The more weired think is that writeFragment only works in cases that one query leaks write dependencies to the second query :)

Yea, the garbage collection is deferred and persistence (which isn’t on by default) is deferred. Either of them don’t operate on dependencies however, and dependencies are kept synchronously. initDataState provides them, then synchronous reads/writes executes, then clearDataState flushes all states and unsets all dependencies.

It’d be really beneficial if you could isolate your behaviour to the tests. But what I’m thinking is, if you’re seeing different dependencies (and you didn’t post the full keys or context here?) then you’re likely also getting different data 😅

So the signs do still point to some kind of usage issue unfortunately 😅🙏

Edit; the dependencies make sense. When the queries arrive in reverse order then the data will still be applied in the “right” order, meaning that the second query always overrides data from the first. I just realised you posted the length of dependencies. If you check this you have 8, 5. When both queries complete the temporary “fixed” order will be flushed since all queries completed. This means that the second query that came in in reverse will have the dependencies of the first 5, 8+5

Edit 2: I’ll work on a small patch, that removes the above behaviour. It’s generally irrelevant but I suppose it is indeed confusing

One more.

The reason that the element is not updated after writeFragment is because reflock for that object is never released.

Now i need to find why reflock is increased more than what it should

In theory that shouldn’t be the cause either. refLock only prevents GC from deleting an entity when something else is still pointing to it 😅 nothing else. It doesn’t affect the data itself. You can comment out the GC logic and no behaviour will change

Grrrrrrr :)

I’m sorry to see you having a bad time â™„ïžđŸ˜… let’s maybe take a step back? If you’re using the dev tools or debug exchange you should be able to see whether after the mutation you see your operation being reexecuted. That’d be a good sanity check.

If it is, then somehow your query is being reexecuted but isn’t seeing the new data.

If it isn’t, then we must have non-overlapping dependencies is what I’d assume. So maybe we could double check that we are in fact writing the right fragment. If you’re not seeing a warning then we must assume you are (unless the id isn’t correct). So at that point we’d have to explore deeper again

The query is beeing reexecuted but it returns the previous data (Before write fragment)

I can see in the cache that

cache.records.base contains the updated object (After write fragment)
but
cache.records.optimistic contains an entry with the object before write fragment

Ok, that’s a step closer. So we could check what that optimistic key is and which operation it is from (optimistic key is the same as the operation key)

But I suspect it’s from a query layer? In that case though the behaviour shouldn’t actually occur in 2.1.1; also this can only happen if the mutation completes before all queries with that same entity do... which is already a tricky edge case in theory, since it’s hard to do the right thing in this case, due to a preexisting race condition

Optimistic is from the original query (cache-and-network) which is the same one i am updating using writeFragment

As a side note.

updateQuery behaves exactly just like writeFragment
i.e No updates

This actually did turn out to be a regression in @urql/[email protected], which didn't exist in v2.1.1 since we introduced commutativity. There were some cases where this didn't work as intended, which I suppose was to be expected, since it was a rather new feature. We're now planning to change its behaviour a little again soon, but for now we've fixed the bugs :tada:

The fixes are included in v2.2.2.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dotansimha picture dotansimha  Â·  4Comments

Andrew-Talley picture Andrew-Talley  Â·  4Comments

nicollecastrog picture nicollecastrog  Â·  3Comments

pix2D picture pix2D  Â·  4Comments

capaj picture capaj  Â·  5Comments