Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useCssTransitionGroup hook #479

Open
jantimon opened this issue Mar 28, 2019 · 9 comments
Open

Add useCssTransitionGroup hook #479

jantimon opened this issue Mar 28, 2019 · 9 comments

Comments

@jantimon
Copy link

jantimon commented Mar 28, 2019

Now that react supports hooks you might add one to this project e.g. like this:

const myButton = ({isVisible}) => {
  const {ref, className} = useCssTransitionGroup({in: isVisible, classNames: 'button'}); 

  return <button className={className} ref={ref}>Click me</button>;
}

This would allow people to wrap the library hook with a special behaviour based on their project requirements like this:

const useSlideIn = ({in}) => useCssTransitionGroup({in, classNames: 'slide'});

or

const useSlideIn = ({in}) => useCssTransitionGroup({onEnter: () => /* special slide logic */, in, classNames: 'slide'})

And then reuse such a transition effect multiple times in the project:

const Component1 = () => {
   const {ref, className} = useSlideIn({in});
  return <div ref={ref} className={className}>...</div>
}
@silvenon
Copy link
Collaborator

I assume you meant useCssTransition, not useCssTransitionGroup? 🙂 You can also very easily create a SlideIn component which composes CSSTransition, but I do appreciate the flatter syntax of hooks and the flexibility it provides, although I would rather change the API to return an array instead:

const [transitionRef, transitionClassName] = useCssTransition({ in: isVisible, classNames: 'button' })

That way it will be easier to create multiple transitions because array destructuring makes it easier to name variables differently so they don't clash (which I assume is why useState has the same API).

I'll put a pin in this idea and come back to it later, thanks for the suggestion and feel free to discuss further.

@jantimon
Copy link
Author

jantimon commented Mar 30, 2019

Oh yes you are absolutely right - useCssTransition is way better.

Maybe it would also have a third return value to support the mounted/unmount feature:

const [transitionRef, transitionClassName, inDom] = useCssTransition({ in: isVisible, classNames: 'button' });
return inDom && <div ... >

And yes - the array syntax is probably better here - className is a quite often used variable and maybe you would even like to add two animations at the same time.

@cdoublev
Copy link

cdoublev commented Apr 18, 2019

Hello,

Please forgive me for this little aside, but still related to using hooks to set a classname after on a component lifecycle event.

I came a few times to look at the features of React Transition Group but I've always found a workaround and avoided adding it to the project dependency. Now that React hooks are stable and I have a project involving animations that require this kind of features, I tried to implement a custom hook useTransition with useState and useEffect, because I forget about React Transition Group before starting doing this...

https://codepen.io/creative-wave/pen/vMRRWd

My hook has serious limitations. The features exposed in your comments are also very nice. But this proof of concept seems ok for my current project and I was just wondering why React Transition Group is using a node reference to force a reflow. It's not easy to debug through setTimeout callbacks, but it seems that they are called in the right order in my tests.

Do you have some insights to give me or links to look at on this topic?

EDIT: comments from this PR was helpfull. I've also been able to see why a reflow might be seen as necessary, but an easy workaround is to make enter overlapping a bit over enter--active, ie. using a timeout with a delay a few ms longer. Anyway, I think that something using node reference like how React Spring is doing it is the most performant solution.

@chilbi
Copy link

chilbi commented May 7, 2019

I rewrite class Transition Component with useTransition Hook.
https://codesandbox.io/s/x773r3zq2p

Use Example:

const duration = 300;
const defaultStyle = {
  transition: `opacity ${duration}ms ease-in-out`,
  opacity: 0,
};
const transitionStyles = {
  entering: { opacity: 1 },
  entered: { opacity: 1 },
  exiting: { opacity: 0 },
  exited: { opacity: 0 },
};
function FadeExample({ show, ...other }) {
  const [status, unmounted, nodeRef] = useTransition<HTMLDivElement>({ in: show, timeout: duration, unmountOnExit: true });
  if (unmounted) return null;
  return (
    <div
      {...other}
      ref={nodeRef}
      style={{ ...defaultStyle, ...transitionStyles[status] }}
    />
  );
}

@rppld
Copy link

rppld commented May 27, 2019

@chilbi Ah that’s amazing, was looking for exactly that just now! Would love to see this merged into the library.

@dario-colombo
Copy link

@chilbi that's a quite complex solution for people looking for an easy way to animate mounting component, I mean it's working, but is a deep dig code to follow. still big up for your commitment

@aranoe
Copy link

aranoe commented May 19, 2020

So, can we expect to see hooks integrated into the library any time soon?

@silvenon
Copy link
Collaborator

Hopefully soonish 😜 I'll start working on improving our test suite, then I'll see what to do from there, I also want to resolve #623, but maybe these will happen simultaneously.

@KutnerUri
Copy link

KutnerUri commented Oct 13, 2020

I might be oversimplifying things, but I think this basic hook is enough:

enum TransitionStage {
  entering = 'entering',
  entered = 'entered',
  exiting = 'exiting',
  exited = 'exited',
  appear = 'appear',
}

export function useInOutTransition(value: boolean, duration: number) {
  const [state, setState] = useState(value ? TransitionStage.appear : TransitionStage.exited);
  const isInitialRun = useRef(true);
  const durationRef = useRef(duration);
  durationRef.current = duration; // use latest

  useEffect(() => {
    if (isInitialRun.current) {
      isInitialRun.current = false;
      return () => {};
    }

    setState(value ? TransitionStage.entering : TransitionStage.exiting);
    const tmId = setTimeout(() => {
      const next = value ? TransitionStage.entered : TransitionStage.exited;
      setState(next);
    }, durationRef.current);

    return () => {
      if (tmId) clearTimeout(tmId);
    };
  }, [value]);

  return state;
}
  • nodeRef - seems to be needed just for callbacks.
    In any case, hooks should receive ref, instead of creating them, or only one hook could be used.
  • callbacks - did not implement, but seems easy to add.
  • unmount - does not seem necessary with hooks, as the developer can just write this:
    {state !== TransitionStage.exited && <div>...</div>}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants