facebook / facebook/relay

ErrorAttribute changes mutation payload shape, preventing Relay @prependNode from updating connections correctly

Open
#5,364 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.