ethyca / fides

The Privacy Engineering & Compliance Framework
https://ethyca.com/docs
Apache License 2.0
359 stars 72 forks source link

Debounce User Management Search Bar #3389

Open RobertKeyser opened 1 year ago

RobertKeyser commented 1 year ago

Is your feature request related to a specific problem?

When searching for users, the search bar on the /user-management page makes an API request to the server with each character typed, even if the user is still typing.

image

Describe the solution you'd like

The field should be debounced so the API requests are only made after the user is done typing.

Describe alternatives you've considered, if any

N/A

Additional context

It's a minor improvement, but it should help with performance and bandwidth.

RobertKeyser commented 1 year ago

I was able to get this to work, but only if I swapped out the <SearchBar> component for the raw <Input> component. I based my changes off of the ChooseConnection component, which also implements the debounce function.

RobertKeyser commented 1 year ago
import { Button, Input, InputGroup, InputLeftElement, SearchLineIcon, Stack } from "@fidesui/react";
import NextLink from "next/link";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";

import { USER_MANAGEMENT_ROUTE } from "~/features/common/nav/v2/routes";
import Restrict from "~/features/common/Restrict";
import SearchBar from "~/features/common/SearchBar";
import { ScopeRegistryEnum } from "~/types/api";

import { selectUserFilters, setUsernameSearch } from "./user-management.slice";
import { debounce } from "../common/utils";

const useUserManagementTableActions = () => {
  const filters = useSelector(selectUserFilters);
  const dispatch = useDispatch();
  const mounted = useRef(false);

  const handleSearchChange = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      console.log(event.target.value);
      dispatch(setUsernameSearch(event.target.value));
    },
    [dispatch]
  );

  const debounceHandleSearchChange = useMemo(
    () => debounce(handleSearchChange, 250),
    [handleSearchChange]
  );

  useEffect(() => {
    mounted.current = true;
    return () => {
      dispatch(setUsernameSearch(""));
      mounted.current = false;
    };
  }, [dispatch]);

  return {
    handleSearchChange,
    debounceHandleSearchChange,
    ...filters,
  };
};

const UserManagementTableActions: React.FC = () => {
  const { debounceHandleSearchChange } = useUserManagementTableActions();

  return (
    <Stack direction="row" spacing={4} mb={6}>
      <InputGroup size="sm">
        <InputLeftElement pointerEvents="none">
          <SearchLineIcon color="gray.300" h="17px" w="17px" />
        </InputLeftElement>
        <Input
          autoComplete="off"
          autoFocus
          borderRadius="md"
          name="search"
          onChange={debounceHandleSearchChange}
          placeholder="Search by Username"
          size="sm"
          type="search"
        />
      </InputGroup>
      <Restrict scopes={[ScopeRegistryEnum.USER_CREATE]}>
        <NextLink href={`${USER_MANAGEMENT_ROUTE}/new`} passHref>
          <Button
            colorScheme="primary"
            flexShrink={0}
            size="sm"
            data-testid="add-new-user-btn"
          >
            Add New User
          </Button>
        </NextLink>
      </Restrict>
    </Stack>
  );
};

export default UserManagementTableActions;