Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slimy-chairs-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/exchange-graphcache': patch
---

Fix traversal issue, where when a prior selection set has set a nested result field to `null`, a subsequent traversal of this field attempts to access `prevData` on `null`.
61 changes: 61 additions & 0 deletions exchanges/graphcache/src/operations/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,65 @@ describe('Query', () => {
todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }],
});
});

it('should allow subsequent read when first result was null', () => {
const QUERY_WRITE = gql`
query writeTodos {
todos {
...ValidRead
}
}

fragment ValidRead on Todo {
id
}
`;

const QUERY_READ = gql`
query getTodos {
todos {
...MissingRead
}
todos {
id
}
}

fragment ValidRead on Todo {
id
}

fragment MissingRead on Todo {
id
text
}
`;

const store = new Store({
schema: alteredRoot,
});

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_root',
todos: [null],
});
});
});
25 changes: 13 additions & 12 deletions exchanges/graphcache/src/operations/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ const resolveResolverResult = (
fieldName: string,
key: string,
select: SelectionSet,
prevData: void | Data | Data[],
prevData: void | null | Data | Data[],
result: void | DataField
): DataField | void => {
if (Array.isArray(result)) {
Expand All @@ -410,7 +410,7 @@ const resolveResolverResult = (
joinKeys(key, `${i}`),
select,
// Get the inner previous data from prevData
prevData !== undefined ? prevData[i] : undefined,
prevData != null ? prevData[i] : undefined,
result[i]
);

Expand All @@ -424,8 +424,12 @@ const resolveResolverResult = (
return data;
} else if (result === null || result === undefined) {
return result;
} else if (prevData === null) {
// If we've previously set this piece of data to be null,
// we skip it and return null immediately
return null;
} else if (isDataOrKey(result)) {
const data = (prevData === undefined ? {} : prevData) as Data;
const data = (prevData || {}) as Data;
return typeof result === 'string'
? readSelection(ctx, result, select, data)
: readSelection(ctx, key, select, data, result);
Expand All @@ -448,7 +452,7 @@ const resolveLink = (
typename: string,
fieldName: string,
select: SelectionSet,
prevData: void | Data | Data[]
prevData: void | null | Data | Data[]
): DataField | undefined => {
if (Array.isArray(link)) {
const { store } = ctx;
Expand All @@ -462,7 +466,7 @@ const resolveLink = (
typename,
fieldName,
select,
prevData !== undefined ? prevData[i] : undefined
prevData != null ? prevData[i] : undefined
);
if (childLink === undefined && !_isListNullable) {
return undefined;
Expand All @@ -472,15 +476,12 @@ const resolveLink = (
}

return newLink;
} else if (link === null) {
} else if (link === null || prevData === null) {
// If the link is set to null or we previously set this piece of data to be null,
// we skip it and return null immediately
return null;
} else {
return readSelection(
ctx,
link,
select,
prevData === undefined ? ({} as any) : prevData
);
return readSelection(ctx, link, select, (prevData || {}) as Data);
}
};

Expand Down