66 type DenormalizedQuery ,
77 type IBuilderFieldProps ,
88} from '@vojtechportes/react-query-builder' ;
9+ import { waitForTimeout } from './utils/wait-for-timeout.util' ;
910
1011const DemoCard = styled . div `
1112 display: grid;
@@ -16,36 +17,6 @@ const DemoCard = styled.div`
1617 background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
1718` ;
1819
19- const Toolbar = styled . div `
20- display: flex;
21- flex-wrap: wrap;
22- gap: 0.75rem;
23- align-items: center;
24- justify-content: space-between;
25- ` ;
26-
27- const StatusRow = styled . div `
28- display: flex;
29- flex-wrap: wrap;
30- gap: 0.75rem;
31- font-size: 0.92rem;
32- color: #475569;
33- ` ;
34-
35- const SmallButton = styled . button `
36- padding: 0.6rem 0.9rem;
37- border: 1px solid #cbd5e1;
38- border-radius: 999px;
39- background: #fff;
40- color: #0f172a;
41- cursor: pointer;
42-
43- &:hover {
44- border-color: #93c5fd;
45- background: #eff6ff;
46- }
47- ` ;
48-
4920const fields : IBuilderFieldProps [ ] = [
5021 {
5122 field : 'COUNTRY' ,
@@ -74,153 +45,88 @@ const initialData: DenormalizedQuery = [
7445 isNegated : false ,
7546 children : [
7647 {
77- field : 'COUNTRY' ,
78- operator : 'EQUAL' ,
79- value : 'CZ' ,
48+ type : 'GROUP' ,
49+ value : 'AND' ,
50+ isNegated : false ,
51+ children : [
52+ {
53+ field : 'COUNTRY' ,
54+ operator : 'EQUAL' ,
55+ value : 'CZ' ,
56+ } ,
57+ {
58+ field : 'CITY' ,
59+ operator : 'EQUAL' ,
60+ value : 'PRG' ,
61+ } ,
62+ ] ,
8063 } ,
8164 {
82- field : 'CITY' ,
83- operator : 'EQUAL' ,
84- value : '' ,
65+ type : 'GROUP' ,
66+ value : 'AND' ,
67+ isNegated : false ,
68+ children : [
69+ {
70+ field : 'COUNTRY' ,
71+ operator : 'EQUAL' ,
72+ value : 'SK' ,
73+ } ,
74+ {
75+ field : 'CITY' ,
76+ operator : 'EQUAL' ,
77+ value : 'BTS' ,
78+ } ,
79+ ] ,
8580 } ,
8681 ] ,
8782 } ,
8883] ;
8984
85+ const cityOptionsByCountry = {
86+ CZ : [
87+ { value : 'PRG' , label : 'Prague' } ,
88+ { value : 'BRN' , label : 'Brno' } ,
89+ { value : 'OSR' , label : 'Ostrava' } ,
90+ ] ,
91+ SK : [
92+ { value : 'BTS' , label : 'Bratislava' } ,
93+ { value : 'KSC' , label : 'Kosice' } ,
94+ { value : 'ZIL' , label : 'Zilina' } ,
95+ ] ,
96+ DE : [
97+ { value : 'BER' , label : 'Berlin' } ,
98+ { value : 'MUC' , label : 'Munich' } ,
99+ { value : 'HAM' , label : 'Hamburg' } ,
100+ ] ,
101+ } as const ;
102+
90103export const ImperativeFieldOptionsDemo : React . FC = ( ) => {
91104 const [ data , setData ] = React . useState < DenormalizedQuery > ( initialData ) ;
92- const [ cityOptionsStatus , setCityOptionsStatus ] = React . useState <
93- 'idle' | 'loading' | 'success' | 'error'
94- > ( 'idle' ) ;
95- const [ cityReloadToken , setCityReloadToken ] = React . useState ( 0 ) ;
96105 const builderRef = useBuilderRef ( ) ;
97- const cityRuleInUse = React . useMemo (
98- ( ) =>
99- data . some (
100- node =>
101- 'children' in node &&
102- node . children . some (
103- child => 'field' in child && child . field === 'CITY'
104- )
105- ) ,
106- [ data ]
107- ) ;
108- const selectedCountry = React . useMemo ( ( ) => {
109- const rootGroup = data [ 0 ] ;
110- const countryRule =
111- rootGroup && 'children' in rootGroup
112- ? rootGroup . children . find (
113- child => 'field' in child && child . field === 'COUNTRY'
114- )
115- : undefined ;
116-
117- return countryRule && 'value' in countryRule && typeof countryRule . value === 'string'
118- ? countryRule . value
119- : '' ;
120- } , [ data ] ) ;
121-
122- const handleReloadCityOptions = React . useCallback ( ( ) => {
123- setCityReloadToken ( token => token + 1 ) ;
124- } , [ ] ) ;
125-
126106 React . useEffect ( ( ) => {
127- if ( ! cityRuleInUse ) {
128- setCityOptionsStatus ( 'idle' ) ;
129- builderRef . current ?. clearFieldOptions ( 'CITY' ) ;
130- return ;
131- }
132-
133- builderRef . current ?. invalidateFieldOptions ( 'CITY' ) ;
134- builderRef . current ?. setFieldOptionsStatus ( 'CITY' , 'loading' ) ;
135- setCityOptionsStatus ( 'loading' ) ;
136-
137- const timeoutId = window . setTimeout ( ( ) => {
138- if ( selectedCountry === 'CZ' ) {
139- builderRef . current ?. setFieldOptions ( 'CITY' , [
140- { value : 'PRG' , label : 'Prague' } ,
141- { value : 'BRN' , label : 'Brno' } ,
142- { value : 'OSR' , label : 'Ostrava' } ,
143- ] ) ;
144- } else if ( selectedCountry === 'SK' ) {
145- builderRef . current ?. setFieldOptions ( 'CITY' , [
146- { value : 'BTS' , label : 'Bratislava' } ,
147- { value : 'KSC' , label : 'Kosice' } ,
148- { value : 'ZIL' , label : 'Zilina' } ,
149- ] ) ;
150- } else if ( selectedCountry === 'DE' ) {
151- builderRef . current ?. setFieldOptions ( 'CITY' , [
152- { value : 'BER' , label : 'Berlin' } ,
153- { value : 'MUC' , label : 'Munich' } ,
154- { value : 'HAM' , label : 'Hamburg' } ,
155- ] ) ;
156- } else {
157- builderRef . current ?. setFieldOptions ( 'CITY' , [ ] ) ;
158- }
107+ return builderRef . bindRuleOptions ( 'CITY' , {
108+ dependencies : [ 'COUNTRY' ] ,
109+ resolve : async ( { dependencies, signal } ) => {
110+ const countryValue = dependencies . COUNTRY ?. value ;
159111
160- setCityOptionsStatus ( 'success' ) ;
161- } , 650 ) ;
112+ if ( typeof countryValue !== 'string' ) {
113+ return [ ] ;
114+ }
162115
163- return ( ) => window . clearTimeout ( timeoutId ) ;
164- } , [ builderRef , cityRuleInUse , cityReloadToken , selectedCountry ] ) ;
116+ await waitForTimeout ( 550 , signal ) ;
117+ return cityOptionsByCountry [ countryValue ] ?? [ ] ;
118+ } ,
119+ onOptionsResolved : ( { ruleId } ) => {
120+ builderRef . reconcileRuleValueWithOptions ( ruleId , {
121+ strategy : 'clear-if-missing' ,
122+ } ) ;
123+ } ,
124+ } ) ;
125+ } , [ builderRef ] ) ;
165126
166127 return (
167128 < DemoCard >
168- < Toolbar >
169- < SmallButton
170- type = "button"
171- onClick = { ( ) => {
172- const rootGroupId = builderRef . current
173- ?. getNodes ( )
174- . find ( ( node ) => 'type' in node ) ?. id ;
175- const cityRuleId = builderRef . current
176- ?. getNodes ( )
177- . find ( ( node ) => 'field' in node && node . field === 'CITY' ) ?. id ;
178-
179- if ( cityRuleId ) {
180- builderRef . current ?. deleteNode ( cityRuleId ) ;
181- return ;
182- }
183-
184- if ( rootGroupId ) {
185- builderRef . current ?. addRule (
186- {
187- field : 'CITY' ,
188- operator : 'EQUAL' ,
189- value : '' ,
190- } ,
191- rootGroupId
192- ) ;
193- }
194- } }
195- >
196- { cityRuleInUse ? 'Remove city rule' : 'Add city rule' }
197- </ SmallButton >
198- < SmallButton
199- type = "button"
200- onClick = { ( ) => builderRef . current ?. reloadFieldOptions ( 'CITY' ) }
201- >
202- Reload city options
203- </ SmallButton >
204- </ Toolbar >
205- < StatusRow >
206- < span > City rule in scope: { cityRuleInUse ? 'yes' : 'no' } </ span >
207- < span > City options status: { cityOptionsStatus } </ span >
208- < span >
209- Runtime options:{ ' ' }
210- { builderRef . current ?. getFieldOptionState ( 'CITY' ) . options . length ?? 0 }
211- </ span >
212- </ StatusRow >
213- < Builder
214- ref = { builderRef }
215- fields = { fields }
216- data = { data }
217- onFieldOptionsReload = { ( field ) => {
218- if ( field === 'CITY' ) {
219- handleReloadCityOptions ( ) ;
220- }
221- } }
222- onChange = { setData }
223- />
129+ < Builder ref = { builderRef } fields = { fields } data = { data } onChange = { setData } />
224130 </ DemoCard >
225131 ) ;
226132} ;
0 commit comments