Skip to content

Mount & unmount React elements with CSS transitions

License

Notifications You must be signed in to change notification settings

lederer/react-showtime

Repository files navigation

React Showtime 🎟️ 🥁 🎭

Mount & unmount with CSS transitions

React Showtime makes it easy to apply CSS transitions to the appearance and disappearance of React elements. It automatically handles mounting and unmounting to allow time for transitions to occur.

Demo: https://react-showtime.dev/

👯 Choose between useShowtime hook and <Showtime /> component.
💃 Feels familiar: useShowtime is a near-drop-in replacement for conditional rendering with a state boolean.
💅 Specify showing styles however you like – inline, emotion, styled-components, classnames, you name it.
💨 Sensible API for defining hidden styles and custom transitions.
🎩 Included transitions: slideFade, slide, fade, rise, scale.
🎭 Symmetric or asymmetric show/hide transitions.
🕴 Zero dependencies. 3.4k min+gzip.

The essential insight of React Showtime is that the one-two sequence of React's useLayoutEffect and useEffect hooks is nicely suited to the one-two sequence of mounting a component with hidden CSS values and then applying showing CSS values to trigger the transition. As for hiding, transition event handlers trigger unmounting once the "hide" transition is complete.

What React Showtime is not

React Showtime is not for transitions that do not involve mounting/unmounting. It was created specifically as a shim for conditional rendering.

React Showtime is not for sophisticated animations, as it executes via CSS transition, not animation. Consider a more full-featured library like react-spring if you need keyframes or additional sophistication.

Getting Started

Installation

yarn add react-showtime
npm install react-showtime

tl;dr

  1. Choose between the useShowtime hook or the Showtime component. The component is better for list items or if you need to listen for events (onShowing, onHidden).
  2. Define your transition by describing the item's hidden styles with a CSS object literal. Or just pass the name of an included transition (slideFade, slide, fade, rise, scale).
  3. If using the hook, attach the supplied ref to your containing element and conditionally render your item with the supplied isMounted boolean.
  4. Call the hook's show() and hide() functions – or toggle the component's show prop – as needed.

Optionally adjust transition timing via duration, delay, easing.

You can also define asymmetric show/hide transitions (showTransition, hideTransition) and timing (showDuration, showDelay, showEasing, hideDuration, hideDelay, hideEasing).

Usage

Hook vs Component

As a rule of thumb, since you can't call hooks inside loops, use the hook when dealing with a singleton item that needs to conditionally appear or disappear (eg, a notification), and use the component when you've an indeterminate set of children that need to individually transition in and out of the DOM (eg, a list of messages).

useShowtime hook

The hook is designed to integrate with React's conditional rendering idiom. It returns an array/object containing a ref that must be attached to your element or component, an isMounted boolean that will conditionally render it, and show() and hide() functions for you to call as needed.

import React from "react";
import { useShowtime } from "react-showtime";

const HookExample = () => {
    const [ref, isMounted, show, hide] = useShowtime();

    // Or use object destructuring...
    // const {ref, isMounted, show, hide} = useShowtime();

    const toggle = () => (isMounted ? hide() : show());

    return (
        <div>
            <button onClick={toggle}>Toggle</button>
            {isMounted && <div ref={ref}>Hi there</div>}
        </div>
    );
};

Your element or component will start off showing by default. Pass { startHidden: true } to override that.

Pass { startWithTransition: true } to automatically execute the show transition when the item initially mounts. It will be ignored if startHidden is true.

Showtime component

Showtime takes a single child component. It uses useShowtime under the hood, cloning the child and adding the ref to it.

Toggle the show boolean prop to trigger show/hide.

import React, { useState } from "react";
import { Showtime } from "react-showtime";

const ComponentExample = () => {
    const [show, setShow] = useState(true);

    const toggle = () => setShow((current) => !current);

    return (
        <div>
            <button onClick={toggle}>Toggle</button>
            <Showtime show={show}>
                <div>Oh hi</div>
            </Showtime>
        </div>
    );
};

Pass startWithTransition={true} to automatically execute the show transition when the item initially mounts. It will be ignored if show is initially set to false.

Additional performance consideration

Since Showtime clones the child to attach its ref, it may be an expensive operation in some cases if the child component is substantially complicated. If so, provide a function that takes a ref and returns the child component instead, which may be more performant:

import React, { useState } from "react";
import { Showtime } from "react-showtime";

const ComponentExample = () => {
    const [show, setShow] = useState(true);

    const toggle = () => setShow((current) => !current);

    return (
        <div>
            <button onClick={toggle}>Toggle</button>
            <Showtime show={show}>
                {(ref) => <div ref={ref}>Oh hi</div>}
            </Showtime>
        </div>
    );
};

However, the direct child specification is recommended for most users.

Transitions

If you accept all defaults, you'll get a slideFade transition with a 250ms duration, 16ms delay, and "ease" easing:

// Hook
const [ref, isMounted, show, hide] = useShowtime();
// Component
<Showtime show={true}>...</Showtime>

Included transitions

React Showtime includes a set of pre-configured transitions: slideFade, slide, fade, rise, scale.

Choose one by passing its name as the hook's sole parameter or to the component's transition prop.

// Hook
const [ref, isMounted, show, hide] = useShowtime("slide");
// Component
<Showtime transition="fade" show={true}>
    ...
</Showtime>

The hook also accepts an object instead of a string, in which case pass { transition: ... }:

const [ref, isMounted, show, hide] = useShowtime({ transition: "slide" });

Timing

Adjust the transition's timing via duration, delay, and easing.

duration and delay accept integers (interpreted as milliseconds), floats (interpreted as seconds), or strings (eg, "1s" or "300ms".)

easing accepts any valid CSS transition-timing-function value.

// Hook
const [ref, isMounted, show, hide] = useShowtime({
    transition: "rise",
    duration: 1000,
    delay: 250,
    easing: "linear",
});
// Component
<Showtime
    show={true}
    transition="scale"
    duration={500}
    delay={50}
    easing="ease-out"
>

If you need different timings across show and hide transitions, use showDuration, showDelay, showEasing, and hideDuration, hideDelay, hideEasing.

Custom transitions

To define a custom transition, pass a CSS object literal describing the item's hidden state to the transition prop(erty).

Each value can be a string, number, or object. Strings and numbers will be passed through as CSS.

As an example, here's how you might define a revolve transition, where showing would mount the item then fade it in while spinning it around the y-axis. Hiding would do the reverse.

// Hook
const [ref, isMounted, show, hide] = useShowtime({
    transition: {
        transform: "rotate3d(0, 1, 0, 180deg)",
        opacity: 0,
    },
    ...
});
// Component
<Showtime
    transition={{
        transform: "rotate3d(0, 1, 0, 180deg)",
        opacity: 0,
    }}
    ...
>
Per-property timing

You can pass an object instead of a string or number as a CSS property's value. It should contain { value, duration, delay, easing } properties.

value is required and will be passed through as the CSS property's value.

The other properties are optional and will be applied to that property's transition timing, overriding any inherited timing values.

In this example, the right and top CSS properties will have a 350ms transition duration and the default "ease" easing, while opacity will take 400ms using "linear" easing.

const HookExample = () => {
    const [ref, isMounted, show, hide] = useShowtime({
        duration: 350,
        transition: {
            right: "100vw",
            top: "-100vh",
            opacity: {
                value: 0,
                duration: 400,
                easing: "linear",
            },
        },
    });

    // ...
};

Asymmetric transitions with showTransition and hideTransition

The showTransition and hideTransition properties allow you to use different transitions for showing and hiding. This is useful if, say, a notification should slide down from above, but fade away when dismissed.

Like transition, these properties accept a string (included transition) or object (custom transition). They override transition if that's also passed in.

const HookExample = () => {
    const [ref, isMounted, show, hide] = useShowtime({
        showTransition: "fade",
        hideTransition: {
            right: "100vw",
            top: "-100vh",
            opacity: 0,
        },
        hideDuration: 350,
    });

    // ...
};

Attaching the ref

The useShowtime hook provides a ref that must end up attached to the element you're showing/hiding. It uses the ref to directly assign CSS transition properties and hidden styles to the element, and to listen for transition events.

If you are transitioning an element directly, you can just pass the provided ref as a prop.

If you are transitioning a custom component, consider updating the component to use ref forwarding to pass the ref down to the component's outermost element.

If you are transitioning a component you cannot edit and that does not forward refs to its outermost element, attach the ref to a wrapper div.

Attaching multiple refs

There may be times when you need to attach your own ref to the element or component, along with React Showtime's ref. You can do this using a callback ref.

import React, { useRef } from "react";
import { useShowtime } from "react-showtime";

const MultipleRefsExample = () => {
    const myRef = useRef();
    const [showtimeRef, isMounted, show, hide] = useShowtime();

    const setRefs = (node) => {
        myRef.current = node;
        showtimeRef.current = node;
    };

    const toggle = () => (isMounted ? hide() : show());

    return (
        <>
            <button onClick={toggle}>Toggle</button>
            {isMounted && <div ref={setRefs}>Hi there</div>}
        </>
    );
};

Attaching your own ref to the Showtime component's child

If you pass a function child to the Showtime component (aka render prop), you can use a similar solution as the above to attach both your own ref and the ref Showtime supplies to the function child.

If you pass a normal JSX child to the Showtime component, the implementation is robust to any normal or callback ref you might attach yourself. Eg, in the following code, myRef will be attached as expected, along with the ref that Showtime attaches internally.

import React, { useRef } from "react";
import { Showtime } from "react-showtime";

const MultipleRefsExample = () => {
    const myRef = useRef();
    const [show, setShow] = useState(true);

    const toggle = () => setShow((current) => !current);

    return (
        <>
            <button onClick={toggle}>Toggle</button>
            <Showtime show={show}>
                <div ref={myRef}>Hi there</div>
            </Showtime>
        </>
    );
};

Events

The Showtime component accepts handlers for onHidden and onShowing. Other lifecycle events are being considered.

The useShowtime hook currently does not accept any event handlers.

API

All timing-related numbers are interpreted as milliseconds if integer, and seconds if float.

useShowtime hook

The useShowtime hook accepts a single parameter, which can be either of:

Name Type Req'd Default Description
startHidden boolean no false Hide the element initially
startWithTransition boolean no false Execute show transition on initial mount. Ignored if startHidden is true.
transition string or CSS Properties no "slideFade" Included transition or object defining custom transition (see below)
showTransition string or CSS Properties no "slideFade" Included transition or object defining custom transition (see below)
hideTransition string or CSS Properties no "slideFade" Included transition or object defining custom transition (see below)
duration number or string no 250 Transition duration
delay number or string no 16 Transition delay
easing string no "ease" Transition timing function
showDuration number or string no Transition duration
showDelay number or string no Transition delay
showEasing string no Transition timing function
hideDuration number or string no Transition duration
hideDelay number or string no Transition delay
hideEasing string no Transition timing function

Showtime component

Name Type Req'd Default Description
show boolean yes Toggle this to show/hide the element or component
startWithTransition boolean no false Execute show transition on initial mount. Ignored if show initially set to false.
transition string or CSS Properties no "slideFade" Included transition or object defining custom transition (see below)
showTransition string or CSS Properties no "slideFade" Included transition or object defining custom transition (see below)
hideTransition string or CSS Properties no "slideFade" Included transition or object defining custom transition (see below)
duration number or string no 250 Transition duration
delay number or string no 16 Transition delay
easing string no "ease" Transition timing function
showDuration number or string no Transition duration
showDelay number or string no Transition delay
showEasing string no Transition timing function
hideDuration number or string no Transition duration
hideDelay number or string no Transition delay
hideEasing string no Transition timing function
onHidden function no Called when hide transition complete.
onShowing function no Called when show transition complete.

Transition objects

CSS Properties

Name Type Req'd Default Description
[Any CSS property name] string, number, or CSS Property yes Use camelCase for names. String and number values passed directly to style.

CSS Property

Name Type Req'd Default Description
value string or number yes Any CSS property name.
duration number or string no inherited Transition duration
delay number or string no inherited Transition delay
easing string no inherited Transition timing function

Development

React Showtime was originally bootstrapped with create-react-hook using this blog post as a guiding reference.

Be sure to reflect any API changes in test fixtures, the demo app in example/, the TypeScript types in index.d.ts, and this readme.

To get started:

  1. yarn start to watch for local changes to react-showtime,
  2. yarn start in example/ to watch for changes to the demo site and run a dev server.

This project uses the Git Flow branching workflow. Usage of the gitflow-cjs CLI tool is recommended for releases and hotfixes.