useQuery与useEffect一起进行简单搜索

时间:2020-03-27 03:52:58

标签: reactjs graphql apollo

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

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

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

1 个答案:

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