我有一个非常复杂的组件,现在我正在尝试实现一种搜索,用户可以在其中键入内容并过滤结果。
// query
const GET_ACCOUNTS = gql`
query accounts{
accounts{
id
name
status
company{
id
name
}
}
}
`;
// get query
const { loading } = useQuery(GET_ACCOUNTS, {
fetchPolicy: "no-cache",
skip: userType !== 'OS_ADMIN',
onCompleted: setSearchResults
});
// example of query result (more than 1)
{
"accounts": [
{
"id": "5deed7df947204960f286010",
"name": "Acme Test Account",
"status": "active",
"company": {
"id": "5de84532ce5373afe23a05c8",
"name": "Acme Inc.",
"__typename": "Company"
},
"__typename": "Account"
},
]
}
// states
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState([]);
// code to render
<FormControl fullWidth>
<InputLabel htmlFor="seach">Search for accounts</InputLabel>
<Input
id="search"
aria-describedby="Search for accounts"
startAdornment={<InputAdornment position="start"><SearchIcon /></InputAdornment>}
value={searchTerm}
onChange={handleChange}
/>
</FormControl>
{searchResults && searchResults.accounts &&
searchResults.accounts.map(c => {
return (
<>
<ListItem
dense
button
className={classnames({ [classes.selectedAccountContext]: c.id === accountContextId })}
key={c.id}
onClick={() => accountClicked(c.id)}
>
<ListItemText
primary={c.name}
secondary={
<>
<span>{c.company.name}</span>
<span className="d-flex align-items-center top-margin-tiny">
<Badge
color={c.status === 'active' ? "success" : "danger"}
style={{ marginBottom: 0 }}
>
{c.status.replace(/^\w/, c => c.toUpperCase())}
</Badge>
<span className='ml-auto'>
<SvgIcon><path d={mdiMapMarkerRadius} /></SvgIcon>
<SMARTCompanyIcon />
</span>
</span>
</>
}
/>
</ListItem>
</>
)
})
}
// useEffect
useEffect(() => {
if (searchTerm) {
const results = searchResults.accounts.filter((c => c.name.toLowerCase().includes(searchTerm)))
setSearchResults(results)
}
}, [searchTerm])
问题是,当我开始在搜索字段中输入内容时,我正在查看searchResults
,当我输入一个字符时,它会被过滤,但是当我键入下一个字符时,它会中断。
TypeError: Cannot read property 'filter' of undefined
即使我输入了一个字母,它也不会在视图上呈现。
答案 0 :(得分:1)
根据您的数据,searchResults
的初始值是带有accounts
键的字典。但是,当您在useEffect
部分中对其进行更新时,它将更改为列表:
useEffect(() => {
if (searchTerm) {
const results = searchResults.accounts.filter((c => c.name.toLowerCase().includes(searchTerm)))
// This changes the value of searchResults to an array
setSearchResults(results)
}
}, [searchTerm])
在setSearchResults
内部调用useEffect
时,searchResults
的值从对象更改为数组:
从这里:
searchResults = { 帐户:[ ... ] }
对此:
searchResults = [ ... ]
这就是为什么由于没有TypeError: Cannot read property 'filter' of undefined
键而在第一次搜索后会引发accounts
的原因。
要解决此问题,您需要保持searchResults
的数据类型的一致性,最好首先将其作为一个列表。您可以在onCompleted
部分中完成此操作:
const { loading } = useQuery(GET_ACCOUNTS, {
fetchPolicy: "no-cache",
skip: userType !== 'OS_ADMIN',
onCompleted: (data) => setSearchResults(data.accounts || [])
});
请注意,我们将searchResults
设置为accounts
的值。之后,您还需要有关如何访问searchResults
{searchResults &&
searchResults.map(c => {
return (
...renderhere
)
})
}
您的useEffect
将如下所示:
useEffect(() => {
if (searchTerm) {
const results = searchResults.filter((c => c.name.toLowerCase().includes(searchTerm)))
setSearchResults(results)
}
}, [searchTerm])
提示:
您可能想将searchResults
重命名为accounts
,以使其更清晰。另请注意,在第一次搜索之后,您的选项将仅限于先前的搜索结果,因此您可能还希望将所有帐户存储在不同的变量中:
const [allAccounts, setAllAccounts] = useState([])
const [searchedAccounts, setSearchedAccounts] = useState([])
// useQuery
const { loading } = useQuery(GET_ACCOUNTS, {
fetchPolicy: "no-cache",
skip: userType !== 'OS_ADMIN',
onCompleted: (data) => {
setAllAccounts(data.accounts || [])
setSearchedAccounts(data.accounts || [])
}
});
// useEffect
useEffect(() => {
if (searchTerm) {
// Notice we always search from allAccounts
const results = allAccounts.filter((c => c.name.toLowerCase().includes(searchTerm)))
setSearchedAccounts(results)
}
}, [searchTerm])