-
Notifications
You must be signed in to change notification settings - Fork 135
Expand file tree
/
Copy pathuseSref.ts
More file actions
110 lines (99 loc) · 4.34 KB
/
useSref.ts
File metadata and controls
110 lines (99 loc) · 4.34 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
/** @packageDocumentation @reactapi @module react_hooks */
import * as React from 'react';
import { ReactHTMLElement, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { isString, StateDeclaration, TransitionOptions, UIRouter } from '@uirouter/core';
import { UISrefActiveContext } from '../components';
import { useDeepObjectDiff } from './useDeepObjectDiff';
import { useParentView } from './useParentView';
import { useRouter } from './useRouter';
export interface LinkProps {
onClick: React.MouseEventHandler<any>;
href?: string;
}
/** @hidden */
export const IncorrectStateNameTypeError = `The state name passed to useSref must be a string.`;
/** @hidden Gets all StateDeclarations that are registered in the StateRegistry. */
function useListOfAllStates(router: UIRouter) {
const initial = useMemo(() => router.stateRegistry.get(), []);
const [states, setStates] = useState(initial);
useEffect(() => router.stateRegistry.onStatesChanged(() => setStates(router.stateRegistry.get())), []);
return states;
}
/** @hidden Gets the StateDeclaration that this sref targets */
function useTargetState(router: UIRouter, stateName: string, relative: string): StateDeclaration {
// Whenever any states are added/removed from the registry, get the target state again
const allStates = useListOfAllStates(router);
return useMemo(() => {
return router.stateRegistry.get(stateName, relative);
}, [router, stateName, relative, allStates]);
}
/**
* A hook to create a link to a state.
*
* This hook returns link (anchor tag) props for a given state reference.
* The resulting props can be spread onto an anchor tag.
*
* The props returned from this hook are:
*
* - `href`: the browser URL of the referenced state
* - `onClick`: a mouse event handler that will active the referenced state
*
* Example:
* ```jsx
* function HomeLink() {
* const sref = useSref('home');
* return <a {...sref}>Home</a>
* }
* ```
*
* Example:
* ```jsx
* function UserLink({ userId, username }) {
* const sref = useSref('users.user', { userId: userId });
* return <a {...sref}>{username}</a>
* }
* ```
*
* The `onClick` handler falls back to native browser behavior (does not initiate a state transition) when:
*
* - the user Ctrl+Click / Alt+Click / Meta+Click / Shift+Click
* - the underlying tag (e.g.: anchor tag) has a 'target' attribute, such as `<a target="_blank">Open in new window</a>`
* - preventDefault has been called on the event, e.g.: `<a onClick={e => e.preventDefault()}>no-op</a>`
*
* @param stateName The name of the state to link to
* @param params Any parameter values
* @param options Transition options used when the onClick handler fires.
*/
export function useSref(stateName: string, params: object = {}, options: TransitionOptions = {}): LinkProps {
if (!isString(stateName)) {
throw new Error(IncorrectStateNameTypeError);
}
const router = useRouter();
// memoize the params object until the nested values actually change so they can be used as deps
const paramsMemo = useMemo(() => params, [useDeepObjectDiff(params)]);
const relative: string = useParentView().context.name;
const optionsMemo = useMemo(() => ({ relative, inherit: true, ...options }), [relative, options]);
const targetState = useTargetState(router, stateName, relative);
// Update href when the target StateDeclaration changes (in case the the state definition itself changes)
// This is necessary to handle things like future states
const href = useMemo(() => {
return router.stateService.href(stateName, paramsMemo, optionsMemo);
}, [router, stateName, paramsMemo, optionsMemo, targetState]);
const onClick = useCallback(
(e: React.MouseEvent) => {
const targetAttr = (e.currentTarget as any)?.attributes?.target;
const modifierKey = e.button >= 1 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey;
if (!e.defaultPrevented && targetAttr == null && !modifierKey) {
e.preventDefault();
router.stateService.go(stateName, paramsMemo, optionsMemo);
}
},
[router, stateName, paramsMemo, optionsMemo]
);
// Participate in any parent UISrefActive
const parentUISrefActiveAddStateInfo = useContext(UISrefActiveContext);
useEffect(() => {
return parentUISrefActiveAddStateInfo(targetState && targetState.name, paramsMemo);
}, [targetState, paramsMemo]);
return { onClick, href };
}