facebook / facebook/relay

Type-safe updaters: non-assignability of unions

Open
#4,560 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
19k
Forks
1.9k
PR merge metrics
No merged PRs in 30d

Description

I seem to have encountered an issue with the new typesafe updaters. Here's the basic repro: https://github.com/Malien/typesafe-relay-updaters-repro

_Obligatory "using typescript, not flow" mention_

Assume we have a simple Todo app with todo groupings. Like this:
- ~~Make coffee~~
- Touch grass
- Buy groceries
- Tomatoes
- ~~Bread~~
- Work out

Let us have the following gql schema:
```graphql
type Query {
todos: [Todo!]!
}

interface Node {
id: ID!
}

union Todo = TodoItem | TodoGroup

type TodoItem implements Node {
id: ID!
title: String!
completed: Boolean!
}

type TodoGroup implements Node {
id: ID!
title: String!
todos: [TodoItem!]!
}

type Mutation {
createTodo(title: String!): TodoItem!
}
```

```graphql
fragment TodoList_assignable_todo on Todo @assignable {
__typename
}
```

given the query:

```graphql
query TodoListQuery {
todos {
...Todo_todo # Some component that renders the todo items/groups
...TodoList_assignable_todo
}
}
```

and the mutation

```graphql
mutation TodoListAddTodoMutation($title: String!) {
createTodo(title: $title) {
...Todo_todo
...TodoList_assignable_todo
}
}
```

the update of

```js
function updater(store, response) {
const newTodo = response?.createTodo;
if (!newTodo) return;

const { updatableData } = store.readUpdatableQuery(
graphql`
query TodoListUpdateQuery @updatable {
todos {
...TodoList_assignable_todo
}
}
`,
{},
);

updatableData.todos = [...todos, newTodo];
}
```

fails to typecheck, since the generated types from `TodoListAddTodoMutation` is:
```ts
export type TodoListAddTodoMutation$data = {
readonly createTodo: {
readonly __id: string;
readonly __isTodoList_assignable_todo?: "TodoItem";
readonly " $fragmentSpreads": FragmentRefs<"TodoList_assignable_todo" | "Todo_todo">;
};
};
```

`__isTodoList_assignable_todo` is optional.

This seems to be related to the sections of the docs ["where is guaranteed to implement an interface"](https://relay.dev/docs/guided-tour/updating-data/imperatively-modifying-linked-fields/#example-assigning-to-an-interface-when-the-source-is-guaranteed-to-implement-that-interface)/["where is not guaranteed to implement an interface"](https://relay.dev/docs/guided-tour/updating-data/imperatively-modifying-linked-fields/#example-assigning-to-an-interface-when-the-source-is-not-guaranteed-to-implement-that-interface), yet this seems like a missed case.

`TodoItem` (the return type of `createTodo`) is guaranteed to """implement""" union `Todo`. Yet this requires additional type-guard function (aka. validators). In the end the new typesafe updaters code is a lot more cumbersome to deal with, in comparison to the old unsafe variant. Not to mention all of the new concepts of assignability and updatability being thrown back at you.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.