我花了一个下午对我认为应该是一个相当简单的任务be之以鼻:根据用户输入触发查询;例如搜索字段。
我有一个可以在搜索字段中接受用户输入的应用程序。当用户单击“搜索”按钮时,我需要进行查询,将变量设置为该字段的值。该查询需要设置为network-only
,以便它每次都获取新数据。查询完成后,我需要显示一个详细组件,该组件显示从服务器获取的数据。该详细信息视图包含两个<Mutation />
组件,这些组件对用户数据进行更新。详细信息视图需要保持同步,以便反映当前状态。
我已经尝试了几种方法来解决这个问题,但是每种方法都可以使我部分地实现这一目标,但并非一路走来。我确定我缺少简单的东西。
<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”。我得到了Jane的信息,并且呈现了<DetailView />
。如果我想通过再次单击搜索按钮来重新获取Jane的详细信息,则没有任何反应,因为<App />
的状态实际上并未更改。
我知道我可以将控件呈现为Query
中DetailView
的子控件,并使其触发refetch
函数,但这不是我想要的-我想要搜索按钮,每次都会触发查询。
我考虑过的一种可能的解决方法是将App
转换为Class组件,并使用搜索按钮的单击处理程序执行类似`this.setState({username:null},()=> {this。 setState({username:searchFieldInput})}),应该触发重新渲染,但这似乎难以置信。
<ApolloConsumer />
并手动触发query()
react-apollo docs on queries建议,要响应用户输入手动触发查询,应使用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
中的突变起作用,并且缓存在它们返回时被更新,但UI不会更新,因为它是<App />
状态对象的结果。这意味着,为了使UI对突变的结果做出反应,我需要将setUser
的功能向下传递给DetailView
的所有子代(突变所在的地方),并且在突变的setUser
处理程序中调用onCompleted
。这应该可以,但是似乎很多不必要的工作。另外,当强制调用loading
时,我无法轻易地对error
和client.query()
状态做出反应。
我考虑过但尚未尝试过的一件事是命令式和声明式的结合:将<App />
包裹在<ApolloConsumer>
中,然后在按钮的onClick
中手动触发查询具有network-only
提取策略的处理程序,并让DetailView
组件为同一查询呈现<Query/>
组件,但具有cache-only
提取策略。同样,这似乎太麻烦了。
就像我说的那样,我确定我缺少非常简单的东西,但是我看不到它是什么。任何帮助将不胜感激。谢谢!