react-gsap icon indicating copy to clipboard operation
react-gsap copied to clipboard

How to prevent re-rendering when <Timeline /> playState changes?

Open capi1O opened this issue 4 years ago • 1 comments

I have defined my timeline as such:

const Component = () => {

const [playState, setPlayState] = useState(PlayState.pause);

return (
		<Timeline
			playState={playState}
			labels={[{ label: 'start', position: 0 }]}
			target={<Content />}
		>
			<Tweens />
		</Timeline>
);

where <Content /> is a component using react-gsap targets.set to set targets similarly to what is done in the examples.

I want to control my timeline playState using useState inside my <Component /> which contains the <Timeline />.

But every time I change state <Content /> is re-rendered of course. The thing is my playState depends depends on <Content /> (I wait for some elements inside <Content /> to load, then I inform <Component /> that animation can begin and setPlayState(PlayState.play). Because of the re-rendering it is not possible because <Content /> stuff are reloaded, the animation begins before it has fully loaded.

I don't think this is currently possible due how react-gsap is structured because content is a rendered prop of <Timeline />. I understand this was done this way in order to be able to set the targets but IMO we should be able to separate content and animation in the React tree.

I tried to make it clear and I hope I am not doing anything too weird.

capi1O avatar Feb 10 '21 10:02 capi1O

I ended up not using playState but using low-level GSAP to control timeline play state from content. In order to do that I had to pass the <Timeline /> ref to <Content />. because of issue #33 I had to use a wrapper component.

import React, { useRef, forwardRef } from 'react';
import { Timeline, PlayState } from 'react-gsap';

import Content from './Content';
import Tweens from './Tweens';

const Component = () => {

	const timelineRef = useRef(null);

	const ContentWrapper = forwardRef<HTMLDivElement, {}>((props, targets: TargetsRef) => (
		<Content targets={targets} timelineRef={timelineRef} />
	));

	return (

		<Timeline
			ref={timelineRef}
			labels={[{ label: 'start', position: 0 }]}
			target={<ContentWrapper />}
			playState={PlayState.stop}
		>
			<Tweens />
		</Timeline>
	);
};
export default Component;

then in <Content /> I could access

const Content = ({ timelineRef: MutableRefObject<Timeline>, targets: TargetsRef }) => {

   const handleClick = () => {
      timelineRef.current?.getGSAP().play(0);
   };

   return <button ref={node => targets.set('button', node)} onClick={handleClick}>some content</button>
};

capi1O avatar Feb 11 '21 18:02 capi1O