Repository: urql-graphql/urql (ts)

Issue:
null data has been returned after fetch done with valid data when using nested local resolver
### Describe the bug

When using nested local resolver (resolver not under root `Query`), returned data may be `null` even though data has been fetched correctly.

If I changed the line below:

https://github.com/urql-graphql/urql/blob/%40urql/exchange-graphcache%406.3.2/exchanges/graphcache/src/operations/query.ts#L612

```diff
-  } else if (!isOwnedData && prevData === null) {
+  } else if (isOwnedData && prevData === null) {
```

... it seems to work correctly.  
\# I saw that in commit 6031d913732f5e480cdbc2e3c2c537dc7e5d6d42 `skipNull` was changed to `isOwnedData`, but in the only above line `!` was added.

### Reproduction

https://github.com/jet2jet/urql-local-resolver-test

1. Run `npm run start`
2. Open http://localhost:3000
3. When Todo list is displayed, type Todo id to `ID` field and press `Get`
4. Expect behavior: Todo detail data is retrieved and displayed
5. Actual behavior: `Todo is null...` is displayed

### Urql version

urql v4.0.5
@urql/exchange-graphcache v6.3.2

### Validations

- [X] I can confirm that this is a bug report, and not a feature request, RFC, question, or discussion, for which [GitHub Discussions](https://github.com/urql-graphql/urql/discussions) should be used
- [X] Read the [docs](https://formidable.com/open-source/urql/docs/).
- [X] Follow our [Code of Conduct](https://github.com/urql-graphql/urql/blob/main/CODE_OF_CONDUCT.md)

Pull request:
fix(graphcache): Fix typo in owned data null resolution for resolvers
Resolves #3370

## Summary

This fixes a typo in an inverted condition for an owned data check in `resolveResolverResult`. The equivalent check in `resolveLink` is `isOwnedData && prevData === null`, i.e. when a prior result was `null` it keeps the field set to `null`.

Instead, the resolver did the opposite and hence turned a first resolution of a `prevData === null` result into a nulled value, i.e. preventing subsequent results from succeeding.

## Set of changes

- Fix inverted `resolveResolverResult` condition for owned `null` values


Patch:
diff --git a/.changeset/real-ducks-speak.md b/.changeset/real-ducks-speak.md
new file mode 100644
index 00000000..7d7f549c
--- /dev/null
+++ b/.changeset/real-ducks-speak.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-graphcache': patch
+---
+
+Fix a typo that caused an inverted condition, for checking owned data, to cause incorrect results when handling `null` values and encountering them first.
diff --git a/docs/advanced/server-side-rendering.md b/docs/advanced/server-side-rendering.md
index 7fcfc54b..6b227452 100644
--- a/docs/advanced/server-side-rendering.md
+++ b/docs/advanced/server-side-rendering.md
@@ -318,7 +318,7 @@ server-component on the client needs refreshing. You can forcefully
 tell the server to do so by using the Next router and calling `.refresh()`.
 
 ```tsx
-import { useRouter } from 'next/router';
+import { useRouter } from 'next/navigation';
 
 const Todo = () => {
   const router = useRouter();
diff --git a/exchanges/graphcache/src/operations/query.ts b/exchanges/graphcache/src/operations/query.ts
index c335bb0e..c0312c30 100644
--- a/exchanges/graphcache/src/operations/query.ts
+++ b/exchanges/graphcache/src/operations/query.ts
@@ -609,7 +609,7 @@ const resolveResolverResult = (
     return hasChanged ? data : prevData;
   } else if (result === null || result === undefined) {
     return result;
-  } else if (!isOwnedData && prevData === null) {
+  } else if (isOwnedData && prevData === null) {
     return null;
   } else if (isDataOrKey(result)) {
     const data = (prevData || InMemoryData.makeData(prevData)) as Data;


Test patch:
diff --git a/exchanges/graphcache/src/operations/query.test.ts b/exchanges/graphcache/src/operations/query.test.ts
index 6e48bd99..808102b6 100644
--- a/exchanges/graphcache/src/operations/query.test.ts
+++ b/exchanges/graphcache/src/operations/query.test.ts
@@ -268,6 +268,76 @@ describe('Query', () => {
     expect(console.error).not.toHaveBeenCalled();
   });
 
+  it('should not allow subsequent reads when first result was null (with resolvers)', () => {
+    const QUERY_WRITE = gql`
+      query writeTodos {
+        __typename
+        todos {
+          __typename
+          ...ValidRead
+        }
+      }
+
+      fragment ValidRead on Todo {
+        id
+      }
+    `;
+
+    const QUERY_READ = gql`
+      query getTodos {
+        __typename
+        todos {
+          __typename
+          ...MissingRead
+        }
+        todos {
+          __typename
+          id
+        }
+      }
+
+      fragment MissingRead on Todo {
+        id
+        text
+      }
+    `;
+
+    const store = new Store({
+      schema,
+      resolvers: {
+        Query: {
+          todos: (_parent, _args, cache) => cache.resolve('Query', 'todos'),
+        },
+      },
+    });
+
+    let { data } = query(store, { query: QUERY_READ });
+    expect(data).toEqual(null);
+
+    write(
+      store,
+      { query: QUERY_WRITE },
+      {
+        todos: [
+          {
+            __typename: 'Todo',
+            id: '0',
+          },
+        ],
+        __typename: 'Query',
+      }
+    );
+
+    ({ data } = query(store, { query: QUERY_READ }));
+    expect(data).toEqual({
+      __typename: 'Query',
+      todos: [null],
+    });
+
+    expect(console.warn).not.toHaveBeenCalled();
+    expect(console.error).not.toHaveBeenCalled();
+  });
+
   it('should not mix references', () => {
     const QUERY_WRITE = gql`
       query writeTodos {