Skip to content

Commit be8e07f

Browse files
christopherthielenmergify[bot]
authored andcommitted
fix(useSref): Update href if the stateName or params have changed
1 parent b3efe35 commit be8e07f

File tree

2 files changed

+149
-53
lines changed

2 files changed

+149
-53
lines changed
Lines changed: 132 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,15 @@
11
import * as React from 'react';
22
import { makeTestRouter, muteConsoleErrors } from '../../__tests__/util';
33
import { useSref } from '../useSref';
4-
import { UISref } from '../../UISref';
5-
import { UISrefActiveContext } from '../../UISrefActive';
6-
7-
const state = {
8-
name: 'state',
9-
url: '',
10-
component: () => (
11-
<UISref to="state2">
12-
<a>state2</a>
13-
</UISref>
14-
),
15-
};
16-
17-
const state2 = {
18-
name: 'state2',
19-
url: '/state2',
20-
component: () => <span>state2</span>,
21-
};
4+
import { UISrefActive, UISrefActiveContext } from '../../UISrefActive';
5+
6+
const state = { name: 'state', url: '/state' };
7+
const state2 = { name: 'state2', url: '/state2' };
8+
const state3 = { name: 'state3', url: '/state3/:param' };
229

2310
describe('useUiSref', () => {
2411
let { router, routerGo, mountInRouter } = makeTestRouter([]);
25-
beforeEach(() => ({ router, routerGo, mountInRouter } = makeTestRouter([state, state2])));
12+
beforeEach(() => ({ router, routerGo, mountInRouter } = makeTestRouter([state, state2, state3])));
2613

2714
it('throws if to is not a string', () => {
2815
const Component = () => {
@@ -70,37 +57,142 @@ describe('useUiSref', () => {
7057
expect(spy).not.toHaveBeenCalled();
7158
});
7259

73-
it('registers itself with the parent UISrefActive addStateInfo callback', () => {
74-
const spy = jest.fn();
75-
const Component = () => {
76-
const uiSref = useSref('state', {});
60+
it('updates the href when the stateName changes', () => {
61+
const Component = props => {
62+
const uiSref = useSref(props.state);
7763
return <a {...uiSref} />;
7864
};
7965

80-
mountInRouter(
81-
<UISrefActiveContext.Provider value={spy}>
82-
<Component />
83-
</UISrefActiveContext.Provider>
84-
);
66+
const wrapper = mountInRouter(<Component state="state" />);
67+
expect(wrapper.html()).toBe('<a href="/state"></a>');
8568

86-
expect(spy).toBeCalledTimes(1);
69+
wrapper.setProps({ state: 'state2' });
70+
expect(wrapper.html()).toBe('<a href="/state2"></a>');
8771
});
8872

89-
it('deregisters itself with the parent UISrefActive addStateInfo callback when unmounted', () => {
90-
const spy = jest.fn();
91-
const Component = () => {
92-
const uiSref = useSref('state', {});
93-
return <a {...uiSref} />;
73+
it('updates the href when the params changes', () => {
74+
const State3Link = props => {
75+
const sref = useSref('state3', { param: props.param });
76+
return <a {...sref} />;
77+
};
78+
79+
const wrapper = mountInRouter(<State3Link param="123" />);
80+
expect(wrapper.html()).toBe('<a href="/state3/123"></a>');
81+
82+
wrapper.setProps({ param: '456' });
83+
expect(wrapper.html()).toBe('<a href="/state3/456"></a>');
84+
});
85+
86+
it('participates in parent UISrefActive component active state', async () => {
87+
await routerGo('state2');
88+
89+
const State2Link = props => {
90+
const sref = useSref('state2');
91+
return (
92+
<a {...sref} className={props.className}>
93+
state2
94+
</a>
95+
);
9496
};
9597

9698
const wrapper = mountInRouter(
97-
<UISrefActiveContext.Provider value={() => spy}>
98-
<Component />
99-
</UISrefActiveContext.Provider>
99+
<UISrefActive class="active">
100+
<State2Link />
101+
</UISrefActive>
100102
);
103+
expect(wrapper.html()).toBe('<a href="/state2" class="active">state2</a>');
104+
});
101105

102-
expect(spy).toBeCalledTimes(0);
103-
wrapper.unmount();
104-
expect(spy).toBeCalledTimes(1);
106+
it('participates in grandparent UISrefActive component active state', async () => {
107+
await routerGo('state2');
108+
109+
const State2Link = props => {
110+
const sref = useSref('state2');
111+
return (
112+
<a {...sref} className={props.className}>
113+
state2
114+
</a>
115+
);
116+
};
117+
118+
const Component = () => {
119+
return (
120+
<UISrefActive class="grandparentactive">
121+
<div>
122+
<UISrefActive class="active">
123+
<State2Link />
124+
</UISrefActive>
125+
</div>
126+
</UISrefActive>
127+
);
128+
};
129+
130+
const wrapper = mountInRouter(<Component />);
131+
expect(wrapper.html()).toBe('<div class="grandparentactive"><a href="/state2" class="active">state2</a></div>');
132+
});
133+
134+
it('stops participating in parent/grandparent UISrefActive component active state when unmounted', async () => {
135+
await routerGo('state2');
136+
137+
const State2Link = props => {
138+
const sref = useSref('state2');
139+
return (
140+
<a {...sref} className={props.className}>
141+
state2
142+
</a>
143+
);
144+
};
145+
146+
const Component = props => {
147+
return (
148+
<UISrefActive class="grandparentactive">
149+
<div>
150+
<UISrefActive class="active">{props.show ? <State2Link /> : <span />}</UISrefActive>
151+
</div>
152+
</UISrefActive>
153+
);
154+
};
155+
156+
const wrapper = mountInRouter(<Component show={true} />);
157+
expect(wrapper.html()).toBe('<div class="grandparentactive"><a href="/state2" class="active">state2</a></div>');
158+
159+
wrapper.setProps({ show: false });
160+
expect(wrapper.html()).toBe('<div><span></span></div>');
161+
});
162+
163+
describe('implementation detail: ', () => {
164+
it('registers itself with the parent UISrefActive addStateInfo callback', () => {
165+
const spy = jest.fn();
166+
const Component = () => {
167+
const uiSref = useSref('state', {});
168+
return <a {...uiSref} />;
169+
};
170+
171+
mountInRouter(
172+
<UISrefActiveContext.Provider value={spy}>
173+
<Component />
174+
</UISrefActiveContext.Provider>
175+
);
176+
177+
expect(spy).toBeCalledTimes(1);
178+
});
179+
180+
it('deregisters itself with the parent UISrefActive addStateInfo callback when unmounted', () => {
181+
const spy = jest.fn();
182+
const Component = () => {
183+
const uiSref = useSref('state', {});
184+
return <a {...uiSref} />;
185+
};
186+
187+
const wrapper = mountInRouter(
188+
<UISrefActiveContext.Provider value={() => spy}>
189+
<Component />
190+
</UISrefActiveContext.Provider>
191+
);
192+
193+
expect(spy).toBeCalledTimes(0);
194+
wrapper.unmount();
195+
expect(spy).toBeCalledTimes(1);
196+
});
105197
});
106198
});

src/components/hooks/useSref.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useCallback, useContext, useEffect, useMemo } from 'react';
44
import { StateRegistry, UIViewAddress } from '../../index';
55
import { UISrefActiveContext } from '../UISrefActive';
66
import { UIViewContext } from '../UIView';
7+
import { useDeepObjectDiff } from './useDeepObjectDiff';
78
import { useRouter } from './useRouter';
89

910
export interface LinkProps {
@@ -33,28 +34,31 @@ export function useSref(stateName: string, params: object = {}, options: Transit
3334
throw new Error(IncorrectStateNameTypeError);
3435
}
3536

36-
useEffect(() => parentUISrefActiveAddStateInfo(stateName, params), []);
37+
useEffect(() => parentUISrefActiveAddStateInfo(stateName, params), [stateName, useDeepObjectDiff(params)]);
3738

3839
const hrefOptions = useMemo(() => getTransitionOptions(stateRegistry, options, parentUIViewAddress), [
3940
options,
4041
parentUIViewAddress,
4142
stateRegistry,
4243
]);
4344

44-
const handleClick = useCallback(
45-
(e: React.MouseEvent) => {
46-
if (!e.defaultPrevented && !(e.button == 1 || e.metaKey || e.ctrlKey)) {
47-
e.preventDefault();
48-
stateService.go(stateName, params, hrefOptions);
49-
}
50-
},
51-
[hrefOptions, params, stateService, stateName]
52-
);
45+
const href = useMemo(() => stateService.href(stateName, params, hrefOptions), [
46+
stateService,
47+
stateName,
48+
useDeepObjectDiff(params),
49+
hrefOptions,
50+
]);
5351

54-
return {
55-
onClick: handleClick,
56-
href: stateService.href(stateName, params, hrefOptions),
52+
const handleClick = (e: React.MouseEvent) => {
53+
if (!e.defaultPrevented && !(e.button == 1 || e.metaKey || e.ctrlKey)) {
54+
e.preventDefault();
55+
stateService.go(stateName, params, hrefOptions);
56+
}
5757
};
58+
59+
const onClick = useCallback(handleClick, [hrefOptions, params, stateService, stateName]);
60+
61+
return { onClick, href };
5862
}
5963

6064
/** @hidden */

0 commit comments

Comments
 (0)