response-apollo: запрос на запуск в ответ на пользовательский ввод (например, onClick) - PullRequest
0 голосов
/ 14 мая 2019

(кросс-пост от https://spectrum.chat/apollo/react-apollo/firing-query-in-response-to-user-input-e-g-onclick~13ae09a8-6028-48cf-ac3f-3fbba2307ccc)

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

У меня есть приложение, которое принимает пользовательский ввод в поле поиска. Мне нужно сделать запрос, установив переменную в значение этого поля, когда пользователь нажимает кнопку «поиск». Запрос должен быть установлен на network-only, чтобы он каждый раз получал свежие данные. Когда запрос завершится, мне нужно отобразить подробный компонент, который отображает данные, полученные с сервера. Этот подробный вид содержит пару <Mutation /> компонентов, которые обновляют данные пользователя. Подробный вид должен синхронизироваться, чтобы отражать текущее состояние.

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

Подход 1: <Query /> Компонент

Компонент приложения верхнего уровня выглядит примерно так (упрощенно от фактического):

const App = () => {
  const [searchFieldValue, setSearchFieldValue] = useState('');
  const [username, setUsername] = useState('');
  return (
    <Container>
      <Input
        placeholder="Username"
        name="username"
        id="username"
        value={searchFieldValue}
        width={6}
        onChange={(e, { value }) => {
          setSearchFieldValue(value);
        }}
        />
        <Button onClick={ () => { 
          setUsername(searchFieldInput)
        }>Search</Button>

        {username && <DetailView username={username} />
    </Container>
}

const DetailView = ({username}) => (
  <Query query={GET_USER} variables={{username}} fetchPolicy="network-only">
    {({data, loading, error}) => {
      if (loading) return <Loading />
      if (error) return <Error />
      const { user } = data;
      return (
      // display user data
      // includes a couple of buttons that trigger mutations on the user
      )
    }}
  </Query>
)

По сути, когда нажимается кнопка «поиск», я устанавливаю переменную состояния username для содержимого поля поиска. Это приводит к тому, что username больше не будет ложным, и компонент DetailView монтируется.

Что работает:

Запрос отправляется на сервер, данные принимаются и сохраняются в кеше, а представление отображается. Все мутации работают должным образом, и кэш обновляется с учетом их результатов. Если я введу другое значение в поиске, это приведет к изменению состояния <App /> и повторной визуализации; дочерний запрос перерисовывается, отправляется новый запрос и т. д.

Что не работает:

Я не могу искать одного и того же пользователя дважды. Например, допустим, я ввел «janesmith» в поле поиска. Я получаю информацию Джейн, и <DetailView /> отображается. Если я захочу повторно получить данные Джейн, нажав кнопку поиска еще раз, ничего не произойдет, потому что состояние <App /> фактически не изменилось.

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

Один из возможных обходных путей, который я рассмотрел, - это преобразование App в компонент Class, а обработчик нажатия кнопки поиска делает что-то вроде `this.setState ({username: null}, () => {this.setState ( {username: searchFieldInput})}), который должен вызвать повторную визуализацию, но это кажется невероятно хакерским.

Подход 2: <ApolloConsumer /> и стрельба query() вручную

Документы реагировать на запросы предполагают, что для ручного запуска запроса в ответ на пользовательский ввод необходимо использовать ApolloConsumer и вручную вызывать client.query() в обработчике кликов:

const App = () => {
  const [searchFieldValue, setSearchFieldValue] = useState('');
  const [user, setUser] = useState(null);

  return (
    <ApolloConsumer>
      {client => (
        <Container>
        <Input
          placeholder="Username"
          name="username"
          id="username"
          value={searchFieldValue}
          width={6}
          onChange={(e, { value }) => {
            setSearchFieldValue(value);
          }}
          />
        <Button onClick={async () => { 
          const data = await client.query({
            query: GET_USER,
            variables: {
              username: searchFieldValue
            },
            fetchPolicy='network-only'
          })
          setUser(data.user);
        }>Search</Button>

        {user && <DetailView user={user} setUser={setUser} />
        </Container>
      )}
    </ApolloConsumer>
  )
}

const DetailView = ({user, setUser}) => (
  // .... render the user detail
  // includes a couple of <Mutation /> components that modify the user
  // These need to have the setUser function passed down to them so that they can update the state in <App />
)

Что работает:

При нажатии кнопки поиска запрос отправляется на сервер. Результат возвращается и сохраняется в кеше. Однако в этом случае невозможно использовать значение кэша в <DetailView />; результат должен быть сохранен в состоянии и передан.

Поскольку запрос выполняется в обработчике onClick, я могу искать одно и то же значение снова и снова, и он будет отправлять запросы на сервер (из-за политики извлечения network-only).

Что не работает:

Хотя мутации в DetailView работают, и кэш обновляется при их возврате, пользовательский интерфейс не обновляется, поскольку он является результатом объекта состояния <App />. Это означает, что для того, чтобы пользовательский интерфейс реагировал на результат мутаций, мне нужно передать мою функцию setUser через подпорки всех детей DetailView (где живут мутации) и вызвать setUser в обработчике мутаций onCompleted. Это должно работать, но, похоже, много ненужной работы. Кроме того, я не могу легко реагировать на состояния loading и error при вызове client.query() обязательно.

Одна вещь, которую я рассмотрел, но не попробовал, - это сочетание императивного и декларативного: wrap <App /> in <ApolloConsumer> и запуск запроса вручную в обработчике onClick кнопки с network-onlyполитика извлечения, и компонент DetailView отображает компонент <Query/> для того же запроса, но с политикой извлечения cache-only.Опять же, это кажется слишком громоздким.

Как я уже сказал, я уверен, что упускаю что-то чрезвычайно простое, но я не вижу, что это такое.Любая помощь будет принята с благодарностью.Спасибо!

...