Как заставить компонент 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 решение этой проблемы - ему не нужно знать об этой сложности или проблемах с извлечением данных.


person Andrei R    schedule 26.07.2018    source источник
comment
Я добавил пару вариантов, я всегда использовал первый, второй метод не сработает, если вы не хотите ничего передавать.   -  person leogoesger    schedule 26.07.2018


Ответы (5)


У меня была почти аналогичная ситуация, которую я решил с атрибутом 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();
person Saro    schedule 09.04.2019
comment
Для меня это было самое простое решение - person Daryl Spitzer; 30.09.2019
comment
Это сработало как шарм. Самое простое решение, которое я нашел до сих пор. - person Little Bamboo; 14.03.2020

Мне кажется, что компонент 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);
person Pedro Baptista Afonso    schedule 26.07.2018
comment
Спасибо. Это немного открыло мне глаза. По сути, <Query> - плохой выбор практически для любого случая использования, кроме самых тривиальных. - person Andrei R; 27.07.2018
comment
да. В итоге я не использую его так часто. Это решит вашу проблему? - person Pedro Baptista Afonso; 27.07.2018

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

Я сделал это без использования 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>

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

person leogoesger    schedule 26.07.2018
comment
в обоих ваших примерах дочерний компонент что-то делает, чтобы вызвать повторную визуализацию. как описано в вопросе, дочерний компонент не знает когда запускать повторную визуализацию (это не интерактивно). спасибо, но это не совсем то, что мне нужно - person Andrei R; 26.07.2018
comment
нет, первый - нет. У родителя есть запрос в качестве опоры, и вы можете re-render использовать хуки жизненного цикла. - person leogoesger; 26.07.2018
comment
Ах я вижу. теперь, когда вы упростили его, вам будет легче следовать. Благодарю. - person Andrei R; 27.07.2018
comment
Большой! Рад работать @AndreiR. Позвольте мне знать, как это получается. Если сработает, не могли бы вы проголосовать и принять ответ других зрителей? Спасибо - person leogoesger; 27.07.2018

В дополнение к уже принятому ответу выше есть 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()
person DarkLite1    schedule 01.12.2020

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

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>
    )
  }
}
person Andrei R    schedule 04.03.2019