Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add SearchController #1434

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

Conversation

MartinCupela
Copy link
Contributor

Description of the changes, What, Why and How?

This is an addition of a new classes that control the search logic.

  1. SearchController
  • Presents a public API for coordination of search activities btw search sources.
  • Is not aware of what data is being searched
  • Delegates the data retrieval logic to search sources
  • Has own reactive state used by integrators in the UI components
  1. Search sources
  • Take care of retrieving the searched information from the back-end
  • Contain data-specific logic
  • Handle pagination
  • Have own reactive state related to pagination over search results

Advantages:

  • Data retrieval logic and search parameters are fully customizable by integrators
  • Integrators can subscribe to data changes of the reactive states => no need for onEvent props in the UI components

Copy link
Contributor

github-actions bot commented Jan 16, 2025

Size Change: +19.3 kB (+4.13%)

Total Size: 486 kB

Filename Size Change
dist/browser.es.js 106 kB +4.17 kB (+4.1%)
dist/browser.full-bundle.min.js 59.8 kB +2.43 kB (+4.24%)
dist/browser.js 107 kB +4.24 kB (+4.12%)
dist/index.es.js 106 kB +4.17 kB (+4.1%)
dist/index.js 107 kB +4.24 kB (+4.12%)

compressed-size-action

hasMore: boolean;
isActive: boolean;
isLoading: boolean;
items: T[] | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
items: T[] | undefined;
items: T[] | undefined;

Why undefined? Can't it be an empty array instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value undefined says that no search has been executed yet. This is handy to know if we want to differ btw the state where will tell the user to start search vs telling the user there are not results for the given search query.

We could think that maybe the same could be inferred from the combination of the length of the search query and the length of the items array. The problem is, when you start typing, for a split of a second, the value of loading is not set. In that case you have a search query of non-zero lenght, loading is false and results are an empty array. That leads to showing "No results found" or whatever the empty results component is. So I would say we need an indicator that tells us, whether the items value is the result of a search query.

Comment on lines 140 to 142
if (this.searchQuery) {
this.search();
}
Copy link
Contributor

@arnautov-anton arnautov-anton Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it'd be better to hide this side-effect behind a state-chage subscription of sorts or is it something you'd want to avoid in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will remove the side effect from both the source and controller activate methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.searchDebounced = debounce(this.executeQuery.bind(this), debounceMs);
};

activate = (sourceStateOverride?: Partial<SearchSourceState>) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the partial state here? I'd say activate should flip active to true and all the other side-effects should happen elsewhere. Multiple StateStore.next calls seem better than unexpected state changes seemingly unrelated to the method call.

Comment on lines +156 to +157
if (hasNewSearchQuery) {
this.resetState({ isActive: this.isActive, isLoading: true, searchQuery: newSearchString });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep the behavior similar across the newly released classes, resetState shouldn't take any value but rather set the store to its initial state (at class construction). Any other modification should happen after the fact:

Suggested change
if (hasNewSearchQuery) {
this.resetState({ isActive: this.isActive, isLoading: true, searchQuery: newSearchString });
if (hasNewSearchQuery) {
this.resetState();
this.state.partialNext({ isLoading: true, searchQuery: newSearchString })

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if you want to avoid two calls:

if (hasNewSearchQuery) {
    this.state.next((current) => {
        ...current,
        ...this.initialState,
        // overrides
        isActive: current.isActive,
        isLoading: true,
        searchQuery: newSearchString
    });

stateUpdate.lastQueryError = e as Error;
} finally {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
this.state.next(({ lastQueryError, ...prev }: SearchSourceState<T>) => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.state.next(({ lastQueryError, ...prev }: SearchSourceState<T>) => ({
this.state.next((current) => ({

Also, stateUpdate seems to get unwrapped after the "prev" (current state) and unless it (stateUpdate) contains reset value it does not really seem necessary to exclude it (reset value is not being set in the try block).

...prev,
...stateUpdate,
isLoading: false,
items: [...(prev.items ?? []), ...(stateUpdate.items || [])],
Copy link
Contributor

@arnautov-anton arnautov-anton Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eliminating the undefined from T[] | undefined would seemingly reduce the unnecessary complexity here.

Comment on lines +195 to +205
this.state.next({
hasMore: true,
isActive: false,
isLoading: false,
items: undefined,
lastQueryError: undefined,
next: undefined,
offset: 0,
searchQuery: '',
...stateOverrides,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where initialState would get set at the beginning (during construction) and would be private readonly property.

Suggested change
this.state.next({
hasMore: true,
isActive: false,
isLoading: false,
items: undefined,
lastQueryError: undefined,
next: undefined,
offset: 0,
searchQuery: '',
...stateOverrides,
});
this.state.next(this.initialState)

clear = () => {
this.cancelSearchQueries();
this.sources.forEach((source) => source.resetState({ isActive: source.isActive }));
this.state.next((prev) => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
this.state.next((prev) => ({
this.state.next((current) => ({

const { debounceMs, isActive, pageSize } = { ...DEFAULT_SEARCH_SOURCE_OPTIONS, ...options };
this.pageSize = pageSize;
this.state = new StateStore<SearchSourceState<T>>({
hasMore: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
hasMore: true,
hasNext: true,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants