Skip to content

Commit

Permalink
debounce search text and use react query to cache requests
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffibm committed May 7, 2024
1 parent d52357d commit 8cce049
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 61 deletions.
113 changes: 56 additions & 57 deletions app/javascript/components/AeInlineMethod/NamespaceSelector.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useQuery } from 'react-query';
import { Loading } from 'carbon-components-react';
import { debounce } from 'lodash';
import FilterNamespace from './FilterNamespace';
import MiqDataTable from '../miq-data-table';
import NotificationMessage from '../notification-message';
Expand All @@ -10,93 +12,90 @@ import {
} from './helper';
import './style.scss';

/** Component to search and select AeMethods. */
const NamespaceSelector = ({ onSelectMethod, selectedIds }) => {
const [data, setData] = useState({
domains: [],
methods: [],
loading: true,
searchText: undefined,
selectedDomain: undefined,
});
const [filterData, setFilterData] = useState({ searchText: '', selectedDomain: '' });

/** Function to update the component state data. */
const updateData = (updatedData) => setData({ ...data, ...updatedData });
/** Loads the domains and stores in domainData for 60 seconds. */
const { data: domainsData, isLoading: domainsLoading } = useQuery(
'domainsData',
async() => (await http.get(namespaceUrls.aeDomainsUrl)).domains,
{
staleTime: 60000,
}
);

/** Loads the 'domains' and 'methods' from its respective URL's during the component's onLoad event. */
useEffect(() => {
Promise.all([
http.get(namespaceUrls.aeDomainsUrl),
http.get(namespaceUrls.aeMethodsUrl)])
.then(([{ domains }, { methods }]) => updateData({ loading: false, domains, methods: formatMethods(methods) }));
}, []);
/** Loads the methods and stores in methodsData for 60 seconds.
* If condition works on page load
* Else part would work if there is a change in filterData.
*/
const { data, isLoading: methodsLoading } = useQuery(
['methodsData', filterData.searchText, filterData.selectedDomain],
async() => {
if (!filterData.searchText && !filterData.selectedDomain) {
const response = await http.get(namespaceUrls.aeMethodsUrl);
return formatMethods(response.methods);
}
const url = searchUrl(filterData.selectedDomain, filterData.searchText);
const response = await http.get(url);
return formatMethods(response.methods);
},
{
keepPreviousData: true,
refetchOnWindowFocus: false,
staleTime: 60000,
}
);

const retrieveMethods = async(url) => {
const response = await http.get(url);
return response.methods;
};
/** Debounce the search text by delaying the text input provided to the API. */
const debouncedSearch = debounce((newFilterData) => {
setFilterData(newFilterData);
}, 300);

/** Function to handle search text and drop-down item onchange events. */
const onSearch = async(filterData) => {
updateData({ loading: true });
const searchText = filterData.searchText ? filterData.searchText : data.searchText;
const selectedDomain = filterData.selectedDomain ? filterData.selectedDomain : data.selectedDomain;
const url = searchUrl(selectedDomain, searchText);
try {
const methods = await retrieveMethods(url);
updateData({
loading: false,
selectedDomain,
searchText,
methods: formatMethods(methods),
});
} catch (error) {
console.error('Error retrieving methods:', error);
updateData({ loading: false }); // Update loading state even if there's an error
}
};
/** Function to handle the onSearch event during a filter change event. */
const onSearch = (newFilterData) => debouncedSearch(newFilterData);

/** Function to handle the click events for the list. */
/** Function to handle the click event of a cell in the data table. */
const onCellClick = (selectedRow, cellType, checked) => {
const selectedItems = cellType === CellAction.selectAll
? data.methods.map((item) => item.id)
? data && data.map((item) => item.id)
: [selectedRow];
onSelectMethod({ selectedItems, cellType, checked });
};

/** Function to render the contents of the list. */
const renderContents = () => (data.methods && data.methods.length > 0
? (
const renderContents = () => {
if (!data || data.length === 0) {
return <NotificationMessage type="info" message={__('No methods available.')} />;
}

return (
<MiqDataTable
headers={methodSelectorHeaders}
stickyHeader
rows={data.methods}
rows={data}
mode="miq-inline-method-list"
rowCheckBox
sortable={false}
gridChecks={selectedIds}
onCellClick={(selectedRow, cellType, event) => onCellClick(selectedRow, cellType, event.target.checked)}
/>
)
: <NotificationMessage type="error" message={__('No methods available.')} />);
);
};

return (
<div className="inline-method-selector">
<FilterNamespace domains={data.domains} onSearch={(filterData) => onSearch(filterData)} />
<FilterNamespace domains={domainsData} onSearch={onSearch} />
<div className="inline-contents-wrapper">
{
data.loading
? <Loading active small withOverlay={false} className="loading" />
: renderContents()
}
{(domainsLoading || methodsLoading)
? <Loading active small withOverlay={false} className="loading" />
: renderContents()}
</div>
</div>
);
};

export default NamespaceSelector;

NamespaceSelector.propTypes = {
onSelectMethod: PropTypes.func.isRequired,
selectedIds: PropTypes.arrayOf(PropTypes.any).isRequired,
};

export default NamespaceSelector;
13 changes: 9 additions & 4 deletions app/javascript/components/AeInlineMethod/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import PropTypes from 'prop-types';
import {
Modal, Button, ModalBody, Accordion, AccordionItem,
Expand All @@ -12,6 +13,8 @@ import { formatListMethods, methodListHeaders, namespaceUrls } from './helper';

/** Component to render a tree and to select an embedded method. */
const AeInlineMethod = ({ type }) => {
const queryClient = new QueryClient();

const [data, setData] = useState({
isModalOpen: false,
selectedIds: [],
Expand Down Expand Up @@ -80,10 +83,12 @@ const AeInlineMethod = ({ type }) => {
{
data.isModalOpen
&& (
<NamespaceSelector
onSelectMethod={({ selectedItems, cellType, checked }) => onSelectMethod(selectedItems, cellType, checked)}
selectedIds={data.selectedIds}
/>
<QueryClientProvider client={queryClient}>
<NamespaceSelector
onSelectMethod={({ selectedItems, cellType, checked }) => onSelectMethod(selectedItems, cellType, checked)}
selectedIds={data.selectedIds}
/>
</QueryClientProvider>
)
}
</ModalBody>
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"jquery-ujs": "~1.2.2",
"jquery.hotkeys": "~0.1.0",
"jquery.observe_field": "~0.1.0",
"loadash": "^1.0.0",
"lodash": "~4.17.10",
"moment": "~2.29.4",
"moment-duration-format": "~2.2.2",
Expand All @@ -90,6 +91,7 @@
"react-codemirror2": "^6.0.0",
"react-dom": "~16.13.1",
"react-markdown": "6.0.0",
"react-query": "^3.39.3",
"react-redux": "^7.1.1",
"react-router": "~5.1.2",
"react-router-dom": "~5.1.2",
Expand Down

0 comments on commit 8cce049

Please sign in to comment.