@bitrise/bitkit 13.341.0 → 13.343.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bitrise/bitkit",
3
3
  "description": "Bitrise React component library",
4
- "version": "13.341.0",
4
+ "version": "13.343.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+ssh://git@github.com/bitrise-io/bitkit.git"
@@ -1,4 +1,4 @@
1
- import { FormEvent, useEffect, useMemo, useState } from 'react';
1
+ import { FormEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { isEqual, useDebounce } from '../../../utils/utils';
3
3
  import { useFilterContext } from '../Filter.context';
4
4
  import { FilterFormProps, FilterOptions, FilterOptionsMap, FilterValue } from '../Filter.types';
@@ -26,52 +26,56 @@ const useFilterForm = (props: Omit<FilterFormProps, 'onCancel'>) => {
26
26
  );
27
27
 
28
28
  const [searchValue, setSearchValue] = useState<string>('');
29
- const debouncedSearchValue = useDebounce<string>(searchValue, 1000);
29
+ const debouncedSearchValue = useDebounce<string>(searchValue, 500);
30
30
 
31
31
  const [isLoading, setLoading] = useState(Boolean(isInitialLoading));
32
32
  const [foundOptions, setFoundOptions] = useState<FilterOptions>([]);
33
33
  const [foundOptionsMap, setFoundOptionsMap] = useState<FilterOptionsMap>();
34
34
 
35
+ const latestRequestRef = useRef(0);
36
+
35
37
  const isAsync = !!onAsyncSearch;
36
38
  const withSearch = (options && options.length > 5) || isAsync;
37
39
 
38
40
  const isSubmitDisabled =
39
41
  isEqual(selected, value) || (mode === 'pattern' && !!selected[0] && !selected[0].includes('*'));
42
+
40
43
  const filteredOptions = useMemo(() => {
41
- if (options?.length) {
42
- return options.filter((opt) => {
43
- const optLabel = (optionsMap && optionsMap[opt]) || opt;
44
+ return (
45
+ options?.filter((opt) => {
46
+ const optLabel = optionsMap?.[opt] || opt;
44
47
  return optLabel.toLowerCase().includes(searchValue.toLowerCase());
45
- });
46
- }
47
- return [];
48
- }, [searchValue, options]);
49
-
50
- const onSearchChange = (q: string) => {
51
- setSearchValue(q);
52
- if (isAsync) {
53
- if (q.length > 0) {
54
- setLoading(true);
55
- } else {
48
+ }) || []
49
+ );
50
+ }, [searchValue, options, optionsMap]);
51
+
52
+ const onSearchChange = useCallback(
53
+ (q: string) => {
54
+ setSearchValue(q);
55
+ if (isAsync && q.length === 0) {
56
56
  setFoundOptions([]);
57
57
  }
58
- }
59
- };
58
+ },
59
+ [isAsync],
60
+ );
60
61
 
61
- const onSubmit = (e?: FormEvent<HTMLDivElement>) => {
62
- e?.preventDefault();
63
- const newSelected = selected[0] === '' ? [] : selected;
64
- onChange(category, newSelected, value);
65
- };
62
+ const onSubmit = useCallback(
63
+ (e?: FormEvent<HTMLDivElement>) => {
64
+ e?.preventDefault();
65
+ const newSelected = selected[0] === '' ? [] : selected;
66
+ onChange(category, newSelected, value);
67
+ },
68
+ [selected, onChange, category, value],
69
+ );
66
70
 
67
- const onClearClick = () => {
71
+ const onClearClick = useCallback(() => {
68
72
  let newValue: FilterValue = [];
69
73
  if (data[category]?.type === 'switch') {
70
74
  newValue = ['all'];
71
75
  }
72
76
  setSelected(newValue);
73
77
  onChange(category, newValue, value);
74
- };
78
+ }, [data, category, onChange, value]);
75
79
 
76
80
  const getEmptyText = () => {
77
81
  if (searchValue.length) {
@@ -80,36 +84,60 @@ const useFilterForm = (props: Omit<FilterFormProps, 'onCancel'>) => {
80
84
  return '';
81
85
  };
82
86
 
83
- const getAsyncList = async () => {
84
- if (onAsyncSearch) {
85
- const response = await onAsyncSearch(category, searchValue);
86
- setLoading(false);
87
- setFoundOptions(response.options);
88
- setFoundOptionsMap(response.optionsMap || optionsMap);
87
+ const getAsyncList = useCallback(async () => {
88
+ if (!onAsyncSearch) return;
89
+
90
+ latestRequestRef.current += 1;
91
+ const requestId = latestRequestRef.current;
92
+ setLoading(true);
93
+
94
+ try {
95
+ const response = await onAsyncSearch(category, debouncedSearchValue);
96
+
97
+ if (requestId === latestRequestRef.current) {
98
+ setFoundOptions(response.options);
99
+ setFoundOptionsMap(response.optionsMap || optionsMap);
100
+ setLoading(false);
101
+ }
102
+ } catch (error) {
103
+ if (requestId === latestRequestRef.current) {
104
+ setLoading(false);
105
+ }
89
106
  }
90
- };
107
+ }, [onAsyncSearch, category, debouncedSearchValue, optionsMap]);
91
108
 
92
109
  useEffect(() => {
93
- setLoading(Boolean(isInitialLoading));
110
+ setLoading(!!isInitialLoading);
94
111
  }, [isInitialLoading]);
95
112
 
96
113
  useEffect(() => {
97
- if (debouncedSearchValue.length > 0) {
114
+ if (isAsync && debouncedSearchValue) {
98
115
  getAsyncList();
99
- } else {
100
- setLoading(Boolean(isInitialLoading));
116
+ } else if (isAsync && !debouncedSearchValue) {
117
+ setFoundOptions([]);
118
+ setLoading(!!isInitialLoading);
101
119
  }
102
- }, [debouncedSearchValue]);
120
+ }, [debouncedSearchValue, isAsync, getAsyncList, isInitialLoading]);
103
121
 
104
- const isEditMode = value.length !== 0;
122
+ const isEditMode = !!value.length;
123
+ const isAsyncSearch = isAsync && !!debouncedSearchValue && !!searchValue;
124
+ const currentOptionMap = isAsyncSearch ? foundOptionsMap : optionsMap;
105
125
 
106
- const isAsyncSearch = isAsync && !!searchValue;
126
+ const items = useMemo(() => {
127
+ let baseItems: string[] = [];
107
128
 
108
- const items = isAsyncSearch
109
- ? Array.from(new Set(preserveOptionOrder ? [...foundOptions] : [...value, ...foundOptions]))
110
- : Array.from(new Set(preserveOptionOrder ? [...filteredOptions] : [...value, ...filteredOptions]));
129
+ if (isAsync && searchValue) {
130
+ baseItems = debouncedSearchValue ? foundOptions : options || [];
131
+ } else {
132
+ baseItems = searchValue ? filteredOptions : options || [];
133
+ }
111
134
 
112
- const currentOptionMap = isAsyncSearch ? foundOptionsMap : optionsMap;
135
+ if (preserveOptionOrder) {
136
+ return Array.from(new Set(baseItems));
137
+ }
138
+
139
+ return Array.from(new Set([...value, ...baseItems]));
140
+ }, [isAsync, debouncedSearchValue, searchValue, foundOptions, filteredOptions, options, preserveOptionOrder, value]);
113
141
 
114
142
  return {
115
143
  currentOptionMap,