useQuery с useEffect для простого поиска - PullRequest
0 голосов
/ 27 марта 2020

У меня довольно сложный компонент, и сейчас я пытаюсь реализовать поиск, где пользователь может печатать, и он фильтрует результаты.

// 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 Ответ

1 голос
/ 27 марта 2020

Исходя из ваших данных, начальное значение 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 = {account: [...]}

в это:

searchResults = [.. .]

Именно поэтому он вызывает TypeError: Cannot read property 'filter' of undefined после первого поиска, поскольку ключа accounts больше нет.

Чтобы это исправить, необходимо быть согласованным в типе данных ваш searchResults, было бы лучше сделать его в виде списка в первую очередь. Вы можете сделать это в onCompleted part:

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