Skip to content
This repository was archived by the owner on Nov 9, 2024. It is now read-only.

Commit 8ec5fd9

Browse files
authored
Support enabled prop in singletons (#138)
* Support `enabled` prop in singletons * Add enabled prop type
1 parent 7eb1c8e commit 8ec5fd9

File tree

7 files changed

+200
-78
lines changed

7 files changed

+200
-78
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const tippy: typeof tippyCore;
2727

2828
export interface TippySingletonProps extends Partial<KnownProps> {
2929
children: Array<React.ReactElement<any>>;
30+
enabled?: boolean;
3031
className?: string;
3132
plugins?: Plugin[];
3233
[key: string]: any;

src/Tippy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
useInstance,
88
useIsomorphicLayoutEffect,
99
useUpdateClassName,
10-
} from './hooks';
10+
} from './util-hooks';
1111

1212
export function Tippy({
1313
children,

src/TippySingleton.js

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import {Children, cloneElement} from 'react';
22
import PropTypes from 'prop-types';
3-
import {createSingleton} from 'tippy.js';
43
import {
5-
useIsomorphicLayoutEffect,
64
useInstance,
75
useUpdateClassName,
8-
} from './hooks';
6+
useSingletonUpdate,
7+
useSingletonCreate,
8+
} from './util-hooks';
99

1010
export default function TippySingleton({
1111
children,
1212
className,
1313
plugins,
14+
enabled = true,
1415
ignoreAttributes = true,
1516
...restOfNativeProps
1617
}) {
@@ -24,28 +25,11 @@ export default function TippySingleton({
2425
...restOfNativeProps,
2526
};
2627

27-
useIsomorphicLayoutEffect(() => {
28-
const {instances} = component;
29-
const instance = createSingleton(instances, props, plugins);
28+
const deps = [children.length];
3029

31-
component.instance = instance;
32-
33-
return () => {
34-
instance.destroy();
35-
component.instances = instances.filter(i => !i.state.isDestroyed);
36-
};
37-
}, [children.length]);
38-
39-
useIsomorphicLayoutEffect(() => {
40-
if (component.renders === 1) {
41-
component.renders++;
42-
return;
43-
}
44-
45-
component.instance.setProps(props);
46-
});
47-
48-
useUpdateClassName(component, className, children.length);
30+
useSingletonCreate(component, props, plugins, enabled, deps);
31+
useSingletonUpdate(component, props, enabled);
32+
useUpdateClassName(component, className, deps);
4933

5034
return Children.map(children, child => {
5135
return cloneElement(child, {

src/hooks.js

Lines changed: 11 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,14 @@
1-
import {isBrowser, updateClassName} from './utils';
2-
import {useLayoutEffect, useEffect, useRef} from 'react';
3-
import {createSingleton} from 'tippy.js';
4-
5-
export const useIsomorphicLayoutEffect = isBrowser
6-
? useLayoutEffect
7-
: useEffect;
8-
9-
export function useUpdateClassName(component, className, childrenDep) {
10-
useIsomorphicLayoutEffect(() => {
11-
const {tooltip} = component.instance.popperChildren;
12-
if (className) {
13-
updateClassName(tooltip, 'add', className);
14-
return () => {
15-
updateClassName(tooltip, 'remove', className);
16-
};
17-
}
18-
}, [className, childrenDep]);
19-
}
20-
21-
export function useInstance(initialValue) {
22-
// Using refs instead of state as it's recommended to not store imperative
23-
// values in state due to memory problems in React(?)
24-
const ref = useRef();
25-
26-
if (!ref.current) {
27-
ref.current =
28-
typeof initialValue === 'function' ? initialValue() : initialValue;
29-
}
30-
31-
return ref.current;
32-
}
1+
import {
2+
useInstance,
3+
useSingletonCreate,
4+
useSingletonUpdate,
5+
useUpdateClassName,
6+
} from './util-hooks';
337

348
export function useSingleton({
359
className,
3610
plugins,
11+
enabled = true,
3712
ignoreAttributes = true,
3813
...restOfNativeProps
3914
} = {}) {
@@ -48,28 +23,11 @@ export function useSingleton({
4823
...restOfNativeProps,
4924
};
5025

51-
useIsomorphicLayoutEffect(() => {
52-
const {instances} = component;
53-
const instance = createSingleton(instances, props, plugins);
54-
55-
component.instance = instance;
56-
57-
return () => {
58-
instance.destroy();
59-
component.instances = instances.filter(i => !i.state.isDestroyed);
60-
};
61-
}, [component.instances.length]);
62-
63-
useIsomorphicLayoutEffect(() => {
64-
if (component.renders === 1) {
65-
component.renders++;
66-
return;
67-
}
68-
69-
component.instance.setProps(props);
70-
});
26+
const deps = [component.instances.length];
7127

72-
useUpdateClassName(component, className, component.instances.length);
28+
useSingletonCreate(component, props, plugins, enabled, deps);
29+
useSingletonUpdate(component, props, enabled);
30+
useUpdateClassName(component, className, deps);
7331

7432
return instance => {
7533
component.instances.push(instance);

src/util-hooks.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {isBrowser, updateClassName} from './utils';
2+
import {useLayoutEffect, useEffect, useRef} from 'react';
3+
import {createSingleton} from 'tippy.js';
4+
5+
export const useIsomorphicLayoutEffect = isBrowser
6+
? useLayoutEffect
7+
: useEffect;
8+
9+
export function useUpdateClassName(component, className, deps) {
10+
useIsomorphicLayoutEffect(() => {
11+
const {tooltip} = component.instance.popperChildren;
12+
if (className) {
13+
updateClassName(tooltip, 'add', className);
14+
return () => {
15+
updateClassName(tooltip, 'remove', className);
16+
};
17+
}
18+
}, [className, ...deps]);
19+
}
20+
21+
export function useInstance(initialValue) {
22+
// Using refs instead of state as it's recommended to not store imperative
23+
// values in state due to memory problems in React(?)
24+
const ref = useRef();
25+
26+
if (!ref.current) {
27+
ref.current =
28+
typeof initialValue === 'function' ? initialValue() : initialValue;
29+
}
30+
31+
return ref.current;
32+
}
33+
34+
export function useSingletonCreate(component, props, plugins, enabled, deps) {
35+
useIsomorphicLayoutEffect(() => {
36+
const {instances} = component;
37+
const instance = createSingleton(instances, props, plugins);
38+
39+
component.instance = instance;
40+
41+
if (!enabled) {
42+
instance.disable();
43+
}
44+
45+
return () => {
46+
instance.destroy();
47+
component.instances = instances.filter(i => !i.state.isDestroyed);
48+
};
49+
}, deps);
50+
}
51+
52+
export function useSingletonUpdate(component, props, enabled) {
53+
useIsomorphicLayoutEffect(() => {
54+
if (component.renders === 1) {
55+
component.renders++;
56+
return;
57+
}
58+
59+
const {instance} = component;
60+
61+
instance.setProps(props);
62+
63+
if (enabled) {
64+
instance.enable();
65+
} else {
66+
instance.disable();
67+
}
68+
});
69+
}

test/TippySingleton.test.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,62 @@ describe('<TippySingleton />', () => {
228228
expect(tooltip.classList.contains('one')).toBe(true);
229229
});
230230

231+
test('props.enabled initially `true`', () => {
232+
const {rerender} = render(
233+
<TippySingleton enabled={true}>
234+
<Tippy content="tooltip">
235+
<button />
236+
</Tippy>
237+
<Tippy content="tooltip">
238+
<button />
239+
</Tippy>
240+
</TippySingleton>,
241+
);
242+
243+
expect(instance.state.isEnabled).toBe(true);
244+
245+
rerender(
246+
<TippySingleton enabled={false}>
247+
<Tippy content="tooltip">
248+
<button />
249+
</Tippy>
250+
<Tippy content="tooltip">
251+
<button />
252+
</Tippy>
253+
</TippySingleton>,
254+
);
255+
256+
expect(instance.state.isEnabled).toBe(false);
257+
});
258+
259+
test('props.enabled initially `false`', () => {
260+
const {rerender} = render(
261+
<TippySingleton enabled={false}>
262+
<Tippy content="tooltip">
263+
<button />
264+
</Tippy>
265+
<Tippy content="tooltip">
266+
<button />
267+
</Tippy>
268+
</TippySingleton>,
269+
);
270+
271+
expect(instance.state.isEnabled).toBe(false);
272+
273+
rerender(
274+
<TippySingleton enabled={true}>
275+
<Tippy content="tooltip">
276+
<button />
277+
</Tippy>
278+
<Tippy content="tooltip">
279+
<button />
280+
</Tippy>
281+
</TippySingleton>,
282+
);
283+
284+
expect(instance.state.isEnabled).toBe(true);
285+
});
286+
231287
test('props.plugins', () => {
232288
const plugins = [{fn: () => ({})}];
233289

test/useSingleton.test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,60 @@ describe('The useSingleton hook', () => {
202202
).toBe(true);
203203
});
204204

205+
test('props.enabled initially `true`', () => {
206+
let instance;
207+
208+
function App({enabled}) {
209+
const singleton = useSingleton({
210+
enabled,
211+
onCreate(i) {
212+
instance = i;
213+
},
214+
});
215+
216+
return (
217+
<Tippy singleton={singleton}>
218+
<button />
219+
</Tippy>
220+
);
221+
}
222+
223+
const {rerender} = render(<App enabled={true} />);
224+
225+
expect(instance.state.isEnabled).toBe(true);
226+
227+
rerender(<App enabled={false} />);
228+
229+
expect(instance.state.isEnabled).toBe(false);
230+
});
231+
232+
test('props.enabled initially `false`', () => {
233+
let instance;
234+
235+
function App({enabled}) {
236+
const singleton = useSingleton({
237+
enabled,
238+
onCreate(i) {
239+
instance = i;
240+
},
241+
});
242+
243+
return (
244+
<Tippy singleton={singleton}>
245+
<button />
246+
</Tippy>
247+
);
248+
}
249+
250+
const {rerender} = render(<App enabled={false} />);
251+
252+
expect(instance.state.isEnabled).toBe(false);
253+
254+
rerender(<App enabled={true} />);
255+
256+
expect(instance.state.isEnabled).toBe(true);
257+
});
258+
205259
test('props.plugins', () => {
206260
const plugins = [{fn: () => ({})}];
207261

0 commit comments

Comments
 (0)