useQuery 和 useEffect 用于简单搜索

useQuery with useEffect for simple search

我有一个非常复杂的组件,现在我正在尝试实现一个搜索,用户可以在其中键入内容并过滤结果。

// 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

即使我输入了一个字母,它也不会呈现在视图上。

根据您的数据,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])

setSearchResultsuseEffect中被调用时,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])