@cgarciagarcia/react-query-builder 1.20.2 → 1.20.4

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.
Files changed (2) hide show
  1. package/README.md +210 -155
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
- **TypeScript React hook query Builder** provides a way to build a query string compatible with
3
- [spatie/laravel-query-builder](https://github.com/spatie/laravel-query-builder).
2
+ # @cgarciagarcia/react-query-builder
3
+
4
+ A TypeScript React hook that builds query strings compatible with [spatie/laravel-query-builder](https://github.com/spatie/laravel-query-builder).
4
5
 
5
6
  [![Coverage Status](https://coveralls.io/repos/github/cgarciagarcia/react-query-builder/badge.svg?branch=main&service=github&kill_cache=1)](https://coveralls.io/github/cgarciagarcia/react-query-builder?branch=main)
6
7
  [![Test CI](https://github.com/cgarciagarcia/react-query-builder/actions/workflows/test.yml/badge.svg)](https://github.com/cgarciagarcia/react-query-builder/actions/workflows/test.yml)
@@ -8,253 +9,307 @@
8
9
  [![Codacy Badge](https://app.codacy.com/project/badge/Grade/1f3f48abc84f4e3cba76e39e804786d6)](https://app.codacy.com/gh/cgarciagarcia/react-query-builder/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
9
10
  [![Downloads](https://img.shields.io/npm/d18m/@cgarciagarcia/react-query-builder?style=flat-square)](https://www.npmjs.com/package/@cgarciagarcia/react-query-builder)
10
11
 
11
- ## Installation
12
+ ---
13
+
14
+ ## Table of Contents
12
15
 
13
- You can install package using yarn (or npm):
16
+ - [Installation](#installation)
17
+ - [Quick Start](#quick-start)
18
+ - [Configuration](#configuration)
19
+ - [API Reference](#api-reference)
20
+ - [Filters](#filters)
21
+ - [Fields](#fields)
22
+ - [Sorts](#sorts)
23
+ - [Includes](#includes)
24
+ - [Params](#params)
25
+ - [Pagination](#pagination)
26
+ - [Utilities](#utilities)
27
+ - [Advanced: Conflicting Filters](#advanced-conflicting-filters)
28
+ - [Support](#support)
29
+ - [License](#license)
30
+
31
+ ---
32
+
33
+ ## Installation
14
34
 
15
35
  ```bash
36
+ # npm
37
+ npm install @cgarciagarcia/react-query-builder
38
+
39
+ # yarn
16
40
  yarn add @cgarciagarcia/react-query-builder
17
- ```
18
41
 
19
- ## Usage
42
+ # pnpm
43
+ pnpm add @cgarciagarcia/react-query-builder
44
+ ```
20
45
 
21
- This package is designed to provide an easy way to interact with the backend integration
22
- of `spatie/laravel-query-builder`
23
- using your favorite frontend library, `React.js`. It includes a custom hook that you can use for seamless interaction.
46
+ **Peer dependencies:** React 17, 18, or 19.
24
47
 
25
- <h3 style="color:#cb3837">Some Examples</h3>
48
+ ---
26
49
 
27
- Here is a simple example:
50
+ ## Quick Start
28
51
 
29
- ```js
52
+ ```ts
30
53
  import { useQueryBuilder } from "@cgarciagarcia/react-query-builder";
31
54
 
32
- const baseConfig = {
33
- aliases: {
34
- "frontend_name": "backend_name",
35
- },
36
-
37
- }
38
-
39
- const builder = useQueryBuilder(baseConfig)
55
+ const builder = useQueryBuilder()
40
56
 
41
57
  builder
42
- .fields('user.name', 'user.last_name', 'other')
43
- .filter('salary', ">", 1000)
44
- .filter("age", 18)
45
- .sort("created_at") // by default sorting asc
46
- .sort("age", 'desc') // sorting desc
47
- .setParam("external_param", 123) // you can define it in the baseConfig
48
- .include("posts", "comments")
58
+ .fields('user.name', 'user.last_name')
59
+ .filter('age', 18)
60
+ .filter('salary', '>', 1000)
61
+ .sort('created_at')
62
+ .sort('age', 'desc')
63
+ .include('posts', 'comments')
64
+ .setParam('external_param', 123)
49
65
  .page(1)
50
66
  .limit(10)
51
67
 
52
- function apiCall() {
53
- console.log(builder.build());
54
- // ?fields[user]=name,fields=other,last_name&filter[age]=18&sort=created_at,-age&includes=posts,comments
55
- return fetch("https://myapi.com/api/users" + builder.build()).then(response => response.json())
56
- }
68
+ // Use in fetch
69
+ fetch("https://myapi.com/api/users" + builder.build())
70
+
71
+ // builder.build() returns:
72
+ // ?fields[user]=name,last_name&filter[age]=18&filter[salary][gt]=1000&sort=created_at,-age&includes=posts,comments&external_param=123&page=1&limit=10
57
73
  ```
58
74
 
59
- <h3 style="color:#cb3837;">Configurations</h3>
75
+ ---
60
76
 
61
- You can set the initial state for your query builder, also it's possible to modify
62
- the delimiter for each action (fields, filters, includes, sorts), the global delimiter
63
- will be overwritten with the specific delimiter
77
+ ## Configuration
64
78
 
65
- ```javascript
79
+ Pass an optional config object to `useQueryBuilder` to set initial state and customize behavior.
66
80
 
67
- // Default configuration
68
- const baseConfig = {
69
- aliases: {},
81
+ ```ts
82
+ const builder = useQueryBuilder({
83
+ // Map frontend field names to backend names
84
+ aliases: {
85
+ "frontend_name": "backend_name",
86
+ },
87
+
88
+ // Pre-set initial state
70
89
  filters: [],
71
90
  includes: [],
72
91
  sorts: [],
73
92
  fields: [],
93
+ params: {},
94
+
95
+ // Define mutually exclusive filters (see Advanced section)
74
96
  pruneConflictingFilters: {},
97
+
98
+ // Custom delimiters (default: ',')
75
99
  delimiters: {
76
- global: ',',
100
+ global: ',', // applies to all unless overridden
77
101
  fields: null,
78
102
  filters: null,
79
103
  sorts: null,
80
104
  includes: null,
81
105
  params: null,
82
106
  },
107
+
108
+ // Prepend '?' to the output of build()
83
109
  useQuestionMark: false,
84
- params: {}
85
- }
110
+
111
+ // Initial pagination state
112
+ pagination: {
113
+ page: 1,
114
+ limit: 10,
115
+ },
116
+ })
86
117
  ```
87
118
 
88
- <h3 style="color:#cb3837;">Remove Methods</h3>
89
- You can use the remove method in sort, includes, filter like this:
119
+ ---
90
120
 
91
- ```js
92
- const builder = useQueryBuilder(baseConfig)
121
+ ## API Reference
93
122
 
94
- builder
95
- .removeField('field_1', 'field_2')
96
- .removeFilter('name', 'last_name')
97
- .removeSort('name', 'id')
98
- .removeInclude('address', 'documents')
99
- .removeParam('param1', 'param2')
100
- ```
123
+ All methods return the builder instance, so they are **chainable**.
101
124
 
102
- <h3 style="color:#cb3837;">Clear Methods</h3>
125
+ ### Filters
103
126
 
104
- You can use the clear methods for delete the entire data group
127
+ ```ts
128
+ // Add a filter (appends values by default)
129
+ builder.filter('status', 'active')
105
130
 
106
- ```js
107
- const builder = useQueryBuilder(baseConfig)
131
+ // Add a filter with an operator
132
+ builder.filter('salary', '>', 1000)
133
+ builder.filter('age', '>=', 18)
108
134
 
109
- builder
110
- .clearFields()
111
- .clearFilters()
112
- .clearIncludes()
113
- .clearSorts()
114
- .clearParams()
135
+ // Override existing filter values instead of appending
136
+ builder.filter('status', 'inactive', true)
137
+
138
+ // Remove specific filters
139
+ builder.removeFilter('status', 'age')
140
+
141
+ // Remove all filters
142
+ builder.clearFilters()
143
+
144
+ // Check if filters exist
145
+ builder.hasFilter('status') // → boolean
146
+ builder.hasFilter('status', 'age') // → true only if ALL exist
115
147
  ```
116
148
 
117
- <h3 style="color:#cb3837;">Filters that don't work together</h3>
149
+ Available operators: `=`, `<`, `>`, `<=`, `>=`, `<>`
118
150
 
119
- Maybe your business logic has filters that won't work together, for example you could
120
- have filters like `date` filter and `between_dates` filter in your backend, but you can not filter
121
- by both at the same time, so you have to be sure to clear incompatibles filters
122
- before to adding a new one. With this purpose the property `pruneConflictingFilters`
123
- was created, you can define these incompatibilities in the base configuration and delegate
124
- the humdrum action to the library.
151
+ You can also import `FilterOperator` for type-safe operators:
125
152
 
126
- Example:
153
+ ```ts
154
+ import { FilterOperator } from "@cgarciagarcia/react-query-builder"
127
155
 
128
- ```js
129
- const builder = useQueryBuilder({
130
- pruneConflictingFilters: {
131
- date: ['between_dates']
132
- },
133
- })
156
+ builder.filter('salary', FilterOperator.GreaterThan, 1000)
157
+ ```
134
158
 
135
- builder.filter('date', today)
136
- .build() // the result is ?filter[date]=2024-08-13
137
- .filter('between_dates', [lastWeek, today])
138
- .build() // the result in this line is ?filter[between_dates]=2024-08-06,2024-08-13
159
+ ---
160
+
161
+ ### Fields
162
+
163
+ ```ts
164
+ builder.fields('name', 'email', 'user.avatar')
165
+ builder.removeField('email')
166
+ builder.clearFields()
167
+ builder.hasField('name') // → boolean
139
168
  ```
140
169
 
141
- #### How does it work?
170
+ ---
171
+
172
+ ### Sorts
173
+
174
+ ```ts
175
+ builder.sort('created_at') // default: asc
176
+ builder.sort('age', 'desc')
177
+ builder.removeSort('created_at', 'age')
178
+ builder.clearSorts()
179
+ builder.hasSort('created_at') // → boolean
180
+ ```
142
181
 
143
- When you define that `date` filter is not compatible with `between_dates`, internally
144
- the library define the bidirectional incompatibility for you. Too much magic? Don't
145
- worry, you still could define manually the inverse incompatibility to have explicit
146
- declaration from your side.
182
+ ---
147
183
 
148
- <h3 style="color:#cb3837;">Utilities</h3>
184
+ ### Includes
149
185
 
150
- You could use the builder with your [Tank Stack React query](https://tanstack.com/query/latest)
151
- implementation for example:
186
+ ```ts
187
+ builder.include('posts', 'comments')
188
+ builder.removeInclude('posts')
189
+ builder.clearIncludes()
190
+ builder.hasInclude('posts') // → boolean
191
+ ```
152
192
 
153
- #### toArray()
193
+ ---
154
194
 
155
- ```typescript
195
+ ### Params
156
196
 
157
- import { getApiUsers } from "@/Api/users"
158
- import { useQueryBuilder } from "@cgarciagarcia/react-query-builder";
197
+ ```ts
198
+ builder.setParam('custom_key', 'value')
199
+ builder.setParam('ids', [1, 2, 3])
200
+ builder.removeParam('custom_key')
201
+ builder.clearParams()
202
+ builder.hasParam('custom_key') // → boolean
203
+ ```
159
204
 
160
- const MyComponent = () => {
205
+ ---
161
206
 
162
- const builder = useQueryBuilder()
207
+ ### Pagination
163
208
 
164
- const useUserQuery = useQuery({
165
- fnQuery: () => getApiUsers(builder),
166
- queryKey: ['userQuery', ...builder.toArray()]
167
- })
209
+ ```ts
210
+ const builder = useQueryBuilder({
211
+ pagination: { page: 1, limit: 10 }
212
+ })
168
213
 
169
- /* Rest of code */
170
- }
214
+ builder.page(3) // go to page 3
215
+ builder.nextPage() // page + 1
216
+ builder.previousPage() // page - 1 (stops at 1)
217
+ builder.limit(25) // change page size
171
218
 
219
+ builder.getCurrentPage() // → number | undefined
220
+ builder.getLimit() // → number | undefined
172
221
  ```
173
222
 
174
- #### when()
223
+ > **Note:** Changing filters, removing filters, or changing the limit automatically resets to page 1.
175
224
 
176
- ```typescript
225
+ ---
177
226
 
178
- import { getApiUsers } from "@/Api/users"
179
- import { useQueryBuilder } from "@cgarciagarcia/react-query-builder";
227
+ ### Utilities
228
+
229
+ #### `build()`
180
230
 
181
- const MyComponent = () => {
182
-
183
- const builder = useQueryBuilder()
184
-
185
- const useUserQuery = useQuery({
186
- fnQuery: () => getApiUsers(builder),
187
- queryKey: ['userQuery', ...builder.toArray()]
188
- })
189
-
190
- const onClickButton = (id: number|null) => {
191
-
192
- builder.when(id !== null, (state) => {
193
- console.log(state) // reveal internal state
194
- })
195
- }
196
-
197
- /* Rest of code */
198
- }
231
+ Returns the final query string.
199
232
 
233
+ ```ts
234
+ builder.build() // → "?filter[age]=18&sort=created_at"
200
235
  ```
201
236
 
202
- ### hasMethods()
237
+ #### `toArray()`
203
238
 
239
+ Returns the query state as a flat string array. Useful as a React Query `queryKey`.
204
240
 
205
- ```js
206
- const builder = useQueryBuilder()
241
+ ```ts
242
+ import { useQuery } from "@tanstack/react-query"
207
243
 
208
- builder.hasFilter('filter', 'filter2', ...)
209
- .hasInclude('include', 'include2', ...)
210
- .hasParam('param1', 'param2', ...)
211
- .hasField('field', ...)
212
- .hasSort('sort', ...)
244
+ const builder = useQueryBuilder()
213
245
 
246
+ const { data } = useQuery({
247
+ queryFn: () => getUsers(builder.build()),
248
+ queryKey: ['users', ...builder.toArray()],
249
+ })
214
250
  ```
215
251
 
216
- <h3 style="color:#cb3837">Pagination</h3>
252
+ #### `tap(callback)`
217
253
 
218
- this package also support pagination to provide a verbose and well explained interaction
254
+ Inspect the internal state without interrupting the chain.
219
255
 
220
- ```typescript
256
+ ```ts
257
+ builder
258
+ .filter('age', 18)
259
+ .tap((state) => console.log(state))
260
+ .sort('name')
261
+ ```
221
262
 
222
- const builder = useQueryBuilder({
223
- pagination: {
224
- page: 1,
225
- limit: 10
226
- }
263
+ #### `when(condition, callback)`
264
+
265
+ Conditionally execute a callback based on a boolean. The builder is returned regardless.
266
+
267
+ ```ts
268
+ builder.when(isAdmin, (state) => {
269
+ console.log('Admin state:', state)
227
270
  })
271
+ ```
228
272
 
229
- const onPressNextPage = () => builder.nexPage()
273
+ ---
230
274
 
231
- const onPressPreviousPage = () => builder.previousPage()
275
+ ## Advanced: Conflicting Filters
232
276
 
233
- // Go to specific page
234
- const goToFirstPage = () => builder.page(1)
277
+ Some filters are mutually exclusive in your backend (e.g. `date` vs `between_dates`). Use `pruneConflictingFilters` to let the library handle this automatically.
235
278
 
236
- const goToLastPage = () => builder.page(MY_LAST_PAGE)
279
+ ```ts
280
+ const builder = useQueryBuilder({
281
+ pruneConflictingFilters: {
282
+ date: ['between_dates'],
283
+ // 'between_dates': ['date'] is added automatically (bidirectional)
284
+ },
285
+ })
237
286
 
238
- // Change the limit of pagination
239
- const onChangeLimit = (newLimit: number) => builder.limit(newLimit)
287
+ builder.filter('date', '2024-08-13')
288
+ // ?filter[date]=2024-08-13
240
289
 
290
+ builder.filter('between_dates', ['2024-08-06', '2024-08-13'])
291
+ // → ?filter[between_dates]=2024-08-06,2024-08-13
292
+ // (date filter was automatically removed)
241
293
  ```
242
294
 
295
+ The conflict is **bidirectional by default** — you only need to declare it once. You can also declare both directions explicitly if you prefer.
296
+
297
+ ---
243
298
 
244
- ## Do you have question how to implement it?
299
+ ## Support
245
300
 
246
- Feel free to generate a discussion in the github repository I will help you
301
+ Have a question or need help? [Open a discussion](https://github.com/cgarciagarcia/react-query-builder/discussions) on GitHub.
247
302
 
303
+ ---
248
304
 
249
- ## Consider supporting me
305
+ ## Consider Supporting
250
306
 
251
- I invite you to support its creator. Your contribution will
252
- not only help maintain and improve this package but also promote the continuous
253
- development of quality tools and resources. You can also email me and let me know.
307
+ If this package helps you, consider supporting its creator:
254
308
 
255
- Paypal: [@carlosgarciadev](https://paypal.me/carlosgarciadev?country.x=AR&locale.x=es_XC)
309
+ **PayPal:** [@carlosgarciadev](https://paypal.me/carlosgarciadev?country.x=AR&locale.x=es_XC)
256
310
 
311
+ ---
257
312
 
258
313
  ## License
259
314
 
260
- The MIT License (MIT). Please see [License File](LICENSE) for more information.
315
+ The MIT License (MIT). See [LICENSE](LICENSE) for more information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cgarciagarcia/react-query-builder",
3
- "version": "1.20.2",
3
+ "version": "1.20.4",
4
4
  "description": "",
5
5
  "main": "dist/bundle.js",
6
6
  "types": "dist/index.d.ts",