Skip to Content

Search inputs

The following are some general guidelines for search inputs:

  • Prefer using server-side search functionality over implementing search filtering on the client side. The latter requires fetching the entire data set and can be inefficient. However, client-side search might be the right choice in cases where the server-side search functionality is insufficient or the expected result size is small. In either case, be careful with handling server-side pagination, as most REST resources have a search result size limit  per request. Use the useOpenmrsPagination, useOpenmrsFetchAll, and useOpenmrsInfinite hooks from @openmrs/esm-framework to handle server-side pagination for different use cases.

    import { restBaseUrl, useOpenmrsFetchAll } from "@openmrs/esm-framework"; // client side search filtering, `data` contains entire data set when `isLoading` is no longer true. const { data, totalCount, isLoading } = useOpenmrsFetchAll<Patient>(`${restBaseUrl}/patient`); // do something with the data
  • Debounce search inputs to prevent unnecessary requests to the backend. Use the useDebounce hook from @openmrs/esm-framework to debounce search inputs. Here’s a snippet (some bits are omitted for brevity) showing how you could use the hook:

    import { useDebounce } from "@openmrs/esm-framework"; const [searchTerm, setSearchTerm] = useState(""); const debouncedSearchTerm = useDebounce(searchTerm); return ( <TableToolbarSearch onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)} placeholder={t("searchThisList", "Search this list")} /> ); // Do something with the debouncedSearchTerm
  • Use fuzzy  to implement fuzzy search, adding it to the package’s dependencies first if the module does not already declare it. Fuzzy search is a strategy for matching search terms that are similar, but not exactly the same, as the search term. For example, if the search term is John, fuzzy search will match Jon, Jhon, and Johhn. This is useful for matching search terms that are misspelled or contain typos. Here’s how we can leverage fuzzy to enhance the search experience from the snippet above:

    const [filter, setFilter] = useState(""); const filteredForms: Array<TypedForm> = useMemo(() => { if (!debouncedSearchTerm) { if (filter === "Retired") { return forms.filter((form) => form.retired); } if (filter === "Published") { return forms.filter((form) => form.published); } if (filter === "Unpublished") { return forms.filter((form) => !form.published); } return forms; } return debouncedSearchTerm ? fuzzy .filter(debouncedSearchTerm, forms, { extract: (form: TypedForm) => `${form.name} ${form.version}`, }) .sort((r1, r2) => r1.score - r2.score) .map((result) => result.original) : forms; }, [filter, forms, debouncedSearchTerm]);

    We’re using the debouncedSearchTerm from the snippet above to filter the list of forms. We’re also using the extract option to tell fuzzy how to extract the search term from the form. In this case, we’re extracting the search term from the form’s name and version. This is because we want to match forms that contain the search term in their name or version. Finally, we’re sorting the results by score, which is a measure of how closely the search term matches the form.

  • When doing data fetching based on continuous user action, such as real-time search when typing in a search input, keeping the previous search results in the UI until the new search results are fetched and displayed can improve the user experience significantly. SWR hooks have a keepPreviousData  option that can be used to achieve this. This approach prevents the UI from flashing empty when the new search results are fetched, which can be jarring to the user.

    // Using keepPreviousData with useOpenmrsSWR for search interface PatientSearchResponse { results: Array<Patient>; } const searchUrl = search ? `${restBaseUrl}/patient?q=${encodeURIComponent(search)}&v=custom:(uuid,display)&limit=10` : null; const { data: response, isLoading } = useOpenmrsSWR<PatientSearchResponse>(searchUrl, { swrConfig: { keepPreviousData: true, }, }); const patients = response?.data?.results ?? [];

    This is especially important for search inputs where users are typing continuously, as it provides a smoother experience by maintaining previous results while new ones load.

    import { restBaseUrl, useOpenmrsSWR } from '@openmrs/esm-framework'; interface PatientSearchResponse { results: Array<Patient>; } function PatientSearch() { const [search, setSearch] = useState(''); const searchUrl = search ? `${restBaseUrl}/patient?q=${encodeURIComponent(search)}&v=custom:(uuid,display)&limit=10` : null; const { data: response, isLoading } = useOpenmrsSWR<PatientSearchResponse>(searchUrl, { swrConfig: { keepPreviousData: true, }, }); const patients = response?.data?.results ?? []; return ( <div> <input type="text" value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search..." /> <div className={isLoading ? "loading" : ""}> {patients.map((patient) => ( <PatientSearchResult key={patient.uuid} patient={patient} /> ))} </div> </div> ); }

    With keepPreviousData set to true, you will still get the previous data even if you change the SWR key and the data for the new key starts loading again:

    Here’s an example of using keepPreviousData in the O3 patient search .

Last updated on