throttle-debounce#debounce TypeScript Examples

The following examples show how to use throttle-debounce#debounce. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ui.ts    From monkeytype with GNU General Public License v3.0 6 votes vote down vote up
debouncedEvent = debounce(250, async () => {
  Caret.updatePosition();
  if (
    Config.tapeMode !== "off" &&
    getActivePage() === "test" &&
    !TestUI.resultVisible
  ) {
    TestUI.scrollTape();
  }
  setTimeout(() => {
    Caret.show();
  }, 250);
})
Example #2
Source File: useElementAspectRatio.tsx    From amazon-chime-sdk-smart-video-sending-demo with Apache License 2.0 6 votes vote down vote up
useElementAspectRatio = (ref: RefObject<HTMLElement>): AspectRatio | null => {
  const [ratio, setRatio] = useState<AspectRatio | null>(null);

  useLayoutEffect(() => {
    if (!ref.current) {
      return;
    }

    const { height, width } = ref.current.getBoundingClientRect();
    setRatio(getAspectRatio(height, width));
  }, []);

  useEffect(() => {
    if (!ref.current) {
      return;
    }

    const handleResize = debounce(50, (entries: any) => {
      const { height, width } = entries[0].contentRect;
      setRatio(getAspectRatio(height, width));
    });

    const resizeObserver = new ResizeObserver(handleResize);
    resizeObserver.observe(ref.current);

    return () => resizeObserver.disconnect();
  }, []);

  return ratio;
}
Example #3
Source File: useDynamicFontSize.tsx    From amazon-chime-live-events with Apache License 2.0 6 votes vote down vote up
useDynamicFontSize = (ref: RefObject<HTMLElement>) => {
  const [fontSize, setFontSize] = useState<string>();

  useLayoutEffect(() => {
    if (!ref.current) {
      return;
    }

    const { width } = ref.current.getBoundingClientRect();
    setFontSize(getNamePlateFontSize(width));
  }, []);

  useEffect(() => {
    const el = ref.current;

    if (!el) {
      return;
    }

    const handleResize = debounce(50, (entries: any) => {
      const { width } = entries[0].contentRect;
      setFontSize(getNamePlateFontSize(width));
    });

    const resizeObserver = new ResizeObserver(handleResize);
    resizeObserver.observe(el);

    return () => resizeObserver.unobserve(el);
  }, []);

  return fontSize;
}
Example #4
Source File: Dropdown.tsx    From design-system with MIT License 6 votes vote down vote up
debounceSearch = debounce(this.props.searchDebounceDuration, () => {
    this.setState(
      {
        searchInit: false,
      },
      () => {
        this.updateOptions(false);
      }
    );
  });
Example #5
Source File: useDebounceState.ts    From react-datasheet-grid with MIT License 6 votes vote down vote up
useDebounceState = <T>(
  defaultValue: T,
  delay: number
): [T, (nextVal: T) => void] => {
  const [debouncedValue, setDebouncedValue] = useState(defaultValue)
  const cancelRef = useRef<debounce<(newValue: T) => void>>()

  useEffect(() => () => cancelRef.current?.cancel(), [])

  const setValue = useMemo(
    () =>
      (cancelRef.current = debounce(delay, (newValue: T) => {
        setDebouncedValue(newValue)
      })),
    [delay]
  )

  return [debouncedValue, setValue]
}
Example #6
Source File: Table.tsx    From design-system with MIT License 6 votes vote down vote up
constructor(props: TableProps) {
    super(props);

    const async = 'fetchData' in this.props;
    const data = props.data || [];
    const schema = props.schema || [];

    this.state = {
      async,
      data: !async ? data : [],
      schema: !async ? schema : [],
      page: props.page,
      sortingList: props.sortingList,
      filterList: props.filterList,
      totalRecords: !async ? data.length : 0,
      loading: !async ? props.loading : true,
      error: !async ? props.error : false,
      errorType: props.errorType,
      selectAll: getSelectAll([]),
      searchTerm: undefined,
    };

    this.debounceUpdate = debounce(props.searchDebounceDuration, this.updateDataFn);
  }
Example #7
Source File: CList.tsx    From Cromwell with MIT License 6 votes vote down vote up
private onScroll = debounce(50, () => {
        const props = this.getProps();
        if (props.useAutoLoading) {
            const minRangeToLoad = props.minRangeToLoad ?? 200;
            if (this.scrollBoxRef.current && this.wrapperRef.current) {
                const scrollTop = this.scrollBoxRef.current.scrollTop;
                const scrollBottom = this.wrapperRef.current.clientHeight - this.scrollBoxRef.current.clientHeight - scrollTop;

                this.setThrobberAutoloadingAfter();

                // Rendered last row from data list, but has more pages to load from server
                if (scrollBottom < minRangeToLoad) {
                    if (this.maxPage > this.maxPageBound) {
                        // console.log('onScroll: need to load next page', this.maxPageBound);
                        if (!this.isPageLoading) {
                            this.loadNextPage();
                            return;
                        }
                    }
                }

                // Rendered first element but has more pages to load previously from server
                // console.log('scrollTop', scrollTop, 'scrollBottom', scrollBottom)
                if (this.minPageBound > 1) {
                    if (scrollTop === 0) this.scrollBoxRef.current.scrollTop = 10;
                    if (scrollTop < minRangeToLoad) {
                        // console.log('onScroll: need to load prev page', this.minPageBound);
                        if (!this.isPageLoading) {
                            this.loadPreviousPage();
                        }
                    }
                }
            }
        }
    })
Example #8
Source File: config-change-manager.ts    From karma-test-explorer with MIT License 6 votes vote down vote up
public constructor(private readonly logger: Logger, options?: ConfigChangeManagerOptions) {
    this.configPrefix = options?.configNamespace ? `${options.configNamespace}.` : '';

    const debouncedConfigChangeHandler = debounce(
      options?.changeHandlingDelay ?? CONFIG_FILE_CHANGE_BATCH_DELAY,
      options?.changeHandlingDelayMode === 'Ending' ? false : true,
      this.handleConfigurationChange.bind(this)
    );

    this.logger.debug(() => 'Creating config change subscription');
    const configChangeSubscription = workspace.onDidChangeConfiguration(debouncedConfigChangeHandler);

    this.disposables.push(configChangeSubscription, logger);
  }
Example #9
Source File: login.ts    From monkeytype with GNU General Public License v3.0 6 votes vote down vote up
checkNameDebounced = debounce(1000, async () => {
  const val = $(
    ".page.pageLogin .register.side .usernameInput"
  ).val() as string;

  if (!val) {
    updateSignupButton();
    return;
  }
  const response = await Ape.users.getNameAvailability(val);

  if (response.status === 200) {
    nameIndicator.show("available", response.message);
  } else if (response.status === 422) {
    nameIndicator.show("unavailable", response.message);
  } else if (response.status === 409) {
    nameIndicator.show("taken", response.message);
  } else {
    nameIndicator.show("unavailable", response.message);
    Notifications.add(
      "Failed to check name availability: " + response.message,
      -1
    );
  }

  updateSignupButton();
})
Example #10
Source File: google-sign-up-popup.ts    From monkeytype with GNU General Public License v3.0 6 votes vote down vote up
checkNameDebounced = debounce(1000, async () => {
  const val = $("#googleSignUpPopup input").val() as string;
  if (!val) return;
  const response = await Ape.users.getNameAvailability(val);

  if (response.status === 200) {
    nameIndicator.show("available", response.message);
    enableButton();
    return;
  }

  if (response.status == 422) {
    nameIndicator.show("unavailable", response.message);
    return;
  }

  if (response.status == 409) {
    nameIndicator.show("taken", response.message);
    return;
  }

  if (response.status !== 200) {
    nameIndicator.show("unavailable");
    return Notifications.add(
      "Failed to check name availability: " + response.message,
      -1
    );
  }
})
Example #11
Source File: file-watcher.ts    From karma-test-explorer with MIT License 5 votes vote down vote up
private createFileWatchers(): Disposable[] {
    this.logger.debug(() => 'Creating file watchers for monitored files');

    const reloadTriggerFilesRelativePaths = this.reloadTriggerFiles.map(triggerFile =>
      normalizePath(relative(this.projectRootPath, triggerFile))
    );

    this.logger.trace(
      () =>
        `Monitored files ( configured --> normalized): ` +
        `${JSON.stringify(this.reloadTriggerFiles, null, 2)} --> ` +
        `${JSON.stringify(reloadTriggerFilesRelativePaths, null, 2)}`
    );

    const reloadTriggerFilesWatchers = this.registerFileHandler(
      reloadTriggerFilesRelativePaths,
      debounce(WATCHED_FILE_CHANGE_BATCH_DELAY, filePath => {
        this.logger.info(() => `Reloading due to monitored file changed: ${filePath}`);
        this.projectCommands.execute(ProjectCommand.Reset);
      })
    );

    this.logger.debug(() => 'Creating file watchers for test file changes');
    const testFileGlobs = this.testFilePatterns;

    const reloadTestFilesWatchers = this.registerFileHandler(testFileGlobs, async (changedTestFile, changeType) => {
      if (!this.testLocator?.isTestFile(changedTestFile)) {
        this.logger.warn(() => `Expected changed file to be spec file but it is not: ${changedTestFile}`);
        return;
      }
      this.logger.debug(() => `Changed file is spec file: ${changedTestFile}`);

      await (changeType === FileChangeType.Deleted
        ? this.testLocator.removeFiles([changedTestFile])
        : this.testLocator.refreshFiles([changedTestFile]));

      if (this.fileWatcherOptions.retireTestsInChangedFiles) {
        const changedTestIds = this.testStore?.getTestsByFile(changedTestFile).map(changedTest => changedTest.id) ?? [];

        if (changedTestIds.length > 0) {
          this.logger.debug(() => `Retiring ${changedTestIds.length} tests from updated spec file: ${changedTestFile}`);
          this.testRetireEventEmitter.fire({ tests: changedTestIds });
        }
      }
    });

    return [...reloadTriggerFilesWatchers, ...reloadTestFilesWatchers];
  }
Example #12
Source File: TalentsScreen.tsx    From jobsgowhere with MIT License 5 votes vote down vote up
TalentsScreen: React.FC = function () {
  const [state, actions] = useTalentsReducer();
  const { isDetailView } = useMobileViewContext();
  const { updateTalents, refreshTalents } = actions;
  const active = Boolean(state.activeTalent) && isDetailView;
  const pageRef = React.useRef<number>(1);
  const auth0Ready = useAuth0Ready();

  const debouncedSearch = debounce(500, false, (query) => {
    const body = { text: query };
    ApiClient.post<PostInterface[]>(`${process.env.REACT_APP_API}/talents/search`, body).then(
      (res) => {
        refreshTalents(res.data);
      },
    );
  });

  const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    debouncedSearch(e.target.value);
  };

  const fetchTalents = React.useCallback(
    (page: number): Promise<void> => {
      return ApiClient.get<PostInterface[]>(`${process.env.REACT_APP_API}/talents/${page}`).then(
        (res) => {
          updateTalents(res.data);
        },
      );
    },
    [updateTalents],
  );

  function handleLoadMore() {
    const nextPage = ++pageRef.current;
    return fetchTalents(nextPage);
  }

  React.useEffect(() => {
    if (auth0Ready) {
      fetchTalents(pageRef.current);
    }
  }, [auth0Ready, fetchTalents]);

  return (
    <Main active={active}>
      <Helmet>
        <title>Talents listing</title>
      </Helmet>
      <Search placeholder="Search talents" onChange={onSearchChange} />
      <CategorySelector category="talents" />
      <PostsContainer>
        {state.fetched ? (
          <>
            {state.talents.map((talent: PostInterface) => (
              <PostBlock key={talent.id}>
                <Post category="talents" active={talent.active} data={talent} />
              </PostBlock>
            ))}
            <PostLoader hasMore={state.more} onLoadMore={handleLoadMore} />
          </>
        ) : (
          <PostSpinner />
        )}
      </PostsContainer>

      <DetailsContainer active={active}>
        {state.activeTalent ? (
          <PostDetail data={state.activeTalent} category="talents" />
        ) : (
          <PostDetailPlaceholder type="talents" />
        )}
      </DetailsContainer>
    </Main>
  );
}
Example #13
Source File: config.ts    From monkeytype with GNU General Public License v3.0 5 votes vote down vote up
saveToDatabase = debounce(1000, () => {
  delete configToSend.resultFilters;
  if (Object.keys(configToSend).length > 0) DB.saveConfig(configToSend);
  configToSend = {} as MonkeyTypes.Config;
})
Example #14
Source File: JobsScreen.tsx    From jobsgowhere with MIT License 5 votes vote down vote up
JobsScreen: React.FC = function () {
  const [state, actions] = usePostsReducer();
  const { isDetailView } = useMobileViewContext();
  const { updateJobs, refreshJobs } = actions;
  const active = Boolean(state.activeJob && isDetailView);
  const pageRef = React.useRef<number>(1);
  const auth0Ready = useAuth0Ready();

  const debouncedSearch = debounce(500, false, (query) => {
    const body = { text: query };
    ApiClient.post<PostInterface[]>(`${process.env.REACT_APP_API}/jobs/search`, body).then(
      (res) => {
        refreshJobs(res.data);
      },
    );
  });

  const onSearchChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    debouncedSearch(e.target.value);
  };

  const fetchJobs = React.useCallback(
    (page: number): Promise<void> => {
      return ApiClient.get<PostInterface[]>(`${process.env.REACT_APP_API}/jobs/${page}`).then(
        (res) => {
          updateJobs(res.data);
        },
      );
    },
    [updateJobs],
  );

  function handleLoadMore() {
    const nextPage = ++pageRef.current;
    return fetchJobs(nextPage);
  }

  React.useEffect(() => {
    if (auth0Ready) {
      fetchJobs(pageRef.current);
    }
  }, [auth0Ready, fetchJobs]);

  return (
    <Main active={active}>
      <Helmet>
        <title>Jobs listing</title>
      </Helmet>
      <Search placeholder="Search job postings" onChange={onSearchChange} />
      <CategorySelector category="jobs" />
      <PostsContainer>
        {state.fetched ? (
          <>
            {state.jobs.map((post: PostInterface) => (
              <PostBlock key={post.id}>
                <Post category="jobs" active={post.active} data={post} />
              </PostBlock>
            ))}
            <PostLoader hasMore={state.more} onLoadMore={handleLoadMore} />
          </>
        ) : (
          <PostSpinner />
        )}
      </PostsContainer>
      <DetailsContainer active={active}>
        {state.activeJob ? (
          <PostDetail data={state.activeJob} category="jobs" />
        ) : (
          <PostDetailPlaceholder type="jobs" />
        )}
      </DetailsContainer>
    </Main>
  );
}
Example #15
Source File: notifications.ts    From monkeytype with GNU General Public License v3.0 5 votes vote down vote up
debouncedMarginUpdate = debounce(100, updateMargin)
Example #16
Source File: Dropdown.tsx    From design-system with MIT License 5 votes vote down vote up
debounceClear = debounce(250, () => this.updateOptions(false));
Example #17
Source File: quote-search-popup.ts    From monkeytype with GNU General Public License v3.0 5 votes vote down vote up
searchForQuotes = debounce(250, (): void => {
  const searchText = (<HTMLInputElement>document.getElementById("searchBox"))
    .value;
  updateResults(searchText);
})
Example #18
Source File: index.tsx    From amazon-chime-live-events with Apache License 2.0 5 votes vote down vote up
VideoGrid: React.FC<Props> = ({ children, size, className }) => {
  const gridEl = createRef<HTMLDivElement>();
  const [ratio, setRatio] = useState<any>();

  useLayoutEffect(() => {
    if (!gridEl.current) {
      return;
    }

    const { height, width } = gridEl.current?.getBoundingClientRect();
    setRatio(getAspectRatio(height, width));
  }, []);

  useEffect(() => {
    const el = gridEl.current;

    if (!el) {
      return;
    }

    const handleResize = debounce(100, (entries: any) => {
      const { height, width } = entries[0].contentRect;
      setRatio(getAspectRatio(height, width));
    });

    const resizeObserver = new ResizeObserver(handleResize);
    resizeObserver.observe(el);

    return () => resizeObserver.unobserve(el);
  }, []);

  return (
    <div
      ref={gridEl}
      className={cx('grid', `grid--size-${size}`, ratio, className)}
    >
      {children}
    </div>
  );
}
Example #19
Source File: Filter.tsx    From Cromwell with MIT License 5 votes vote down vote up
private onPriceRangeChange = debounce(500, (newValue: number[]) => {
    this.priceRange = newValue;
    this.applyFilter();
  });
Example #20
Source File: HeaderSearch.tsx    From Cromwell with MIT License 5 votes vote down vote up
private searchRequest = debounce(500, async (postName: string) => {
        const pagedParams: TPagedParams<TPost> = {
            pageNumber: 1,
            pageSize: 10,
        }
        const filterParams: TPostFilter = {
            titleSearch: postName
        }

        const client = getGraphQLClient();
        if (!this.state.isLoading)
            this.setState({ isLoading: true });

        try {
            const data = await client?.query({
                query: gql`
                    query getFilteredPosts($pagedParams: PagedParamsInput, $filterParams: PostFilterInput) {
                        getFilteredPosts(pagedParams: $pagedParams, filterParams: $filterParams) {
                            pagedMeta {
                                ...PagedMetaFragment
                            }
                            elements {
                                id
                                isEnabled
                                slug
                                title
                                mainImage
                            }
                        }
                    }
                    ${client?.PagedMetaFragment}
                    `,
                variables: {
                    pagedParams,
                    filterParams,
                }
            });
            const elements = data?.data?.getFilteredPosts?.elements;
            if (elements) this.setState({ searchItems: elements });

        } catch (e) {
            console.error(e);
        }
        this.setState({ isLoading: false });
    });
Example #21
Source File: ThemeMarket.tsx    From Cromwell with MIT License 5 votes vote down vote up
private handleFilterInput = debounce(400, () => {
        this.resetList();
    });
Example #22
Source File: EditorBlock.tsx    From Cromwell with MIT License 5 votes vote down vote up
private handleSaveDebounced = debounce(200, async () => {
        this.handleSaveEditor();
    });
Example #23
Source File: PluginMarket.tsx    From Cromwell with MIT License 5 votes vote down vote up
private handleFilterInput = debounce(400, () => {
        this.resetList();
    });
Example #24
Source File: Dashboard.tsx    From Cromwell with MIT License 5 votes vote down vote up
private onLayoutChange = debounce(200, (currentLayout, allLayouts, shouldUpdate?: boolean) => {
        window.localStorage.setItem('crw_dashboard_layout', JSON.stringify(allLayouts));
        if (shouldUpdate) this.forceUpdate();
    })
Example #25
Source File: customFields.tsx    From Cromwell with MIT License 5 votes vote down vote up
RenderCustomFields = (props: {
    entityType: EDBEntity | string;
    entityData: TBasePageEntity;
    refetchMeta: () => Promise<Record<string, string> | undefined | null>;
}) => {
    const { entityType, entityData, refetchMeta } = props;
    const forceUpdate = useForceUpdate();
    customFieldsForceUpdates[entityType] = forceUpdate;
    const [updatedMeta, setUpdatedMeta] = useState<Record<string, string> | null>(null);

    useEffect(() => {
        // If some field registered after this page has fetched entity data, we need to
        // re-request data for this field to get its custom meta
        const onFieldRegistered = debounce(300, async () => {
            const newMeta = await refetchMeta();
            if (newMeta) setUpdatedMeta(newMeta);
        });

        addOnFieldRegisterEventListener(entityType, onFieldRegistered);

        return () => {
            removeOnFieldRegisterEventListener(entityType);
            delete customFieldsForceUpdates[entityType];
        }
    }, []);

    // Just update the values that are undefined, but leave the rest
    // for user input to be untouched
    const customMeta = Object.assign({}, updatedMeta, entityData?.customMeta);

    return <>{Object.values(customFields[entityType] ?? {})
        .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
        .map(field => {
            const Comp = field.component;
            return <Comp
                key={field.key}
                initialValue={customMeta?.[field.key]}
                entity={entityData}
            />
        })}</>
}
Example #26
Source File: Autocomplete.tsx    From Cromwell with MIT License 5 votes vote down vote up
private searchRequest = debounce(500, async () => {
        const list = getBlockInstance<TCList>(this.listId)?.getContentInstance();
        if (!list) {
            return;
        }
        list.clearState();
        list.init();
    });
Example #27
Source File: Filter.tsx    From Cromwell with MIT License 5 votes vote down vote up
private onSearchChange = debounce(500, (newValue: string) => {
    this.search = newValue;
    this.applyFilter();
  });
Example #28
Source File: ProductSearch.tsx    From Cromwell with MIT License 4 votes vote down vote up
/**
 * Search input field. Queries products in the store. Results are shown in pop-up window on user input.
 */
export function ProductSearch(props: ProductSearchProps) {
  const { text, classes } = props;
  const { TextField = BaseTextField, Popper = BasePopper,
    ListItem = DefaultListItem } = props.elements ?? {};

  const [searchOpen, setSearchOpen] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [searchItems, setSearchItems] = useState<TProduct[]>([]);
  const searchAnchorRef = useRef<HTMLDivElement | null>(null);

  const searchRequest = useCallback(debounce(500, async (productName: string) => {
    const pagedParams: TPagedParams<TProduct> = {
      pageNumber: 1,
      pageSize: 10,
    }
    const filterParams = {
      nameSearch: productName
    }
    const client = getGraphQLClient();

    if (!isLoading) setIsLoading(true);

    const fragment = props.customFragment ?? gql`
      fragment ProductSearchFragment on Product {
        id
        isEnabled
        slug
        pageTitle
        name
        price
        oldPrice
        mainImage
      }`;
    const fragmentName = props.customFragmentName ?? 'ProductSearchFragment';

    try {
      const data = await client?.query({
        query: gql`
          query getFilteredProducts($pagedParams: PagedParamsInput, $filterParams: ProductFilterInput) {
            getFilteredProducts(pagedParams: $pagedParams, filterParams: $filterParams) {
              pagedMeta {
                ...PagedMetaFragment
              }
              elements {
                ...${fragmentName}
              }
            }
          }
          ${fragment}
          ${client?.PagedMetaFragment}`,
        variables: {
          pagedParams,
          filterParams,
        }
      });
      const products = data?.data?.getFilteredProducts?.elements;
      if (products) setSearchItems(products);

    } catch (e) {
      console.error(e);
    }

    setIsLoading(false);
  }), []);

  const handleSearchInput = (productName: string) => {
    if (!isLoading) setIsLoading(true);

    if (!searchOpen) {
      setSearchOpen(true);
    }
    searchRequest(productName);
  }

  const handleSearchClose = () => {
    setSearchOpen(false);
  }

  return (
    <div className={clsx(styles.ProductSearch, classes?.root)} ref={searchAnchorRef}>
      <TextField
        label={text?.searchLabel ?? "Search..."}
        onChange={(event) => handleSearchInput(event.currentTarget.value)}
      />
      <Popper open={searchOpen}
        anchorEl={searchAnchorRef.current}
        onClose={handleSearchClose}
      >
        <div className={clsx(styles.searchContent, classes?.content)}
          onClick={handleSearchClose}
        >
          {isLoading && (
            <LoadBox size={100} />
          )}
          {!isLoading && searchItems.length === 0 && (
            <p className={clsx(styles.searchNotFoundText, classes?.notFoundText)}
            >{text?.notFound ?? 'No items found'}</p>
          )}
          {!isLoading && searchItems?.map(product => (
            <ListItem
              key={product.id}
              product={product}
              searchProps={props}
            />
          ))}
        </div>
      </Popper>
    </div>
  );
}
Example #29
Source File: search.tsx    From Cromwell with MIT License 4 votes vote down vote up
SearchPage: TPageWithLayout<SearchPageProps> = (props) => {
    const filterInput = useRef<TPostFilter>({});
    const listId = 'Blog_list_01';
    const publishSort = useRef<"ASC" | "DESC">('DESC');
    const forceUpdate = useForceUpdate();
    const titleSearchId = "post-filter-search";

    const updateList = () => {
        const list = getBlockInstance<TCList>(listId)?.getContentInstance();
        list?.clearState();
        list?.init();
        list?.updateData();
    }

    const handleChangeTags = (event: any, newValue?: (TTag | undefined | string)[]) => {
        filterInput.current.tagIds = newValue?.map(tag => (tag as TTag)?.id);
        forceUpdate();
        updateList();
    }

    const handleGetPosts = (params: TPagedParams<TPost>) => {
        params.orderBy = 'publishDate';
        params.order = publishSort.current;
        return handleGetFilteredPosts(params, filterInput.current);
    }

    const handleChangeSort = (event: SelectChangeEvent<unknown>) => {
        if (event.target.value === 'Newest') publishSort.current = 'DESC';
        if (event.target.value === 'Oldest') publishSort.current = 'ASC';
        updateList();
    }

    const handleTagClick = (tag?: TTag) => {
        if (!tag) return;
        if (filterInput.current.tagIds?.length === 1 &&
            filterInput.current.tagIds[0] === tag.id) return;
        handleChangeTags(null, [tag]);
        forceUpdate();
    }

    const handleTitleInput = debounce(400, () => {
        filterInput.current.titleSearch = (document.getElementById(titleSearchId) as HTMLInputElement)?.value ?? undefined;
        updateList();
    });

    return (
        <CContainer className={commonStyles.content} id="search_01">
            <CContainer className={styles.filter} id="search_02">
                <div className={styles.filterLeft}>
                    <TextField
                        className={styles.filterItem}
                        placeholder="Search by title"
                        id={titleSearchId}
                        variant="standard"
                        onChange={handleTitleInput}
                    />
                    <Autocomplete
                        multiple
                        freeSolo
                        value={filterInput.current.tagIds?.map(id => props.tags?.find(tag => tag.id === id)) ?? []}
                        className={styles.filterItem}
                        options={props.tags ?? []}
                        getOptionLabel={(option: any) => option?.name ?? ''}
                        style={{ width: 300 }}
                        onChange={handleChangeTags}
                        renderInput={(params) => (
                            <TextField
                                {...params}
                                variant="standard"
                                placeholder="Tags"
                            />
                        )}
                    />
                </div>
                <FormControl className={styles.filterItem}>
                    <InputLabel className={styles.sortLabel}>Sort</InputLabel>
                    <Select
                        style={{ width: '100px' }}
                        onChange={handleChangeSort}
                        variant="standard"
                        defaultValue='Newest'
                    >
                        {['Newest', 'Oldest'].map(sort => (
                            <MenuItem value={sort} key={sort}>{sort}</MenuItem>
                        ))}
                    </Select>
                </FormControl>
            </CContainer>
            <CContainer style={{ marginBottom: '20px' }} id="search_03">
                <CList<TPost>
                    id={listId}
                    ListItem={(props) => (
                        <div className={styles.postWrapper}>
                            <PostCard onTagClick={handleTagClick} data={props.data} key={props.data?.id} />
                        </div>
                    )}
                    usePagination
                    useShowMoreButton
                    useQueryPagination
                    disableCaching
                    pageSize={20}
                    scrollContainerSelector={`.${layoutStyles.Layout}`}
                    firstBatch={props.posts}
                    loader={handleGetPosts}
                    cssClasses={{
                        page: styles.postList
                    }}
                    elements={{
                        pagination: Pagination
                    }}
                />
            </CContainer>
        </CContainer>
    );
}