0
# Utilities & Helpers
1
2
Resource management, loading states, metadata handling, notifications, and various utility functions for enhanced development experience.
3
4
## Capabilities
5
6
### Resource & Parameter Management
7
8
#### useResourceParams Hook
9
10
Gets resource and parameter information from current context with automatic inference from routes.
11
12
```typescript { .api }
13
/**
14
* Gets resource and parameter information from current context
15
* @param params - Resource parameter configuration
16
* @returns Current resource context and parameter values
17
*/
18
function useResourceParams(params?: UseResourceParamsConfig): UseResourceParamsReturnType;
19
20
interface UseResourceParamsConfig {
21
/** Override resource name */
22
resource?: string;
23
/** Override record ID */
24
id?: BaseKey;
25
/** Override action */
26
action?: string;
27
}
28
29
interface UseResourceParamsReturnType {
30
/** Current resource configuration */
31
resource?: IResourceItem;
32
/** Resource identifier (name) */
33
identifier?: string;
34
/** Current record ID */
35
id?: BaseKey;
36
/** Current action */
37
action?: Action;
38
/** Function to set ID */
39
setId: React.Dispatch<React.SetStateAction<BaseKey | undefined>>;
40
}
41
42
interface IResourceItem {
43
/** Resource name */
44
name: string;
45
/** Display label */
46
label?: string;
47
/** Resource icon */
48
icon?: React.ReactNode;
49
/** Whether resource can be deleted */
50
canDelete?: boolean;
51
/** Parent resource name */
52
parentName?: string;
53
/** Resource metadata */
54
meta?: Record<string, any>;
55
}
56
57
type Action = "list" | "create" | "edit" | "show" | "clone";
58
```
59
60
**Usage Example:**
61
62
```typescript
63
import { useResourceParams } from "@refinedev/core";
64
65
function ResourceHeader() {
66
const { resource, action, id } = useResourceParams();
67
68
const getTitle = () => {
69
if (!resource) return "Unknown Resource";
70
71
switch (action) {
72
case "list":
73
return `${resource.label || resource.name} List`;
74
case "create":
75
return `Create ${resource.label || resource.name}`;
76
case "edit":
77
return `Edit ${resource.label || resource.name} #${id}`;
78
case "show":
79
return `${resource.label || resource.name} #${id}`;
80
case "clone":
81
return `Clone ${resource.label || resource.name} #${id}`;
82
default:
83
return resource.label || resource.name;
84
}
85
};
86
87
return (
88
<header>
89
{resource?.icon}
90
<h1>{getTitle()}</h1>
91
</header>
92
);
93
}
94
95
// Dynamic component behavior based on resource
96
function ResourceActions() {
97
const { resource, action, id, setId } = useResourceParams();
98
99
const canEdit = resource?.meta?.permissions?.includes("edit");
100
const canDelete = resource?.canDelete !== false;
101
102
return (
103
<div className="resource-actions">
104
{action === "show" && canEdit && (
105
<button onClick={() => setId(id)}>
106
Edit {resource?.label}
107
</button>
108
)}
109
{action === "edit" && canDelete && (
110
<button className="danger">
111
Delete {resource?.label}
112
</button>
113
)}
114
</div>
115
);
116
}
117
```
118
119
### Loading & Performance
120
121
#### useLoadingOvertime Hook
122
123
Tracks loading time for performance monitoring and user experience optimization.
124
125
```typescript { .api }
126
/**
127
* Tracks loading time for performance monitoring
128
* @param params - Loading overtime configuration
129
* @returns Loading time information and callbacks
130
*/
131
function useLoadingOvertime(params: UseLoadingOvertimeConfig): UseLoadingOvertimeReturnType;
132
133
interface UseLoadingOvertimeConfig {
134
/** Whether currently loading */
135
isLoading: boolean;
136
/** Interval for time updates in milliseconds */
137
interval?: number;
138
/** Callback fired at each interval */
139
onInterval?: (elapsedTime: number) => void;
140
}
141
142
interface UseLoadingOvertimeReturnType {
143
/** Elapsed time since loading started in milliseconds */
144
elapsedTime?: number;
145
}
146
```
147
148
**Usage Example:**
149
150
```typescript
151
import { useLoadingOvertime, useList } from "@refinedev/core";
152
153
function DataTableWithLoadingIndicator() {
154
const { query } = useList({ resource: "posts" });
155
156
const { elapsedTime } = useLoadingOvertime({
157
isLoading: query.isLoading,
158
interval: 1000,
159
onInterval: (time) => {
160
if (time > 5000) {
161
console.warn("Query taking longer than expected:", time);
162
}
163
}
164
});
165
166
if (query.isLoading) {
167
return (
168
<div className="loading-state">
169
<div>Loading posts...</div>
170
{elapsedTime && elapsedTime > 2000 && (
171
<div className="loading-overtime">
172
Still loading... ({Math.round(elapsedTime / 1000)}s)
173
</div>
174
)}
175
</div>
176
);
177
}
178
179
return (
180
<div>
181
{/* Table content */}
182
</div>
183
);
184
}
185
```
186
187
### Metadata Management
188
189
#### useMeta Hook
190
191
Manages metadata for resources and operations with context-aware defaults.
192
193
```typescript { .api }
194
/**
195
* Manages metadata for resources and operations
196
* @param params - Meta configuration
197
* @returns Current metadata and update functions
198
*/
199
function useMeta(params?: UseMetaConfig): UseMetaReturnType;
200
201
interface UseMetaConfig {
202
/** Initial metadata */
203
meta?: MetaQuery;
204
/** Resource name for context */
205
resource?: string;
206
}
207
208
interface UseMetaReturnType {
209
/** Current metadata */
210
meta: MetaQuery;
211
/** Function to update metadata */
212
setMeta: (meta: MetaQuery) => void;
213
/** Function to merge metadata */
214
mergeMeta: (meta: MetaQuery) => void;
215
}
216
217
interface MetaQuery {
218
[key: string]: any;
219
}
220
```
221
222
#### useMetaContext Hook
223
224
Access to the meta context provider for global metadata management.
225
226
```typescript { .api }
227
/**
228
* Access to meta context provider
229
* @returns Meta context value and functions
230
*/
231
function useMetaContext(): MetaContextValue;
232
233
interface MetaContextValue {
234
/** Global metadata */
235
meta: MetaQuery;
236
/** Function to set global metadata */
237
setMeta: (meta: MetaQuery) => void;
238
}
239
```
240
241
### Notification System
242
243
#### useNotification Hook
244
245
Shows notifications with different types, positions, and auto-dismiss functionality.
246
247
```typescript { .api }
248
/**
249
* Shows notifications with customizable options
250
* @returns Notification functions for different types
251
*/
252
function useNotification(): UseNotificationReturnType;
253
254
interface UseNotificationReturnType {
255
/** Show success notification */
256
open: (params: OpenNotificationParams) => void;
257
/** Close specific notification */
258
close: (key: string) => void;
259
}
260
261
interface OpenNotificationParams {
262
/** Notification type */
263
type: "success" | "error" | "progress" | "warning" | "info";
264
/** Notification message */
265
message: string;
266
/** Additional description */
267
description?: string;
268
/** Unique key for the notification */
269
key?: string;
270
/** Whether notification should not auto-dismiss */
271
undoableTimeout?: number;
272
/** Whether to show undo button */
273
cancelMutation?: () => void;
274
/** Custom duration before auto-dismiss */
275
duration?: number;
276
}
277
```
278
279
**Usage Example:**
280
281
```typescript
282
import { useNotification, useCreate } from "@refinedev/core";
283
284
function CreatePostForm() {
285
const notification = useNotification();
286
const { mutate: createPost } = useCreate();
287
288
const handleCreate = (values: any) => {
289
createPost({
290
resource: "posts",
291
values
292
}, {
293
onSuccess: (data) => {
294
notification.open({
295
type: "success",
296
message: "Post Created",
297
description: `Post "${data.data.title}" has been created successfully.`,
298
duration: 4000
299
});
300
},
301
onError: (error) => {
302
notification.open({
303
type: "error",
304
message: "Creation Failed",
305
description: error.message || "Failed to create post."
306
});
307
}
308
});
309
};
310
311
return (
312
<form onSubmit={handleCreate}>
313
{/* Form fields */}
314
</form>
315
);
316
}
317
318
// Undoable notifications
319
function DeleteWithUndo({ postId }: { postId: string }) {
320
const notification = useNotification();
321
const { mutate: deletePost } = useDelete();
322
323
const handleDelete = () => {
324
let cancelMutation: (() => void) | undefined;
325
326
notification.open({
327
type: "success",
328
message: "Post Deleted",
329
description: "Post has been deleted.",
330
undoableTimeout: 5000,
331
cancelMutation: () => {
332
if (cancelMutation) {
333
cancelMutation();
334
notification.open({
335
type: "success",
336
message: "Deletion Cancelled",
337
description: "Post deletion has been cancelled."
338
});
339
}
340
}
341
});
342
343
// Set timeout for actual deletion
344
const timeoutId = setTimeout(() => {
345
deletePost({
346
resource: "posts",
347
id: postId
348
});
349
}, 5000);
350
351
cancelMutation = () => {
352
clearTimeout(timeoutId);
353
};
354
};
355
356
return <button onClick={handleDelete}>Delete Post</button>;
357
}
358
```
359
360
### Modal Management
361
362
#### useModal Hook
363
364
Manages modal state with multiple modal support and proper cleanup.
365
366
```typescript { .api }
367
/**
368
* Manages modal state with multiple modal support
369
* @returns Modal state and control functions
370
*/
371
function useModal(): UseModalReturnType;
372
373
interface UseModalReturnType {
374
/** Whether modal is visible */
375
visible: boolean;
376
/** Show the modal */
377
show: () => void;
378
/** Hide the modal */
379
close: () => void;
380
/** Toggle modal visibility */
381
toggle: () => void;
382
}
383
```
384
385
**Usage Example:**
386
387
```typescript
388
import { useModal, useOne } from "@refinedev/core";
389
390
function PostDetailsModal({ postId }: { postId?: string }) {
391
const modal = useModal();
392
const { data: post } = useOne({
393
resource: "posts",
394
id: postId!,
395
queryOptions: {
396
enabled: !!postId && modal.visible
397
}
398
});
399
400
return (
401
<>
402
<button onClick={modal.show}>View Details</button>
403
404
{modal.visible && (
405
<div className="modal-overlay" onClick={modal.close}>
406
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
407
<div className="modal-header">
408
<h2>{post?.data.title}</h2>
409
<button onClick={modal.close}>×</button>
410
</div>
411
<div className="modal-body">
412
<p>{post?.data.content}</p>
413
</div>
414
</div>
415
</div>
416
)}
417
</>
418
);
419
}
420
421
// Multiple modals
422
function MultiModalExample() {
423
const editModal = useModal();
424
const deleteModal = useModal();
425
const viewModal = useModal();
426
427
return (
428
<div>
429
<button onClick={editModal.show}>Edit</button>
430
<button onClick={deleteModal.show}>Delete</button>
431
<button onClick={viewModal.show}>View</button>
432
433
{/* Multiple modal components */}
434
</div>
435
);
436
}
437
```
438
439
### Cache Management
440
441
#### useInvalidate Hook
442
443
Invalidates React Query cache with fine-grained control over cache keys.
444
445
```typescript { .api }
446
/**
447
* Invalidates React Query cache with fine-grained control
448
* @returns Cache invalidation functions
449
*/
450
function useInvalidate(): UseInvalidateReturnType;
451
452
interface UseInvalidateReturnType {
453
/** Invalidate specific cache entries */
454
invalidate: (params: InvalidateParams) => Promise<void>;
455
/** Invalidate all cache entries for a resource */
456
invalidateAll: (resource: string) => Promise<void>;
457
}
458
459
interface InvalidateParams {
460
/** Resource name */
461
resource: string;
462
/** Specific record ID */
463
id?: BaseKey;
464
/** Data provider name */
465
dataProviderName?: string;
466
/** Whether to invalidate exact match only */
467
exact?: boolean;
468
}
469
```
470
471
**Usage Example:**
472
473
```typescript
474
import { useInvalidate, useUpdate } from "@refinedev/core";
475
476
function RefreshablePostList() {
477
const invalidate = useInvalidate();
478
const { mutate: updatePost } = useUpdate();
479
480
const handleRefresh = async () => {
481
// Invalidate all posts data
482
await invalidate.invalidateAll("posts");
483
};
484
485
const handleUpdatePost = (id: string, values: any) => {
486
updatePost({
487
resource: "posts",
488
id,
489
values
490
}, {
491
onSuccess: async () => {
492
// Invalidate specific post and list
493
await invalidate.invalidate({ resource: "posts", id });
494
await invalidate.invalidate({ resource: "posts" });
495
}
496
});
497
};
498
499
return (
500
<div>
501
<button onClick={handleRefresh}>Refresh All</button>
502
{/* Post list */}
503
</div>
504
);
505
}
506
```
507
508
### Import/Export Utilities
509
510
#### File Processing Utilities
511
512
Utilities for handling file uploads, downloads, and format conversion.
513
514
```typescript { .api }
515
/**
516
* Convert file to base64 string
517
* @param file - File to convert
518
* @returns Promise resolving to base64 string
519
*/
520
function file2Base64(file: File): Promise<string>;
521
522
/**
523
* CSV import data mapper
524
* @param data - Raw CSV data
525
* @param mapping - Field mapping configuration
526
* @returns Mapped data array
527
*/
528
function importCSVMapper(
529
data: any[],
530
mapping: Record<string, string>
531
): any[];
532
533
interface FileProcessingOptions {
534
/** Maximum file size in bytes */
535
maxSize?: number;
536
/** Allowed file types */
537
allowedTypes?: string[];
538
/** Custom validation function */
539
validate?: (file: File) => boolean | string;
540
}
541
```
542
543
**Usage Example:**
544
545
```typescript
546
import { file2Base64, importCSVMapper } from "@refinedev/core";
547
548
function FileUploadComponent() {
549
const handleFileUpload = async (file: File) => {
550
try {
551
// Convert to base64 for API upload
552
const base64 = await file2Base64(file);
553
554
// Upload to server
555
await fetch("/api/upload", {
556
method: "POST",
557
body: JSON.stringify({ file: base64 }),
558
headers: { "Content-Type": "application/json" }
559
});
560
} catch (error) {
561
console.error("Upload failed:", error);
562
}
563
};
564
565
const handleCSVImport = (csvData: any[]) => {
566
// Map CSV columns to database fields
567
const mappedData = importCSVMapper(csvData, {
568
"Full Name": "name",
569
"Email Address": "email",
570
"Phone Number": "phone"
571
});
572
573
// Process mapped data
574
console.log("Imported data:", mappedData);
575
};
576
577
return (
578
<div>
579
<input
580
type="file"
581
accept=".csv,.xlsx"
582
onChange={(e) => {
583
const file = e.target.files?.[0];
584
if (file) handleFileUpload(file);
585
}}
586
/>
587
</div>
588
);
589
}
590
```
591
592
### Helper Functions
593
594
#### Query Key Management
595
596
Standardized query key generation for consistent React Query caching.
597
598
```typescript { .api }
599
/**
600
* Query key utilities for React Query
601
*/
602
interface QueryKeys {
603
/** Generate keys for list queries */
604
list: (resource: string, params?: any) => string[];
605
/** Generate keys for single record queries */
606
one: (resource: string, id: BaseKey, params?: any) => string[];
607
/** Generate keys for many records queries */
608
many: (resource: string, ids: BaseKey[], params?: any) => string[];
609
/** Generate keys for custom queries */
610
custom: (resource: string, params?: any) => string[];
611
}
612
613
/**
614
* Query key builder for advanced cache management
615
*/
616
class KeyBuilder {
617
/** Set resource name */
618
resource(name: string): KeyBuilder;
619
/** Set action type */
620
action(action: string): KeyBuilder;
621
/** Set parameters */
622
params(params: any): KeyBuilder;
623
/** Build the key array */
624
get(): string[];
625
}
626
```
627
628
#### URL & Route Utilities
629
630
Utilities for URL generation and route matching.
631
632
```typescript { .api }
633
/**
634
* Generate user-friendly names from resource identifiers
635
* @param name - Resource name or identifier
636
* @returns User-friendly display name
637
*/
638
function useUserFriendlyName(): (name: string) => string;
639
640
/**
641
* Match resource from current route
642
* @param route - Current route path
643
* @param resources - Available resources
644
* @returns Matched resource configuration
645
*/
646
function matchResourceFromRoute(
647
route: string,
648
resources: IResourceItem[]
649
): IResourceItem | undefined;
650
651
/**
652
* Generate default document title
653
* @param params - Title generation parameters
654
* @returns Generated document title
655
*/
656
function generateDefaultDocumentTitle(params: {
657
resource?: string;
658
action?: string;
659
id?: BaseKey;
660
}): string;
661
```
662
663
#### Object Utilities
664
665
Utilities for object manipulation and property access.
666
667
```typescript { .api }
668
/**
669
* Flatten nested object keys with dot notation
670
* @param obj - Object to flatten
671
* @param prefix - Key prefix for nested properties
672
* @returns Flattened object
673
*/
674
function flattenObjectKeys(
675
obj: Record<string, any>,
676
prefix?: string
677
): Record<string, any>;
678
679
/**
680
* Convert property path string to array
681
* @param path - Property path (e.g., "user.profile.name")
682
* @returns Path array (e.g., ["user", "profile", "name"])
683
*/
684
function propertyPathToArray(path: string): string[];
685
```
686
687
**Usage Example:**
688
689
```typescript
690
import {
691
flattenObjectKeys,
692
propertyPathToArray,
693
useUserFriendlyName
694
} from "@refinedev/core";
695
696
function ObjectUtilsExample() {
697
const getUserFriendlyName = useUserFriendlyName();
698
699
// Flatten nested object
700
const nestedData = {
701
user: {
702
profile: {
703
name: "John Doe",
704
address: {
705
city: "New York",
706
country: "USA"
707
}
708
}
709
}
710
};
711
712
const flatData = flattenObjectKeys(nestedData);
713
// Result: { "user.profile.name": "John Doe", "user.profile.address.city": "New York", ... }
714
715
// Convert path to array
716
const pathArray = propertyPathToArray("user.profile.name");
717
// Result: ["user", "profile", "name"]
718
719
// Generate friendly names
720
const friendlyName = getUserFriendlyName("user_profiles");
721
// Result: "User Profiles"
722
723
return (
724
<div>
725
<h2>{friendlyName}</h2>
726
<pre>{JSON.stringify(flatData, null, 2)}</pre>
727
</div>
728
);
729
}
730
```
731
732
## Types
733
734
```typescript { .api }
735
interface UtilityConfig {
736
/** Default configuration values */
737
defaults?: Record<string, any>;
738
/** Feature flags */
739
features?: Record<string, boolean>;
740
/** Custom settings */
741
settings?: Record<string, any>;
742
}
743
744
interface ProcessingResult<T> {
745
/** Processing success status */
746
success: boolean;
747
/** Processed data */
748
data?: T;
749
/** Processing errors */
750
errors?: string[];
751
/** Warning messages */
752
warnings?: string[];
753
}
754
755
interface CacheOptions {
756
/** Cache expiry time in milliseconds */
757
expiry?: number;
758
/** Whether to use stale data while revalidating */
759
staleWhileRevalidate?: boolean;
760
/** Custom cache key */
761
key?: string;
762
}
763
764
interface ErrorBoundaryProps {
765
/** Fallback component for errors */
766
fallback?: React.ComponentType<{ error: Error }>;
767
/** Error callback */
768
onError?: (error: Error, errorInfo: any) => void;
769
/** Children to wrap */
770
children: React.ReactNode;
771
}
772
```
773
774
### Real-time & Live Data
775
776
#### usePublish Hook
777
778
Publishes real-time events to subscribed clients for live data synchronization.
779
780
```typescript { .api }
781
/**
782
* Publishes real-time events to subscribed clients
783
* @returns Publish function from the live provider
784
*/
785
function usePublish(): ((event: LiveEvent) => void) | undefined;
786
787
interface LiveEvent {
788
/** Channel name for the event */
789
channel: string;
790
/** Event type */
791
type: "created" | "updated" | "deleted" | string;
792
/** Event payload */
793
payload: {
794
/** IDs of affected records */
795
ids?: BaseKey[];
796
/** Additional event data */
797
[key: string]: any;
798
};
799
/** Timestamp of the event */
800
date: Date;
801
}
802
```
803
804
**Usage Example:**
805
806
```typescript
807
import { usePublish } from "@refinedev/core";
808
809
function ProductForm() {
810
const publish = usePublish();
811
812
const handleProductUpdate = (product) => {
813
// Update product logic...
814
815
// Notify other clients about the update
816
publish?.({
817
channel: "products",
818
type: "updated",
819
payload: {
820
ids: [product.id]
821
},
822
date: new Date()
823
});
824
};
825
826
return (
827
<form onSubmit={handleProductUpdate}>
828
{/* Form fields */}
829
</form>
830
);
831
}
832
```
833
834
#### useSubscription Hook
835
836
Subscribes to real-time events for live data synchronization and automatic updates.
837
838
```typescript { .api }
839
/**
840
* Subscribes to real-time events for live data updates
841
* @param params - Subscription configuration
842
*/
843
function useSubscription(params: UseSubscriptionConfig): void;
844
845
interface UseSubscriptionConfig {
846
/** Channel name to subscribe to */
847
channel: string;
848
/** Callback function when live events are received */
849
onLiveEvent: (event: LiveEvent) => void;
850
/** Types of events to subscribe to */
851
types?: Array<"created" | "updated" | "deleted" | "*" | string>;
852
/** Whether subscription is enabled */
853
enabled?: boolean;
854
/** Additional parameters for the subscription */
855
params?: {
856
ids?: BaseKey[];
857
id?: BaseKey;
858
sorters?: CrudSort[];
859
filters?: CrudFilter[];
860
subscriptionType?: "useList" | "useOne" | "useMany";
861
resource?: string;
862
[key: string]: any;
863
};
864
/** Metadata for the subscription */
865
meta?: MetaQuery & { dataProviderName?: string };
866
}
867
```
868
869
**Usage Example:**
870
871
```typescript
872
import { useSubscription, useList } from "@refinedev/core";
873
874
function ProductsList() {
875
const { data, refetch } = useList({ resource: "products" });
876
877
// Subscribe to product updates
878
useSubscription({
879
channel: "products",
880
onLiveEvent: (event) => {
881
if (event.type === "created" || event.type === "updated" || event.type === "deleted") {
882
// Refetch the list when products are modified
883
refetch();
884
}
885
},
886
types: ["created", "updated", "deleted"],
887
enabled: true
888
});
889
890
return (
891
<div>
892
{data?.data.map(product => (
893
<div key={product.id}>{product.name}</div>
894
))}
895
</div>
896
);
897
}
898
```
899
900
#### useLiveMode Hook
901
902
Manages live mode state and automatic real-time updates for data operations.
903
904
```typescript { .api }
905
/**
906
* Manages live mode state for automatic real-time updates
907
* @returns Live mode configuration and state
908
*/
909
function useLiveMode(): UseLiveModeReturnType;
910
911
interface UseLiveModeReturnType {
912
/** Current live mode setting */
913
liveMode?: "auto" | "manual" | "off";
914
}
915
```
916
917
**Usage Example:**
918
919
```typescript
920
import { useLiveMode, useList } from "@refinedev/core";
921
922
function ProductsList() {
923
const { liveMode } = useLiveMode();
924
925
const { data } = useList({
926
resource: "products",
927
liveMode: liveMode, // Use global live mode setting
928
});
929
930
return (
931
<div>
932
<p>Live mode: {liveMode}</p>
933
{data?.data.map(product => (
934
<div key={product.id}>{product.name}</div>
935
))}
936
</div>
937
);
938
}
939
```
940
941
### Menu & Navigation
942
943
#### useMenu Hook
944
945
Generates menu items for navigation based on available resources, with hierarchical structure support and customizable visibility.
946
947
```typescript { .api }
948
/**
949
* Generates menu items for navigation sidebars and menus
950
* @param params - Menu configuration options
951
* @returns Menu items with navigation structure
952
*/
953
function useMenu(params?: UseMenuConfig): UseMenuReturnType;
954
955
interface UseMenuConfig {
956
/** Additional metadata for menu generation */
957
meta?: Record<string, any>;
958
/** Hide menu items that have missing route parameters */
959
hideOnMissingParameter?: boolean;
960
}
961
962
interface UseMenuReturnType {
963
/** Keys of menu items that should be open by default */
964
defaultOpenKeys: string[];
965
/** Currently selected menu item key */
966
selectedKey: string;
967
/** Hierarchical menu items structure */
968
menuItems: TreeMenuItem[];
969
}
970
971
interface TreeMenuItem {
972
/** Menu item key/identifier */
973
key: string;
974
/** Display label for the menu item */
975
label?: string;
976
/** Icon component for the menu item */
977
icon?: React.ReactNode;
978
/** Route path for navigation */
979
route?: string;
980
/** Child menu items */
981
children: TreeMenuItem[];
982
/** Resource metadata */
983
resource?: IResourceItem;
984
/** Parent resource reference */
985
parentName?: string;
986
}
987
```
988
989
**Usage Example:**
990
991
```typescript
992
import { useMenu } from "@refinedev/core";
993
994
function Sidebar() {
995
const { menuItems, selectedKey, defaultOpenKeys } = useMenu({
996
hideOnMissingParameter: true
997
});
998
999
const renderMenuItem = (item: TreeMenuItem) => (
1000
<div key={item.key} className={selectedKey === item.key ? "active" : ""}>
1001
{item.icon}
1002
<span>{item.label}</span>
1003
{item.children.length > 0 && (
1004
<div>
1005
{item.children.map(renderMenuItem)}
1006
</div>
1007
)}
1008
</div>
1009
);
1010
1011
return (
1012
<nav>
1013
{menuItems.map(renderMenuItem)}
1014
</nav>
1015
);
1016
}
1017
```