Как заставить компонент Apollo Query повторно запускать запрос при повторном рендеринге родительского компонента

Я использую <Query> клиента Apollo в компоненте, который повторно отображается при изменении состояния в рамках метода жизненного цикла. Я хочу, чтобы мой компонент <Query> повторно запускал запрос, потому что я знаю, что данные изменились. Похоже, что компонент запроса использует кеш, который необходимо сделать недействительным перед повторным запуском запроса.

Я использую неудобный обходной путь, который кэширует обратный вызов refetch из опоры рендеринга в родительском компоненте, но это кажется неправильным. Опубликую свой подход в ответах, если кому интересно.

Мой код выглядит примерно так. Я удалил обработку loading и error из запроса, а также некоторые другие детали для краткости.

class ParentComponent extends React.Component {
  componentDidUpdate(prevProps) {
    if (this.props.refetchId !== prevProps.refetchId) {
      const otherData = this.processData() // do something
      this.setState({otherData}) // this forces component to reload
    }
  }

  render() {
    const { otherData } = this.state

    return (
      <Query query = {MY_QUERY}>
        {({ data }) => {
          return <ChildComponent gqlData = {data} stateData = {otherData} />
        }}
      </Query>
    )
  }
}

Как заставить <Query> получать новые данные для передачи в <ChildComponent>?

Несмотря на то, что ParentComponent повторно выполняет рендеринг при изменении свойств или состояния, Query не запускается повторно. ChildComponent получает обновленную опору stateData, но имеет устаревшую опору gqlData. Насколько я понимаю, кеш запросов Apollo необходимо сделать недействительным, но я не уверен.

Обратите внимание, что передача refetch в ChildComponent не является ответом, потому что он отображает только информацию из GraphQL и не знает, когда выполнять повторную выборку. Я не хочу вводить таймеры или иным образом усложнять ChildComponent для решения этой проблемы - ему не нужно знать об этой сложности или проблемах с извлечением данных.

Я добавил пару вариантов, я всегда использовал первый, второй метод не сработает, если вы не хотите ничего передавать.

leogoesger 26.07.2018 05:40
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
20
1
19 319
5

Ответы 5

Не могли бы вы выполнить повторную загрузку родительского компонента? Как только родительский компонент получит обновление, вы можете оценить, запускать ли выборку или нет.

Я сделал это без использования Query, как показано ниже:

class ParentComp extends React.Component {

    lifeCycleHook(e) { //here
        // call your query here
        this.props.someQuery()
    }

    render() {
        return (
            <div>
                <Child Comp data = {this.props.data.key}> //child would only need to render data
            </div>
        );
    }
}

export default graphql(someQuery)(SongCreate);

Таким образом, вы можете запустить загрузку в любое время, когда захотите. В этом случае вы можете получить запрос как prop.

В вашем случае вы бы поместили свой запрос в опору, используя export default graphql(addSongQuery)(SongCreate);. Затем назовите его в перехватчиках жизненного цикла DidUpdate.

Другой вариант - использовать refetch в запросе.

<Query
    query = {GET_DOG_PHOTO}
    variables = {{ breed }}
    skip = {!breed}
  >
    {({ loading, error, data, refetch }) => {
      if (loading) return null;
      if (error) return `Error!: ${error}`;

      return (
        <div>
          <img
            src = {data.dog.displayImage}
            style = {{ height: 100, width: 100 }}
          />
          <button onClick = {() => refetch()}>Refetch!</button>
        </div>
      );
    }}
  </Query>

Второй метод потребует от вас передать что-то своему ребенку, что на самом деле не так уж и плохо.

в обоих ваших примерах дочерний компонент что-то делает, чтобы вызвать повторную визуализацию. как описано в вопросе, дочерний компонент не знает, что когда запускает повторную визуализацию (это не интерактивно). спасибо, но это не совсем то, что мне нужно

Andrei R 26.07.2018 08:27

нет, первый - нет. У родителя есть запрос как опора, и вы можете re-render, используя хуки жизненного цикла.

leogoesger 26.07.2018 17:09

Ах я вижу. теперь, когда вы упростили его, вам будет легче следовать. Благодарю.

Andrei R 27.07.2018 01:45

Большой! Рад работать @AndreiR. Позвольте мне знать, как это получается. Если сработает, не могли бы вы проголосовать и принять ответ других зрителей? Спасибо

leogoesger 27.07.2018 02:50

Мне кажется, что компонент Query не обязательно должен находиться внутри этого ParentComponent.

В этом случае я бы переместил компонент Query вверх, так как я все еще мог бы отображать другие вещи, пока у меня нет результатов в ChildComponent. И тогда у меня был бы доступ к методу query.refetch.

Обратите внимание, что в примере я добавил graphql hoc, но вы все равно можете использовать компонент Query для <ParentComponent />.

class ParentComponent extends React.Component {
    componentDidUpdate(prevProps) {
        if (this.props.refetchId !== prevProps.refetchId) {
            const otherData = this.processData() // do something

            //   won't need this anymore, since refetch will cause the Parent component to rerender)
            //   this.setState({otherData}) // this forces component to reload 

            this.props.myQuery.refetch(); // >>> Refetch here!
        }
    }

    render() {
        const {
            otherData
        } = this.state;

        return <ChildComponent gqlData = {this.props.myQuery} stateData = {otherData} />;
    }
}

export graphql(MY_QUERY, {
    name: 'myQuery'
})(ParentComponent);

Спасибо. Это немного открыло мне глаза. По сути, <Query> - плохой выбор практически для любого случая использования, кроме самых тривиальных.

Andrei R 27.07.2018 04:42

Да. В итоге я не использую его так часто. Это решит вашу проблему?

Pedro Baptista Afonso 27.07.2018 12:42

Как и обещал, вот мой обходной путь

class ParentComponent extends React.Component {
  componentDidUpdate(prevProps) {
    if (this.props.refetchId !== prevProps.refetchId) {
      const otherData = this.processData() // do something
      this.setState({otherData})
      if (this.refetch) {
        this.refetch()
      }
    }
  }

  render() {
    const { otherData } = this.state
    const setRefetch = refetch => {this.refetch = refetch}
    return (
      <Query query = {MY_QUERY}>
        {({ data, refetch }) => {
          setRefetch(refetch)
          return <ChildComponent gqlData = {data} stateData = {otherData} />
        }}
      </Query>
    )
  }
}

У меня была почти аналогичная ситуация, которую я решил с атрибутом fetchPolicy:

<Query fetchPolicy = "no-cache" ...

Задача заключалась в том, чтобы загрузить некоторые детали с сервера, щелкнув элемент списка.

И если я хотел добавить действие для принудительной повторной выборки запроса (например, изменение деталей), я сначала назначил refetch для this.refetch:

<Query fetchPolicy = "no-cache" query = {GET_ACCOUNT_DETAILS} variables=... }}>
  {({ data: { account }, loading, refetch }) => {
  this.refetch = refetch;
...

И в конкретном действии, которое я хотел выполнить, я вызвал:

this.refetch();

Для меня это было самое простое решение

Daryl Spitzer 30.09.2019 06:14

Это сработало как шарм. Самое простое решение, которое я нашел до сих пор.

Little Bamboo 14.03.2020 07:54

В дополнение к уже принятому ответу выше есть 2 способа добиться этого.

Если все, что вам нужно, это получать данные с сервера graphql для создания списка каждый раз, когда компонент монтируется, у вас есть следующие параметры:

  1. Используйте опцию fetchPolicy = "no-cache"
export default defineComponent({
  setup() {
    const { result, loading, error } = useMyQuery(
      () => {
        return { date: new Date() }
      },
      {
        pollInterval: 15 * 60 * 1000, // refresh data every 15 min
        fetchPolicy: 'no-cache',
      }
    )
  1. Используйте метод refetch(), сохраняя кеш на месте:
export default defineComponent({
  setup() {
    const { result, loading, error}, refetch } = useMyQuery(
      () => {
        return { date: new Date() }
      },
      {
        pollInterval: 15 * 60 * 1000, // refresh data every 15 min
      }
    )

    if (!loading.value) void refetch()

Другие вопросы по теме