facebook / facebook/relay

Provide public method to get record before relay runs its pre-updater magic

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

Description

tl;dr: add `store.getBase = (id) => store.__recordSource.__mutator._base.get(id)` to the API

Often times, I need to know the previous value of a record in the updater, onNext, onCompleted handler.
For example, if a user updates the assignee of a todo item, i need to remove it from the old user & add it to the new user.

Today, that requires doing one of the following:

### Method 1: Update your server schema
```graphql
mutation updateTodo {
todo {
id
assigneeId
}
oldAssigneeId
}
```
This is awful because the client needs shouldn't necessitate a server change

### Method 2: Update it in the view layer
```js
class TodosByUserId extends Component {
static getDerivedStateFromProps(nextProps) {
const nextTodos = nextProps.todos.filter((todo) => todo.assigneeId === nextProps.userId)
return {todos: nextTodos}
}
}
```
This is even worse. View layers should be as stupid as possible.

### Method 3: Overfetch
```graphql
mutation updateTodo {
viewer {
all10000Todos {
id
assigneeId
}
}
}
```
This is better, but it doesn't work for all scenarios. For example, maybe I want to set a client-side flag on the document if the old assignee was Bob & now it's Barbara.

### Method 4: Use relay internals
```js
updater = (payload, store) => {
const todoId = payload.getLinkedRecord('todo').getValue('id')
const oldAssigneeId = store.__recordSource.__mutator.__base.get(todoId).assigneeId
const newAssigneeId = store.get(todoId).getValue('assigneeId')
removedFromAssignee(todoId, oldAssigneeId)
addToAssignee(todoId, newAssigneeId)
if (oldAssigneeId === 'Bob' && newAssigneeId === 'Barbara') {
store.get(todoId).setValue(true, 'wasBobs')
}
}
```

This is amazing & it does everything I want it to do, but it uses a lot of relay internals. It also returns a POJO instead of a proxy, so it's a little dangerous.

### Proposal
```js
const newAssigneeId = store.get(todoId).getValue('assigneeId')
const oldAssigneeId = store.getBase(todoId).getValue('assigneeId')
```
To keep the API safe & consistent, I propose a `RelayReadOnlyRecordProxy` as a subset of `RelayRecordProxy`. It just has the `get*` methods: `getBase(dataID: string): ?ReadOnlyRecordProxy`

Happy to write the PR, just wanna achieve consensus first.

Contributor guide

Open the contributing guide

Research direction

Start in the updater's store API and the existing RelayRecordProxy methods, then trace the referenced __recordSource and __mutator base-record access. Compare the proposed RelayReadOnlyRecordProxy with the get* subset described in the issue. Done means updater handlers can read the previous record through public getBase(id) without relying on Relay internals.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, javascript, react
Domain
api, frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.