I am trying to implement a responsive search bar that filters a list of pure components (max 100 components show at once). However, there is a half-second freeze between typing the initial letter and having it show up in the search bar with the rendered list of components below it. I believe it is the number of rendered components that causes this delay since limiting the render amount to ~20 makes the response near-instant.
What are some options for me to minimize this delay and create an almost instant render?
I have looked at lazy-loading, but I can only find methods that dynamically load based on the user scrolling. Is there a method that splits the rendering of the list over time so there isn't one long pause?
I have also looked at React.memo
but the delay is when I go from 0 -> 100 components so caching after the initial loading doesn't really help. Is there way of preloading these components into a cache?
const SearchPage = ({ dataMap }) => {
const [searchResults, setSearchResults] = useState([]);
const onInput = (e) => {
// near instant response when limit is ~20
setSearchResults(searchIndex.search(e.target.value, { limit: 100 }));
};
return (
<div>
<SearchBar onInput={onInput} />
<DataListTable
dataList={searchResults.map((dataId) => (dataMap[dataId]))}
/>
</div>
);
};
const DataListTable = ({ dataList }) => (
<table className="table">
<thead>
<tr>
<th>Inputs</th>
<th>Outputs</th>
<th>Requirement</th>
</tr>
</thead>
<tbody>
{dataList.map((data) => (
<DataRow data={data} key={data.dataId} />
))}
</tbody>
</table>
);
const DataRow = ({ data }) => {}
export default memo(DataRow);
CodePudding user response:
There are several things I can suggest to you, but bear in mind that I am not really sure if it will definitely improve the performance of your components.
- Your
SearchPage
component is coupled to handle the updating of yoursearchResults
data. I think that this could potentially cause performance issues and from a software design principle (precisely the Single-Responsibility principle) it looks like it's badly designed. What I would do in this case is that I would move thestate
to yourDataListTable
component and instead pass the search value as a property to it:
const DataListTable = ({ search }) => {
const [searchResults, setSearchResults] = useState([]);
useEffect(() => {
setSearchResults(searchIndex.search(search, { limit: 100 }));
}, [search]);
return (
<table className="table">
<thead>
<tr>
<th>Inputs</th>
<th>Outputs</th>
<th>Requirement</th>
</tr>
</thead>
<tbody>
{searchResults.map((data) => (
<DataRow data={data} key={data.dataId} />
))}
</tbody>
</table>
);
};
Then your SearchPage
could look like this:
const SearchPage = () => {
const [search, setSearch] = useState('');
const onInput = (e) => {
setSearch(e.target.value);
};
return (
<div>
<SearchBar onInput={onInput} />
<DataListTable search={search} />
</div>
);
};
- About your
dataMap
object: I think that you should map the objects after you receive the results from your API endpoint. You shouldn't do data mapping directly inside of the component. You could do it inside of yoursearchIndex.search
method. A hypothetical example would be:
searchService.js
export const search = async (term, options) => {
const response = await api.getDataListTableData(term, options);
const mappedResponse = response.map((x) => ({
id: x._id,
name: `${x.first_name} ${x.last_name}`,
}));
return mappedResponse;
};
And then simply call it inside of your DataListTable
component:
import * as searchIndex from './searchService';
useEffect(() => {
searchIndex.search(search, { limit: 100 }).then((data) => setSearchResults(data));
}, [search]);
- One another thing you could do is to debounce the search result, because if you don't debounce it, it could cause 2 drawbacks:
- Your API is burdened with unnecessary calls
- Your
DataListTable
component is burdened with unnecessary re-renders
For the purpose of debouncing, you could use this useDebounce
hook, taken from this article:
const useDebounce = (value, timeout) => {
// Save a local copy of `value` in this state which is local to our hook
const [state, setState] = useState(value);
useEffect(() => {
// Set timeout to run after delay
const handler = setTimeout(() => setState(value), timeout);
// clear the setTimeout listener on unMount
return () => clearTimeout(handler);
}, [value, timeout]);
return state;
};
And then just use the useDebounce
inside of your SearchPage
component:
const SearchPage = () => {
const [search, setSearch] = useState('');
const debouncedSearchQuery = useDebounce(search, 500);
const onInput = (e) => {
setSearch(e.target.value);
};
return (
<div>
<SearchBar onInput={onInput} />
<DataListTable search={debouncedSearchQuery} />
</div>
);
};
I hope that some of these things will prove to be helpful. Good luck!