ErrorAttribute changes mutation payload shape, preventing Relay @prependNode from updating connections correctly
- Dominant language
- Rust
- Stars
- 19k
- Forks
- 1.9k
- PR merge metrics
- No merged PRs in 30d
Description
Hi everyone,
Firstly, apologies if this is the wrong repository. I'm not entirely sure whether this belongs here or on the Hot Chocolate repository.
I couldn't find an existing GitHub issue describing this scenario, although it's entirely possible that I'm missing something or that I'm using the framework incorrectly.
---
### Environment
Hot Chocolate – `15.1.14`
React – `18.1.0`
react-relay – `16.2.0`
relay-runtime – `16.2.0`
---
## What I'm trying to achieve
I have a page that displays a list of items using a Relay connection and a MUI DataGrid.
A user can create a new item from a modal dialog. The modal performs a Relay mutation, and I use Relay's declarative mutation directive `@prependNode `so that the newly created node is automatically inserted into the existing Relay connection.
The DataGrid itself contains no local state—it simply renders whatever exists in the Relay store.
The intended flow is:
- The parent component loads a Relay connection.
- The parent renders a MUI DataGrid from that connection.
- The parent passes the Relay connection ID (`__id`) into a modal.
- The modal executes a mutation.
- Relay prepends the newly created node into the connection.
- The Relay store updates.
- The parent re-renders.
- The DataGrid immediately displays the new row.
This works perfectly until I introduce Hot Chocolate's `ErrorAttribute`.
---
### Relevant implementation
#### Parent component
The parent owns the Relay query and Relay connection.
```graphql
items
@connection(key: "ItemList_items") {
__id
edges {
node {
id
name
value
enabled
}
}
}
```
The Relay connection ID is passed into the modal.
```ts
```
The DataGrid rows are derived directly from the Relay connection.
```ts
const rows = React.useMemo(() => {
return data.items?.edges?.map(({ node }) => ({
id: node?.id,
name: node?.name,
value: node?.value,
enabled: node?.enabled,
}));
}, [data.items?.edges]);
```
The DataGrid simply renders these rows.
```ts
row.id}
/>
```
There is no additional React state or manual Relay store manipulation.
---
### Modal component
The modal receives the Relay connection ID.
```ts
interface Props {
connectionId: string;
}
```
When submitting the mutation, the connection ID is passed to relay.
```ts
commitMutation({
variables: {
input: {
...
},
connections: [connectionId],
},
});
```
---
### Working mutation
Without `ErrorAttribute`, my mutation looks like this:
```graphql
mutation CreateItemMutation(
$input: CreateItemInput!
$connections: [ID!]!
) {
createItem(input: $input)
@prependNode(
connections: $connections
edgeTypeName: "ItemEdge"
) {
id
name
value
enabled
}
}
```
Everything works exactly as expected.
Relay prepends the node into the connection, the parent component sees the updated connection, and the DataGrid immediately updates.
---
### After introducing `ErrorAttribute`
I wanted to return typed domain errors using `ErrorAttribute`, so my mutation now returns a payload object instead.
It now looks similar to this:
```graphql
mutation CreateItemMutation(
$input: CreateItemInput!
) {
createItem(input: $input) {
errors {
... on ItemAlreadyExistsError {
message
}
}
item {
id
name
value
enabled
}
}
}
```
The server-side error handling works perfectly.
However, because the created node is now nested under `item`, I can no longer use Relay's `@prependNode `directive.
---
### What happens
The mutation successfully creates the record in the database.
If I refresh the page afterwards, the new record is returned by the query and displays correctly in the DataGrid.
The issue only occurs immediately after the mutation completes.
The DataGrid throws the following error:
```
MUI X: The data grid component requires all rows to have a unique `id` property.
Alternatively, you can use the `getRowId` prop to specify a custom id for each row.
A row was provided without id in the rows prop: {}
```
The DataGrid already specifies:
```ts
row.id}
/>
```
When logging the newly inserted node after the mutation completes, I see:
```
node.id: undefined
```
The newly added row appears to be an empty object `({})`, which causes the DataGrid error.
Refreshing the page fixes everything because the query is executed again and the server returns the correct data.
---
### My understanding
From what I can tell, the issue isn't with MUI DataGrid.
The DataGrid is simply exposing the problem because it's rendering whatever Relay has stored.
It seems like the interaction between ErrorAttribute and Relay's declarative mutation directives is the actual issue.
### Questions
- Is this expected behaviour?
- Is ErrorAttribute intended to be incompatible with Relay's declarative mutation directives like `@prependNode` and `@appendNode`?
- Is there a recommended pattern for combining `ErrorAttribute` with Relay connections?
- Is the recommended solution to always write a custom Relay updater when using ErrorAttribute?
- Is there another Hot Chocolate feature or pattern that I've overlooked which preserves compatibility with Relay's declarative mutation directives?
### Additional context
I've attached both the parent component and the modal component used in this example if seeing the complete implementation is helpful.
The important part is that:
- The parent owns the Relay query and connection.
- The parent passes the connection ID (`__id`) to the modal.
- The modal executes the mutation.
- The DataGrid renders directly from the Relay connection.
- There is no local React state involved.
Without `ErrorAttribute`, Relay updates the connection automatically and everything works as expected.
After introducing `ErrorAttribute`, the mutation succeeds and the record is persisted, but the Relay connection no longer appears to contain a valid node until the page is refreshed.
Any guidance would be greatly appreciated.
Thanks!
Contributor guide
Research direction
Start at the mutation selection using ErrorAttribute and Relay's @prependNode directive, comparing the working flat node response with the nested item payload. Use the reported empty row and undefined node.id as the failure symptoms; done means the connection receives a valid created node immediately after the mutation, or the incompatibility and supported pattern are documented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, react, typescript
- Domain
- api, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100