Как мне обрабатывать удаление в React-Apollo

У меня есть мутация вроде

mutation deleteRecord($id: ID) {
    deleteRecord(id: $id) {
        id
    }
}

а в другом месте у меня есть список элементов.

Есть ли что-нибудь получше, которое я мог бы вернуть с сервера, и как мне обновить список?

В целом, как лучше всего обрабатывать удаление в apollo / graphql?


person derekdreery    schedule 08.11.2016    source источник
comment
Примечание для себя: эта страница может быть полезной dev.apollodata.com/react/cache -updates.html # updateQueries   -  person derekdreery    schedule 08.11.2016
comment
TL; DR: По сути, нет. Вместо этого вы теряете волосы, проклинаете команду Apollo и просматриваете огромный список компрометирующих полуработающих обходных решений, предоставленных такими пользователями, как вы, на их странице Github. github.com/apollographql/apollo-client/issues/621   -  person Vincent Cantin    schedule 27.04.2018
comment
Я могу почти гарантировать, что когда-нибудь появится способ сделать удаленный элемент недействительным, так что Apollo автоматически обновит все запросы, содержащие его, потому что нынешние способы сделать это очень далеки от совершенства.   -  person Andy    schedule 18.07.2018


Ответы (6)


Я не уверен, что это хороший стиль практики, но вот как я обрабатываю удаление элемента в react-apollo с помощью updateQueries:

import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'react-addons-update';
import _ from 'underscore';


const SceneCollectionsQuery = gql `
query SceneCollections {
  myScenes: selectedScenes (excludeOwner: false, first: 24) {
    edges {
      node {
        ...SceneCollectionScene
      }
    }
  }
}`;


const DeleteSceneMutation = gql `
mutation DeleteScene($sceneId: String!) {
  deleteScene(sceneId: $sceneId) {
    ok
    scene {
      id
      active
    }
  }
}`;

const SceneModifierWithStateAndData = compose(
  ...,
  graphql(DeleteSceneMutation, {
    props: ({ mutate }) => ({
      deleteScene: (sceneId) => mutate({
        variables: { sceneId },
        updateQueries: {
          SceneCollections: (prev, { mutationResult }) => {
            const myScenesList = prev.myScenes.edges.map((item) => item.node);
            const deleteIndex = _.findIndex(myScenesList, (item) => item.id === sceneId);
            if (deleteIndex < 0) {
              return prev;
            }
            return update(prev, {
              myScenes: {
                edges: {
                  $splice: [[deleteIndex, 1]]
                }
              }
            });
          }
        }
      })
    })
  })
)(SceneModifierWithState);
person vwrobel    schedule 21.11.2016
comment
Привет, @vwrobel, спасибо за ответ. Я заинтересован в удалении любых остатков этой записи из кеша (я использую ${type}:${id} в качестве ключа кеша). Будет ли это делать то же самое? - person derekdreery; 23.11.2016
comment
Привет @derekdreery. Когда я использую updateQuery, к результатам предыдущего запроса применяется только указанный код. В опубликованном мной коде единственное изменение, которое я получаю, это то, что мой удаленный элемент удаляется из списка SceneCollections.myScenes. Кроме того, если у меня есть другой запрос для получения user { id, sceneCounter }, мне придется обновить sceneCounter вручную из updateQueries: даже если мой DeleteSceneMutation возвращает обновленный user { id, sceneCounter }, другие результаты запроса не обновляются, когда я использую updateQueries. Не уверен, что он отвечает на ваш вопрос, мои знания apollo довольно ограничены ... - person vwrobel; 23.11.2016
comment
Не беспокойтесь - мои знания тоже ограничены !! - person derekdreery; 25.11.2016
comment
Это здорово, спасибо. Что происходит, когда именованные запросы вызываются с ids? Может ли Apollo определить, какой набор данных нужно изменить в updateQueries? т.е. что, если бы у вас уже было несколько результатов из SceneCollections(id: "xyz") в магазине? - person Jazzy; 01.02.2017

Вот аналогичное решение, которое работает без underscore.js. Он протестирован с react-apollo в версии 2.1.1. и создает компонент для кнопки удаления:

import React from "react";
import { Mutation } from "react-apollo";

const GET_TODOS = gql`
{
    allTodos {
        id
        name
    }
}
`;

const DELETE_TODO = gql`
  mutation deleteTodo(
    $id: ID!
  ) {
    deleteTodo(
      id: $id
    ) {
      id
    }
  }
`;

const DeleteTodo = ({id}) => {
  return (
    <Mutation
      mutation={DELETE_TODO}
      update={(cache, { data: { deleteTodo } }) => {
        const { allTodos } = cache.readQuery({ query: GET_TODOS });
        cache.writeQuery({
          query: GET_TODOS,
          data: { allTodos: allTodos.filter(e => e.id !== id)}
        });
      }}
      >
      {(deleteTodo, { data }) => (
        <button
          onClick={e => {
            deleteTodo({
              variables: {
                id
              }
            });
          }}
        >Delete</button>            
      )}
    </Mutation>
  );
};

export default DeleteTodo;
person sinned    schedule 03.04.2018

Все эти ответы предполагают управление кешем, ориентированное на запросы.

Что, если я удалю user с идентификатором 1, и на этого пользователя ссылаются в 20 запросах по всему приложению? Читая ответы выше, я должен предположить, что мне придется написать код для обновления кеша всех из них. Это было бы ужасно для долгосрочной поддержки кодовой базы и сделало бы кошмаром любой рефакторинг.

На мой взгляд, лучшим решением будет что-то вроде apolloClient.removeItem({__typeName: "User", id: "1"}), которое:

  • замените любую прямую ссылку на этот объект в кеше на null
  • отфильтровать этот элемент в любом [User] списке в любом запросе

Но его не существует (пока)

Это может быть отличная идея, а может быть еще хуже (например, разбивается нумерация страниц)

По этому поводу ведется интересное обсуждение: https://github.com/apollographql/apollo-client/issues/899

Я был бы осторожен с этими обновлениями запросов вручную. Сначала это выглядит аппетитно, но не будет, если ваше приложение будет расти. По крайней мере, создайте твердый слой абстракции поверх него, например:

  • рядом с каждым определяемым вами запросом (например, в том же файле) - определите функцию, которая правильно его очищает, например

const MY_QUERY = gql``;

// it's local 'cleaner' - relatively easy to maintain as you can require proper cleaner updates during code review when query will change
export function removeUserFromMyQuery(apolloClient, userId) {
  // clean here
}

а затем соберите все эти обновления и вызовите их все в финальном обновлении.

function handleUserDeleted(userId, client) {
  removeUserFromMyQuery(userId, client)
  removeUserFromSearchQuery(userId, client)
  removeIdFrom20MoreQueries(userId, client)
}
person Adam Pietrasiak    schedule 15.04.2019
comment
Это как раз моя проблема, у меня много запросов / списков в кеше, которые используют одно и то же значение. И я действительно не хочу отслеживать их всех. - person Juan Solano; 31.07.2019

Для Apollo v3 это работает для меня:

const [deleteExpressHelp] = useDeleteExpressHelpMutation({
  update: (cache, {data}) => {
    cache.evict({
      id: cache.identify({
        __typename: 'express_help',
        id: data?.delete_express_help_by_pk?.id,
      }),
    });
  },
});

Из новых документов:

Фильтрация висячих ссылок из поля кэшированного массива (как в приведенном выше примере Deity.offspring) настолько распространена, что Apollo Client выполняет эту фильтрацию автоматически для полей массива, которые не определяют функцию чтения.

person Marcel    schedule 19.08.2020
comment
В документации также говорится, что после этого следует вызывать cache.gc (). - person Alexander Danilov; 25.03.2021

Лично я возвращаю int, который представляет количество удаленных элементов. Затем я использую updateQueries для удаления документа (ов) из кеша.

person Siyfion    schedule 09.11.2016
comment
Не могли бы вы привести пример использования updateQueries для удаления записей: в частности, когда они находятся более чем в одном месте в дереве. - person derekdreery; 09.11.2016
comment
Вы должны удалить его из всех мест? - person derekdreery; 09.11.2016
comment
У тебя получилось, @derekdreery? Я не понял, как использовать updateQueries для удаления элементов - person Dunos; 14.11.2016
comment
Нет, я все еще думаю об этом. Я мог бы поднять это как проблему в библиотеке. - person derekdreery; 14.11.2016
comment
Мы привыкли иметь альтернативный интерфейс результатов мутации, оптимизированный для подобных вещей, но он не был идеальным. Пожалуйста, напишите о проблеме, и мы поговорим о ней подробнее! - person stubailo; 14.11.2016
comment
Если откроете вопрос, поместите ссылку сюда, пожалуйста! Да ладно, я уже нашел проблему :) - person Dunos; 21.11.2016

Я столкнулся с той же проблемой, выбирая соответствующий тип возвращаемого значения для таких мутаций, когда остальные API, связанные с мутацией, могли возвращать http 204, 404 или 500.

Определение произвольного типа с последующим возвратом null (типы по умолчанию допускают значение NULL) не кажется правильным, потому что вы не знаете, что произошло, то есть было ли это успешным или нет.

Возврат логического значения решает эту проблему, вы знаете, сработала ли мутация или нет, но вам не хватает некоторой информации на случай, если она не сработает, например, лучшего сообщения об ошибке, которое вы могли бы показать в FE, например , если мы получили 404, мы можем вернуть «Не найдено».

Возврат настраиваемого типа кажется немного вынужденным, потому что на самом деле это не тип вашей схемы или бизнес-логики, он просто служит для устранения «проблемы связи» между rest и Graphql.

В итоге я вернул строку. Я могу вернуть идентификатор ресурса / UUID или просто «ок» в случае успеха и вернуть сообщение об ошибке в случае ошибки.

Не уверен, что это хорошая практика или идиоматика Graphql.

person Mario    schedule 17.11.2019
comment
Мне нравится такой подход - person lewislbr; 07.12.2019