-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Expand file tree
/
Copy pathuseGetPathForRecord.ts
More file actions
170 lines (160 loc) · 5.57 KB
/
useGetPathForRecord.ts
File metadata and controls
170 lines (160 loc) · 5.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { useState, useEffect } from 'react';
import { useResourceContext } from '../core/useResourceContext';
import { useRecordContext } from '../controller/record/useRecordContext';
import type { RaRecord } from '../types';
import type { LinkToType } from './types';
import { useCanAccess } from '../auth';
import { useResourceDefinition } from '../core';
import { useCreatePath } from './useCreatePath';
/**
* Get a path for a record, based on the current resource and the link type.
*
* Accepted link types are 'edit', 'show', a route string, false, or a function returning one of these types.
*
* @example
* // basic usage (leverages RecordContext, ResourceContext and ResourceDefinitionContext)
* const EditLink = () => {
* const path = useGetPathForRecord();
* return path ? <Link to={path}>Edit</Link> : null;
* };
*
* // controlled mode
* const EditLink = ({ record, resource }) => {
* const path = useGetPathForRecord({ record, resource, link: 'edit' });
* return path ? <Link to={path}>Edit</Link> : null;
* };
*
* // the link option can be a function
* const EditLink = ({ record, resource }) => {
* const path = useGetPathForRecord({ record, resource, link: (record, resource) => record.canEdit ? 'edit' : false });
* return path ? <Link to={path}>Edit</Link> : null;
* };
*
* // the link option can be a function returning a promise
* const EditLink = ({ record, resource }) => {
* const path = useGetPathForRecord({ record, resource, link: async (record, resource) => {
* const canEdit = await canEditRecord(record, resource);
* return canEdit ? 'edit' : false;
* }});
* return path ? <Link to={path}>Edit</Link> : null;
* };
*/
export const useGetPathForRecord = <RecordType extends RaRecord = RaRecord>(
options: UseGetPathForRecordOptions<RecordType> = {}
): string | false | undefined => {
const { link } = options || {};
const record = useRecordContext(options);
const resource = useResourceContext(options);
if (!resource) {
throw new Error(
'Cannot generate a link for a record without a resource. You must use useGetPathForRecord within a ResourceContextProvider, or pass a resource prop.'
);
}
const resourceDefinition = useResourceDefinition(options);
const createPath = useCreatePath();
const [path, setPath] = useState<string | false>(
link && typeof link !== 'function' && record != null
? createPath({
resource,
id: record.id,
type: link,
})
: false
);
// in preparation for the default value, does the user have access to the show and edit pages?
// (we can't run hooks conditionally, so we need to run them even though the link is specified)
const { canAccess: canAccessShow } = useCanAccess({
action: 'show',
resource,
record,
enabled: link == null && resourceDefinition.hasShow,
});
const { canAccess: canAccessEdit } = useCanAccess({
action: 'edit',
resource,
record,
enabled: link == null && resourceDefinition.hasEdit,
});
useEffect(() => {
if (!record) return;
if (link === false) {
setPath(false);
return;
}
// Handle the inferred link type case
if (link == null) {
// We must check whether the resource has an edit view because if there is no
// authProvider, canAccessShow will always be true
if (resourceDefinition.hasShow && canAccessShow) {
setPath(
createPath({
resource,
id: record.id,
type: 'show',
})
);
return;
}
// We must check whether the resource has an edit view because if there is no
// authProvider, canAccessEdit will always be true
if (resourceDefinition.hasEdit && canAccessEdit) {
setPath(
createPath({
resource,
id: record.id,
type: 'edit',
})
);
return;
}
}
// Handle the link function case
if (typeof link === 'function') {
const linkResult = link(record, resource);
if (linkResult instanceof Promise) {
linkResult.then(resolvedPath => setPath(resolvedPath));
return;
}
setPath(
linkResult
? createPath({
resource,
id: record.id,
type: linkResult,
})
: false
);
return;
}
// handle string case
if (link) {
setPath(
createPath({
resource,
id: record.id,
type: link,
})
);
}
}, [
createPath,
canAccessShow,
canAccessEdit,
link,
record,
resource,
resourceDefinition.hasEdit,
resourceDefinition.hasShow,
]);
return path;
};
export interface UseGetPathForRecordOptions<
RecordType extends RaRecord = RaRecord,
> {
resource?: string;
record?: RecordType;
link?: LinkToType<RecordType>;
}
export type UseGetRouteForRecordOptions<
RecordType extends RaRecord = RaRecord,
> = UseGetPathForRecordOptions<RecordType>;