Skip to content

Instantly share code, notes, and snippets.

@atomiks

atomiks/LazyTippy.jsx

Last active Nov 25, 2020
Embed
What would you like to do?
Lazy Tippy
// Will only render the `content` or `render` elements if the tippy is mounted to the DOM.
// Replace <Tippy /> with <LazyTippy /> component and it should work the same.
function LazyTippy(props) {
const [mounted, setMounted] = useState(false);
const lazyPlugin = {
fn: () => ({
onShow: () => setMounted(true),
onHidden: () => setMounted(false),
}),
};
const computedProps = {...props};
computedProps.plugins = [lazyPlugin, ...(props.plugins || [])];
if (props.render) {
computedProps.render = (...args) => (mounted ? props.render(...args) : '');
} else {
computedProps.content = mounted ? props.content : '';
}
return <Tippy {...computedProps} />;
}
// Partially lazy
// Will only mount all <Tippy /> content once the singleton itself is mounted.
function PartiallyLazySingleton() {
const [source, target] = useSingleton();
const [mounted, setMounted] = useState(false);
return (
<>
<Tippy
onShow={() => setMounted(true)}
onHidden={() => setMounted(false)}
singleton={source}
/>
<Tippy content={mounted ? <ExpensiveComponent /> : ''} singleton={target}>
<button>Hello</button>
</Tippy>
<Tippy content={mounted ? <ExpensiveComponent /> : ''} singleton={target}>
<button>Hello</button>
</Tippy>
</>
);
}
// Fully lazy
// Will only mount the content if that is the currently used singleton content.
function LazySingleton() {
const [source, target] = useSingleton();
const [mountedRef, setMountedRef] = useState(null);
const ref1 = useRef();
const ref2 = useRef();
return (
<>
<Tippy
onTrigger={(_, event) => setMountedRef(event.currentTarget)}
onHidden={() => setMountedRef(null)}
singleton={source}
/>
<Tippy
content={mountedRef === ref1.current ? <ExpensiveComponent /> : ''}
singleton={target}
>
<button ref={ref1}>Hello</button>
</Tippy>
<Tippy
content={mountedRef === ref2.current ? <ExpensiveComponent /> : ''}
singleton={target}
>
<button ref={ref2}>Hello</button>
</Tippy>
</>
);
}
@itayganor

This comment has been minimized.

Copy link

@itayganor itayganor commented Apr 21, 2020

About LazySingleton.jsx,
won't it mount all linked singletons' content when a single one gets triggered?

@atomiks

This comment has been minimized.

Copy link
Owner Author

@atomiks atomiks commented Apr 21, 2020

Yeah that's what I meant by This is partially lazy since hidden singleton content is mounted once the singleton mounts, but it's still better than no optimization.

@atomiks

This comment has been minimized.

Copy link
Owner Author

@atomiks atomiks commented Apr 21, 2020

It's possible to optimize to a single one being mounted at once using onTrigger + onUntrigger hooks and storing the reference refs and checking to see which is the current one I'd imagine. It just takes a bit of work to setup but I'm sure you could abstract it into something neat

@sbusch

This comment has been minimized.

Copy link

@sbusch sbusch commented May 13, 2020

Here a TypeScript-enabled version of the code above (tested with TypeScript 3.8.3):

// Will only render the `content` or `render` elements if the tippy is mounted to the DOM.
// Replace <Tippy /> with <LazyTippy /> component and it should work the same.

import React from 'react';

import Tippy, { TippyProps } from '@tippyjs/react';

// Export own set of props (even if they are the same for now) to enable clients to be more future-proof
export type LazyTippyProps = TippyProps;

export const LazyTippy = (props: LazyTippyProps) => {
  const [mounted, setMounted] = React.useState(false);

  const lazyPlugin = {
    fn: () => ({
      onShow: () => setMounted(true),
      onHidden: () => setMounted(false),
    }),
  };

  const computedProps = { ...props };

  computedProps.plugins = [lazyPlugin, ...(props.plugins || [])];

  if (props.render) {
    const render = props.render; // let TypeScript safely derive that render is not undefined
    computedProps.render = (...args) => (mounted ? render(...args) : '');
  } else {
    computedProps.content = mounted ? props.content : '';
  }

  return <Tippy {...computedProps} />;
};
@atomiks

This comment has been minimized.

Copy link
Owner Author

@atomiks atomiks commented Jul 2, 2020

Added a fully lazy singleton example

@christopher-caldwell

This comment has been minimized.

Copy link

@christopher-caldwell christopher-caldwell commented Oct 9, 2020

@atomiks Would you mind adding a TS version of your fully lazy example? I tried for awhile, but couldn't get the ref right.

@lukesmurray

This comment has been minimized.

Copy link

@lukesmurray lukesmurray commented Nov 13, 2020

@christopher-caldwell

import Tippy, { useSingleton } from "@tippyjs/react";
import React, { useRef, useState } from "react";

function LazySingleton() {
  const [source, target] = useSingleton();
  const [mountedRef, setMountedRef] = useState<EventTarget | null>(null);
  const ref1 = useRef<HTMLButtonElement | null>(null); // set these to the type of your tippy contents.
  const ref2 = useRef<HTMLButtonElement | null>(null);
  return (
    <>
      <Tippy
        onTrigger={(_, event) => setMountedRef(event.currentTarget)}
        onHidden={() => setMountedRef(null)}
        singleton={source}
      />
      <Tippy
        content={mountedRef === ref1.current ? <ExpensiveComponent /> : ""}
        singleton={target}
      >
        <button ref={ref1}>Hello</button>
      </Tippy>
      <Tippy
        content={mountedRef === ref2.current ? <ExpensiveComponent /> : ""}
        singleton={target}
      >
        <button ref={ref2}>Hello</button>
      </Tippy>
    </>
  );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.