首页 > 解决方案 > useQuery 和 useEffect 用于简单搜索

问题描述

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

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

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

标签: reactjsgraphqlapollo

解决方案


根据您的数据, 的初始值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从对象变为数组:

由此:

搜索结果 = { 帐户:[ ... ] }

对此:

搜索结果 = [ ... ]

这就是为什么它TypeError: Cannot read property 'filter' of undefined在第一次搜索后引发,因为不再有accounts密钥了。

要解决这个问题,您需要在您的数据类型上保持一致searchResults,最好首先将其设置为 List。您可以在以下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])

小费:

您可能需要重命名searchResultsaccounts以使其更清晰。另请注意,在第一次搜索后,您的选项将仅限于以前的搜索结果,因此您可能还希望将所有帐户存储在不同的变量中:

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])

推荐阅读