diff --git a/index.d.ts b/index.d.ts index a1e0fd9..cf8b04a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -27,6 +27,7 @@ export const tippy: typeof tippyCore; export interface TippySingletonProps extends Partial { children: Array>; + enabled?: boolean; className?: string; plugins?: Plugin[]; [key: string]: any; diff --git a/src/Tippy.js b/src/Tippy.js index 9ada843..20a6dec 100644 --- a/src/Tippy.js +++ b/src/Tippy.js @@ -7,7 +7,7 @@ import { useInstance, useIsomorphicLayoutEffect, useUpdateClassName, -} from './hooks'; +} from './util-hooks'; export function Tippy({ children, diff --git a/src/TippySingleton.js b/src/TippySingleton.js index 088173d..3f0e72e 100644 --- a/src/TippySingleton.js +++ b/src/TippySingleton.js @@ -1,16 +1,17 @@ import {Children, cloneElement} from 'react'; import PropTypes from 'prop-types'; -import {createSingleton} from 'tippy.js'; import { - useIsomorphicLayoutEffect, useInstance, useUpdateClassName, -} from './hooks'; + useSingletonUpdate, + useSingletonCreate, +} from './util-hooks'; export default function TippySingleton({ children, className, plugins, + enabled = true, ignoreAttributes = true, ...restOfNativeProps }) { @@ -24,28 +25,11 @@ export default function TippySingleton({ ...restOfNativeProps, }; - useIsomorphicLayoutEffect(() => { - const {instances} = component; - const instance = createSingleton(instances, props, plugins); + const deps = [children.length]; - component.instance = instance; - - return () => { - instance.destroy(); - component.instances = instances.filter(i => !i.state.isDestroyed); - }; - }, [children.length]); - - useIsomorphicLayoutEffect(() => { - if (component.renders === 1) { - component.renders++; - return; - } - - component.instance.setProps(props); - }); - - useUpdateClassName(component, className, children.length); + useSingletonCreate(component, props, plugins, enabled, deps); + useSingletonUpdate(component, props, enabled); + useUpdateClassName(component, className, deps); return Children.map(children, child => { return cloneElement(child, { diff --git a/src/hooks.js b/src/hooks.js index 46c44e7..b367e3a 100644 --- a/src/hooks.js +++ b/src/hooks.js @@ -1,39 +1,14 @@ -import {isBrowser, updateClassName} from './utils'; -import {useLayoutEffect, useEffect, useRef} from 'react'; -import {createSingleton} from 'tippy.js'; - -export const useIsomorphicLayoutEffect = isBrowser - ? useLayoutEffect - : useEffect; - -export function useUpdateClassName(component, className, childrenDep) { - useIsomorphicLayoutEffect(() => { - const {tooltip} = component.instance.popperChildren; - if (className) { - updateClassName(tooltip, 'add', className); - return () => { - updateClassName(tooltip, 'remove', className); - }; - } - }, [className, childrenDep]); -} - -export function useInstance(initialValue) { - // Using refs instead of state as it's recommended to not store imperative - // values in state due to memory problems in React(?) - const ref = useRef(); - - if (!ref.current) { - ref.current = - typeof initialValue === 'function' ? initialValue() : initialValue; - } - - return ref.current; -} +import { + useInstance, + useSingletonCreate, + useSingletonUpdate, + useUpdateClassName, +} from './util-hooks'; export function useSingleton({ className, plugins, + enabled = true, ignoreAttributes = true, ...restOfNativeProps } = {}) { @@ -48,28 +23,11 @@ export function useSingleton({ ...restOfNativeProps, }; - useIsomorphicLayoutEffect(() => { - const {instances} = component; - const instance = createSingleton(instances, props, plugins); - - component.instance = instance; - - return () => { - instance.destroy(); - component.instances = instances.filter(i => !i.state.isDestroyed); - }; - }, [component.instances.length]); - - useIsomorphicLayoutEffect(() => { - if (component.renders === 1) { - component.renders++; - return; - } - - component.instance.setProps(props); - }); + const deps = [component.instances.length]; - useUpdateClassName(component, className, component.instances.length); + useSingletonCreate(component, props, plugins, enabled, deps); + useSingletonUpdate(component, props, enabled); + useUpdateClassName(component, className, deps); return instance => { component.instances.push(instance); diff --git a/src/util-hooks.js b/src/util-hooks.js new file mode 100644 index 0000000..7d8f132 --- /dev/null +++ b/src/util-hooks.js @@ -0,0 +1,69 @@ +import {isBrowser, updateClassName} from './utils'; +import {useLayoutEffect, useEffect, useRef} from 'react'; +import {createSingleton} from 'tippy.js'; + +export const useIsomorphicLayoutEffect = isBrowser + ? useLayoutEffect + : useEffect; + +export function useUpdateClassName(component, className, deps) { + useIsomorphicLayoutEffect(() => { + const {tooltip} = component.instance.popperChildren; + if (className) { + updateClassName(tooltip, 'add', className); + return () => { + updateClassName(tooltip, 'remove', className); + }; + } + }, [className, ...deps]); +} + +export function useInstance(initialValue) { + // Using refs instead of state as it's recommended to not store imperative + // values in state due to memory problems in React(?) + const ref = useRef(); + + if (!ref.current) { + ref.current = + typeof initialValue === 'function' ? initialValue() : initialValue; + } + + return ref.current; +} + +export function useSingletonCreate(component, props, plugins, enabled, deps) { + useIsomorphicLayoutEffect(() => { + const {instances} = component; + const instance = createSingleton(instances, props, plugins); + + component.instance = instance; + + if (!enabled) { + instance.disable(); + } + + return () => { + instance.destroy(); + component.instances = instances.filter(i => !i.state.isDestroyed); + }; + }, deps); +} + +export function useSingletonUpdate(component, props, enabled) { + useIsomorphicLayoutEffect(() => { + if (component.renders === 1) { + component.renders++; + return; + } + + const {instance} = component; + + instance.setProps(props); + + if (enabled) { + instance.enable(); + } else { + instance.disable(); + } + }); +} diff --git a/test/TippySingleton.test.js b/test/TippySingleton.test.js index 34c65d2..5c37cfa 100644 --- a/test/TippySingleton.test.js +++ b/test/TippySingleton.test.js @@ -228,6 +228,62 @@ describe('', () => { expect(tooltip.classList.contains('one')).toBe(true); }); + test('props.enabled initially `true`', () => { + const {rerender} = render( + + +