Клиент Apollo: оптимистичные обновления в процессе создания
Я хочу иметь возможность обновлять объект, пока он еще создается.
Например: скажем, у меня есть список дел, в который я могу добавить элементы с именами. Я также хочу иметь возможность редактировать названия предметов.
Теперь, скажем, пользователь с медленным соединением создает элемент. В этом случае я запускаю мутацию создания элемента и оптимистично обновляю свой интерфейс. Это прекрасно работает. Пока проблем нет
Теперь предположим, что мутация создания элемента занимает немного времени из-за медленной сети. В это время пользователь решает изменить имя элемента, который он только что создал. Для идеального опыта:
- Пользовательский интерфейс должен немедленно обновить с новым именем
- Новое имя должно быть сохранено на сервере
Я могу достичь #2, ожидая завершения создания мутации (чтобы я мог получить идентификатор элемента), а затем сделав мутацию имени обновления. Но это означает, что части моего пользовательского интерфейса будут оставаться неизменными до тех пор, пока не вернется мутация создания элемента и не начнется оптимистический ответ мутации имени обновления. Это означает, что № 1 не будет достигнут.
Поэтому мне интересно, как я могу достичь и #1 и #2 с помощью клиента Apollo.
Примечание: я не хочу добавлять счетчики или отключать редактирование. Я хочу, чтобы приложение чувствовало себя отзывчивым даже при медленном соединении.
2 ответа
Если у вас есть доступ к серверу, вы можете реализовать upsert
операции, так что вы можете свести все запросы к такому:
mutation {
upsertTodoItem(
where: {
key: $itemKey # some unique key generated on client
}
update: {
listId: $listId
text: $itemText
}
create: {
key: $itemKey
listId: $listId
text: $itemText
}
) {
id
key
}
}
Таким образом, у вас будет последовательность идентичных мутаций, отличающихся только переменными. Оптимистический ответ, соответственно, может быть настроен на эту одну мутацию. На сервере нужно проверить, есть ли товар с таким key
уже существует и соответственно создайте или обновите элемент
Кроме того, вы можете использовать https://github.com/helfer/apollo-link-debounce, чтобы уменьшить количество запросов, когда пользователь печатает
Я думаю, что самый простой способ достичь желаемого эффекта - это отказаться от оптимистичных обновлений в пользу самостоятельного управления состоянием компонента. На данный момент у меня нет полосы пропускания, чтобы написать полный пример, но ваша базовая структура компонентов будет выглядеть так:
<ApolloConsumer>
{(client) => (
<Mutation mutation={CREATE_MUTATION}>
{(create) => (
<Mutation mutation={EDIT_MUTATION}>
{(edit) => (
<Form />
)}
</Mutation>
)}
</Mutation>
)}
</ApolloConsumer>
Давайте предположим, что мы имеем дело только с одним полем - name
, Ваш Form
Компонент будет начинаться с начального состояния
{ name: '', created: null, updates: null }
После отправки форма будет делать что-то вроде:
onCreate () {
this.props.create({ variables: { name: this.state.name } })
.then(({ data, errors }) => {
// handle errors whichever way
this.setState({ created: data.created })
if (this.state.updates) {
const id = data.created.id
this.props.update({ variables: { ...this.state.updates, id } })
}
})
.catch(errorHandler)
}
Тогда логика редактирования выглядит примерно так:
onEdit () {
if (this.state.created) {
const id = this.state.created.id
this.props.update({ variables: { name: this.state.name, id } })
.then(({ data, errors }) => {
this.setState({ updates: null })
})
.catch(errorHandler)
} else {
this.setState({ updates: { name: this.state.name } })
}
}
По сути, ваша мутация редактирования либо запускается немедленно, когда пользователь отправляет (так как мы уже получили ответ от нашей мутации создания)... или изменения, сделанные пользователем, сохраняются и затем отправляются после завершения создания мутации.
Это очень грубый пример, но он должен дать вам представление о том, как справиться с подобным сценарием. Самым большим недостатком является то, что существует вероятность того, что состояние вашего компонента не синхронизируется с кешем - вам нужно убедиться, что вы правильно обрабатываете ошибки, чтобы этого избежать.
Это также означает, что если вы хотите использовать эту форму только для редактирования, вам нужно извлечь данные из кэша, а затем использовать его для заполнения вашего начального состояния (т.е. this.state.created
в приведенном выше примере). Вы можете использовать Query
компонент для этого, просто убедитесь, что вы не рендер Form
компонент, пока у вас нет data
опора предоставлена Query
составная часть.