(кросс-пост от 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
.Опять же, это кажется слишком громоздким.
Как я уже сказал, я уверен, что упускаю что-то чрезвычайно простое, но я не вижу, что это такое.Любая помощь будет принята с благодарностью.Спасибо!