frappe-react-sdk 1.0.5 → 1.0.6

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/README.md CHANGED
@@ -1,2 +1,554 @@
1
1
  # frappe-react-sdk
2
- React hooks for Frappe
2
+ React hooks library for a [Frappe Framework](https://frappeframework.com) backend.
3
+
4
+ <br />
5
+ <p align="center">
6
+ <a href="https://github.com/nikkothari22/frappe-react-sdk"><img src="https://img.shields.io/maintenance/yes/2022?style=flat-square" /></a>
7
+ <a href="https://github.com/nikkothari22/frappe-react-sdk"><img src="https://img.shields.io/github/license/nikkothari22/frappe-react-sdk?style=flat-square" /></a>
8
+ <a href="https://www.npmjs.com/package/frappe-react-sdk"><img src="https://img.shields.io/npm/v/frappe-react-sdk?style=flat-square" /></a>
9
+ <a href="https://www.npmjs.com/package/frappe-react-sdk"><img src="https://img.shields.io/npm/dw/frappe-react-sdk?style=flat-square" /></a>
10
+ </p>
11
+
12
+
13
+ ## Features
14
+
15
+ The library currently supports the following features:
16
+
17
+ - 🔐 Authentication - login with username and password, logout and maintain user state
18
+ - 🗄 Database - Hooks to get document, get list of documents, get count, create, update and delete documents
19
+ - 📄 File upload - Hook to upload a file to the Frappe filesystem. Maintains loading, progress and error states.
20
+ - 🤙🏻 API calls - Hooks to make API calls to your whitelisted backend functions and maintain state
21
+ - 🔍 Search - Hook to search documents in your database (with debouncing ✨)
22
+
23
+
24
+ We plan to add the following features in the future:
25
+
26
+ - 🗝 Authentication with OAuth clients
27
+ - Support for other common functions like `get_last_doc`, `exists` in the database.
28
+ - Realtime event listeners using Socket.io
29
+
30
+
31
+ The library uses [frappe-js-sdk](https://github.com/nikkothari22/frappe-js-sdk) and [SWR](https://swr.vercel.app) under the hood to make API calls to your Frappe backend.
32
+
33
+ <br/>
34
+
35
+ ## SWR
36
+ SWR uses a cache invalidation strategy and also updates the data constantly and automatically (in the background). This allows the UI to always be fast and reactive.
37
+ The hooks in the library use the default configuration for useSWR but you will be able to overwrite the configuration of useSWR. Please refer to the [useSWR API Options](https://swr.vercel.app/docs/options)
38
+
39
+ <br/>
40
+
41
+ ## Looking for a Frappe frontend library for other Javascript frameworks?
42
+
43
+ You can use [frappe-js-sdk](https://github.com/nikkothari22/frappe-js-sdk) to interface your frontend web app with Frappe.
44
+
45
+ <br/>
46
+
47
+ ## Maintainers
48
+
49
+ | Maintainer | GitHub | Social |
50
+ | -------------- | ----------------------------------------------- | --------------------------------------------------- |
51
+ | Nikhil Kothari | [nikkothari22](https://github.com/nikkothari22) | [@nik_kothari22](https://twitter.com/nik_kothari22) |
52
+ | Janhvi Patil | [janhvipatil](https://github.com/janhvipatil) | [@janhvipatil_](https://twitter.com/janhvipatil_) |
53
+
54
+ <br/>
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ npm install frappe-react-sdk
60
+ ```
61
+
62
+ or
63
+
64
+ ```bash
65
+ yarn add frappe-react-sdk
66
+ ```
67
+
68
+
69
+ **Note** - All examples below are in Typescript. If you want to use it with Javascript, just ignore the type generics like `<T>` in the examples below.
70
+
71
+
72
+ <br/>
73
+
74
+ ## Initialising the library
75
+
76
+ To get started, initialise the library by wrapping your App with the `FrappeProvider`.
77
+ You can optionally provide the URL of your Frappe server if the web app is not hosted on the same URL.
78
+
79
+ In `App.tsx` or `App.jsx`:
80
+
81
+
82
+ ```jsx
83
+ import { FrappeProvider } from "frappe-react-sdk";
84
+
85
+ function App() {
86
+ /** The URL is an optional parameter. Only use it if the Frappe server is hosted on a separate URL **/
87
+ return (
88
+ <FrappeProvider url='https://my-frappe-server.frappe.cloud'>
89
+ {/** Your other app components **/}
90
+ </FrappeProvider>
91
+ )
92
+
93
+ ```
94
+
95
+ <br/>
96
+
97
+ ## Authentication
98
+
99
+ The `useFrappeAuth` hook allows you to maintain the state of the current user, as well as login and logout the current user.
100
+
101
+ The hook uses `useSWR` under the hood to make the `get_current_user` API call - you can also pass in parameters to configure the behaviour of the useSWR hook.
102
+
103
+
104
+ ```jsx
105
+ export const MyAuthComponent = () => {
106
+
107
+ const { currentUser, isValidating, login, logout, error, updateCurrentUser } = useFrappeAuth();
108
+
109
+ if (!currentUser && !error) return <div>loading...</div>
110
+
111
+ // render user
112
+ return <div>
113
+ {currentUser}
114
+ <button onClick={() => login("administrator", "admin")}>Login</button>
115
+ <button onClick={logout}>Logout</button>
116
+ <button onClick={updateCurrentUser}>Fetch current user</button>
117
+
118
+ </div>
119
+ }
120
+ ```
121
+
122
+ The hook will throw an error if the API call to `frappe.auth.get_logged_user` fails (network issue etc) or if the user is logged out (403 Forbidden). Handle errors accordingly and route the user to your login page if the error is because the user is not logged in.
123
+
124
+ <br/>
125
+
126
+ ## Database
127
+
128
+ <br/>
129
+
130
+ ### Fetch a document
131
+
132
+ The `useFrappeGetDoc` hook can be used to fetch a document from the database. The hook uses `useSWR` under the hood and it's configuration can be passed to it.
133
+
134
+
135
+ Parameters:
136
+
137
+ | No. | Variable | type | Required | Description |
138
+ | --- | --------- | ------------------ | -------- | ------------------------- |
139
+ | 1. | `doctype` | `string` | ✅ | Name of the doctype |
140
+ | 2. | `docname` | `string` | ✅ | Name of the document |
141
+ | 3. | `options` | `SWRConfiguration` | - | SWR Configuration Options |
142
+
143
+
144
+
145
+ ```tsx
146
+ export const MyDocumentData = () => {
147
+ const { data, error, isValidating, mutate } = useFrappeGetDoc<T>("User", "Administrator", {
148
+ /** SWR Configuration Options - Optional **/
149
+ });
150
+
151
+ if (isValidating) {
152
+ return <>Loading</>
153
+ }
154
+ if (error) {
155
+ return <>{JSON.stringify(error)}</>
156
+ }
157
+ if (data) {
158
+ return <p>
159
+ {JSON.stringify(data)}
160
+ <button onClick={() => mutate()}>Reload</button>
161
+ </p>
162
+ }
163
+ return null
164
+ }
165
+ ```
166
+
167
+
168
+ <hr/>
169
+ <br/>
170
+
171
+ ### Fetch list of documents
172
+
173
+ The `useFrappeGetDocList` hook can be used to fetch a list of documents from the database.
174
+
175
+
176
+ Parameters:
177
+
178
+ | No. | Variable | type | Required | Description |
179
+ | --- | --------- | ------------------ | -------- | --------------------------------------------------------------------------------------------------- |
180
+ | 1. | `doctype` | `string` | ✅ | Name of the doctype |
181
+ | 2. | `args` | `GetDocListArgs` | - | optional parameter (object) to sort, filter, paginate and select the fields that you want to fetch. |
182
+ | 3. | `options` | `SWRConfiguration` | - | SWR Configuration Options |
183
+
184
+
185
+
186
+
187
+ ```tsx
188
+ export const MyDocumentList = () => {
189
+ const { data, error, isValidating, mutate } = useFrappeGetDocList<T>("DocType", {
190
+ /** Fields to be fetched - Optional */
191
+ fields: ["name", "creation"],
192
+ /** Filters to be applied - SQL AND operation */
193
+ filters: [["creation", ">", "2021-10-09"]],
194
+ /** Filters to be applied - SQL OR operation */
195
+ orFilters: [],
196
+ /** Fetch from nth document in filtered and sorted list. Used for pagination */
197
+ limit_start: 5,
198
+ /** Number of documents to be fetched. Default is 20 */
199
+ limit: 10,
200
+ /** Sort results by field and order */
201
+ orderBy: {
202
+ field: "creation",
203
+ order: 'desc'
204
+ },
205
+ /** Fetch documents as a dictionary */
206
+ asDict: false
207
+ },
208
+ {
209
+ /** SWR Configuration Options - Optional **/
210
+ });
211
+
212
+ if (isValidating) {
213
+ return <>Loading</>
214
+ }
215
+ if (error) {
216
+ return <>{JSON.stringify(error)}</>
217
+ }
218
+ if (data) {
219
+ return <p>
220
+ {JSON.stringify(data)}
221
+ <button onClick={() => mutate()}>Reload</button>
222
+ </p>
223
+ }
224
+ return null
225
+ }
226
+ ```
227
+ Type declarations are available for the second argument and will be shown to you in your code editor.
228
+ <br/>
229
+ <br/>
230
+
231
+ #### Some other simpler examples (click to expand):
232
+
233
+ <br/>
234
+
235
+ <details><summary>Fetch 20 items without optional parameters</summary><p>
236
+
237
+ In this case, only the `name` attribute will be fetched.
238
+
239
+ ```tsx
240
+ export const MyDocumentList = () => {
241
+ const { data, error, isValidating } = useFrappeGetDocList<string>("User");
242
+
243
+ if (isValidating) {
244
+ return <>Loading</>
245
+ }
246
+ if (error) {
247
+ return <>{JSON.stringify(error)}</>
248
+ }
249
+ if (data) {
250
+ return <ul>
251
+ {
252
+ data.map(username => <li>{username}</li>)
253
+ }
254
+ </ul>
255
+ }
256
+ return null
257
+ }
258
+ ```
259
+
260
+ </p></details>
261
+
262
+ <details><summary>Fetch usernames and emails with pagination</summary><p>
263
+
264
+
265
+ ```tsx
266
+ type UserItem = {
267
+ name: string,
268
+ email: string
269
+ }
270
+ export const MyDocumentList = () => {
271
+ const [pageIndex, setPageIndex] = useState(0)
272
+ const { data, error, isValidating } = useFrappeGetDocList<UserItem>("User" , {
273
+ fields: ["name", "email"],
274
+ limit_start: pageIndex,
275
+ /** Number of documents to be fetched. Default is 20 */
276
+ limit: 10,
277
+ /** Sort results by field and order */
278
+ orderBy: {
279
+ field: "creation",
280
+ order: 'desc'
281
+ }
282
+ });
283
+
284
+ if (isValidating) {
285
+ return <>Loading</>
286
+ }
287
+ if (error) {
288
+ return <>{JSON.stringify(error)}</>
289
+ }
290
+ if (data) {
291
+ return <div>
292
+ <ul>
293
+ {
294
+ data.map({name, email} => <li>{name} - {email}</li>)
295
+ }
296
+ </ul>
297
+ <button onClick={() => setPageIndex(pageIndex + 10)}>Next page</button>
298
+ </div>
299
+ }
300
+ return null
301
+ }
302
+ ```
303
+
304
+ </p></details>
305
+
306
+ <br/>
307
+ <hr/>
308
+ <br/>
309
+
310
+ ### Fetch number of documents with filters
311
+
312
+ <br/>
313
+
314
+ Parameters:
315
+
316
+ | No. | Variable | type | Required | Description |
317
+ | --- | --------- | ------------------ | -------- | -------------------------------------------------------------- |
318
+ | 1. | `doctype` | `string` | ✅ | Name of the doctype |
319
+ | 2. | `filters` | `Filter[]` | - | optional parameter to filter the result |
320
+ | 3. | `cache` | `boolean` | - | Whether to cache the value on the server - default: `false` |
321
+ | 3. | `debug` | `boolean` | - | Whether to log debug messages on the server - default: `false` |
322
+ | 3. | `config` | `SWRConfiguration` | - | SWR Configuration Options |
323
+
324
+
325
+ ```tsx
326
+ export const DocumentCount = () => {
327
+ const { data, error, isValidating, mutate } = useFrappeGetDocCount("User",
328
+ /** Filters **/
329
+ [["enabled", "=", true]],
330
+ /** Cache the result on server **/
331
+ false,
332
+ /** Print debug logs on server **/
333
+ false,
334
+ {
335
+ /** SWR Configuration Options - Optional **/
336
+ });
337
+
338
+ if (isValidating) {
339
+ return <>Loading</>
340
+ }
341
+ if (error) {
342
+ return <>{JSON.stringify(error)}</>
343
+ }
344
+ if (data) {
345
+ return <p>
346
+ {data} enabled users
347
+ <Button onClick={() => mutate()}>Reload</Button>
348
+ </p>
349
+ }
350
+ return null
351
+ }
352
+ ```
353
+
354
+ #### Some other simpler examples (click to expand):
355
+ <br/>
356
+ <details><summary>Fetch total number of documents</summary><p>
357
+
358
+ ```tsx
359
+ export const DocumentCount = () => {
360
+ const { data, error, isValidating } = useFrappeGetDocCount("User");
361
+
362
+ if (isValidating) {
363
+ return <>Loading</>
364
+ }
365
+ if (error) {
366
+ return <>{JSON.stringify(error)}</>
367
+ }
368
+ if (data) {
369
+ return <p>
370
+ {data} total users
371
+ </p>
372
+ }
373
+ return null
374
+ }
375
+ ```
376
+
377
+ </p></details>
378
+
379
+ <details><summary>Fetch number of documents with filters</summary><p>
380
+
381
+ ```tsx
382
+ export const DocumentCount = () => {
383
+ const { data, error, isValidating } = useFrappeGetDocCount("User", [["enabled", "=", true]]);
384
+
385
+ if (isValidating) {
386
+ return <>Loading</>
387
+ }
388
+ if (error) {
389
+ return <>{JSON.stringify(error)}</>
390
+ }
391
+ if (data) {
392
+ return <p>
393
+ {data} enabled users
394
+ </p>
395
+ }
396
+ return null
397
+ }
398
+ ```
399
+
400
+ </p></details>
401
+
402
+ <br/>
403
+ <hr/>
404
+ <br/>
405
+
406
+ ### Create a document
407
+ To create a new document, pass the name of the DocType and the fields to `createDoc`.
408
+ ```js
409
+ db.createDoc("My Custom DocType", {
410
+ "name": "Test",
411
+ "test_field": "This is a test field"
412
+ })
413
+ .then(doc => console.log(doc))
414
+ .catch(error => console.error(error))
415
+ ```
416
+
417
+ <br/>
418
+ <hr/>
419
+ <br/>
420
+
421
+ ### Update a document
422
+ To update an existing document, pass the name of the DocType, name of the document and the fields to be updated to `updateDoc`.
423
+ ```js
424
+ db.updateDoc("My Custom DocType", "Test", {
425
+ "test_field": "This is an updated test field."
426
+ })
427
+ .then(doc => console.log(doc))
428
+ .catch(error => console.error(error))
429
+ ```
430
+
431
+ <br/>
432
+ <hr/>
433
+ <br/>
434
+
435
+ ### Delete a document
436
+ To create a new document, pass the name of the DocType and the name of the document to be deleted to `deleteDoc`.
437
+ ```js
438
+ db.deleteDoc("My Custom DocType", "Test")
439
+ .then(response => console.log(response.message)) // Message will be "ok"
440
+ .catch(error => console.error(error))
441
+ ```
442
+
443
+ <br/>
444
+
445
+ ## API Calls
446
+
447
+ <br/>
448
+
449
+ ### GET request
450
+
451
+ Make a GET request to your endpoint with parameters.
452
+
453
+ ```js
454
+ const searchParams = {
455
+ doctype: "Currency",
456
+ txt: "IN"
457
+ }
458
+ call.get('frappe.desk.search_link', searchParams)
459
+ .then(result => console.log(result))
460
+ .catch(error => console.error(error))
461
+ ```
462
+
463
+ <br/>
464
+ <hr/>
465
+ <br/>
466
+
467
+ ### POST request
468
+
469
+ Make a POST request to your endpoint with parameters.
470
+
471
+ ```js
472
+ const updatedFields = {
473
+ "doctype": "User",
474
+ "name": "Administrator",
475
+ "fieldname": "interest",
476
+ "value": "Frappe Framework, ERPNext"
477
+ }
478
+ call.post('frappe.client.set_value', updatedFields)
479
+ .then(result => console.log(result))
480
+ .catch(error => console.error(error))
481
+ ```
482
+ <br/>
483
+ <hr/>
484
+ <br/>
485
+
486
+ ### PUT request
487
+
488
+ Make a PUT request to your endpoint with parameters.
489
+
490
+ ```js
491
+ const updatedFields = {
492
+ "doctype": "User",
493
+ "name": "Administrator",
494
+ "fieldname": "interest",
495
+ "value": "Frappe Framework, ERPNext"
496
+ }
497
+ call.put('frappe.client.set_value', updatedFields)
498
+ .then(result => console.log(result))
499
+ .catch(error => console.error(error))
500
+ ```
501
+
502
+ <br/>
503
+ <hr/>
504
+ <br/>
505
+
506
+ ### DELETE request
507
+
508
+ Make a DELETE request to your endpoint with parameters.
509
+
510
+ ```js
511
+ const documentToBeDeleted = {
512
+ "doctype": "Tag",
513
+ "name": "Random Tag",
514
+ }
515
+ call.put('frappe.client.delete', documentToBeDeleted)
516
+ .then(result => console.log(result))
517
+ .catch(error => console.error(error))
518
+ ```
519
+
520
+ <br/>
521
+
522
+ ## File Uploads
523
+
524
+ ```js
525
+ const myFile; //Your File object
526
+
527
+ const fileArgs = {
528
+ /** If the file access is private then set to TRUE (optional) */
529
+ "isPrivate": true,
530
+ /** Folder the file exists in (optional) */
531
+ "folder": "Home",
532
+ /** File URL (optional) */
533
+ "file_url": "",
534
+ /** Doctype associated with the file (optional) */
535
+ "doctype": "User",
536
+ /** Docname associated with the file (mandatory if doctype is present) */
537
+ "docname": "Administrator"
538
+ }
539
+
540
+ file.uploadFile(
541
+ myFile,
542
+ fileArgs,
543
+ /** Progress Indicator callback function **/
544
+ (completedBytes, totalBytes) => console.log(Math.round((c / t) * 100), " completed")
545
+ )
546
+ .then(() => console.log("File Upload complete"))
547
+ .catch(e => console.error(e))
548
+ ```
549
+
550
+ <br/>
551
+
552
+ ## License
553
+
554
+ See [LICENSE](./LICENSE).