Skip to content

Commit

Permalink
feat: use forwardRef with useImperativeHandle for Timeline target
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Fischer committed Apr 1, 2021
1 parent aee8eb3 commit cf6f257
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 60 deletions.
34 changes: 34 additions & 0 deletions packages/docz/src/components/Timeline.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,40 @@ If you don't add a target you transform all target components.

## Advanced multiple targets

If you need to target individual elements you can use a special forwardRef component with useImperativeHandle hook.

In this way these component can be better reused and the refs not only work in a Timeline target context.

You can also pass an array ref like seen with div2. In this way you can use the `stagger` prop.

```javascript
const TargetWithNames = forwardRef((props, ref: any) => {
const div1 = useRef(null);
const div2 = useRef([]);
const div3 = useRef(null);
useImperativeHandle(ref, () => ({
div1,
div2,
div3,
}));
return (
<div style={{ textAlign: 'center' }}>
<h3 ref={div1}>THIS</h3>
<SplitChars
ref={charRef => div2.current.push(charRef)}
wrapper={<h3 style={{ display: 'inline-block' }} />}
>
TEST
</SplitChars>
<h3 ref={div3}>IS A</h3>
</div>
);
});

```

For version < 3:

If you need to target individual elements you can use a special forwardRef function.
The `targets` parameter provide the `set` function, which you can use to set a ref to a certain key.

Expand Down
42 changes: 29 additions & 13 deletions packages/docz/src/components/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
import React, { forwardRef, ReactElement } from 'react';
import React, {
forwardRef,
MutableRefObject,
ReactElement,
useImperativeHandle,
useRef,
} from 'react';
import Timeline, { TimelineProps } from './../../../react-gsap/src/Timeline';
import { SplitChars } from './../../../react-gsap/src';

export const TimelinePropsDummy: React.FunctionComponent<TimelineProps> = props => (
<div {...props} />
);

const TargetWithNames = forwardRef((props, targets: any) => (
<div style={{ textAlign: 'center' }}>
<h3 ref={div => targets.set('div1', div)}>THIS</h3>
<SplitChars
ref={(div: ReactElement) => targets.set('div2', [div])}
wrapper={<h3 style={{ display: 'inline-block' }} />}
>
TEST
</SplitChars>
<h3 ref={div => targets.set('div3', div)}>IS A</h3>
</div>
));
const TargetWithNames = forwardRef((props, ref: any) => {
const div1 = useRef(null);
const div2 = useRef<MutableRefObject<any>[]>([]);
const div3 = useRef(null);
useImperativeHandle(ref, () => ({
div1,
div2,
div3,
}));
return (
<div style={{ textAlign: 'center' }}>
<h3 ref={div1}>THIS</h3>
<SplitChars
ref={(charRef: MutableRefObject<any>) => div2.current.push(charRef)}
wrapper={<h3 style={{ display: 'inline-block' }} />}
>
TEST
</SplitChars>
<h3 ref={div3}>IS A</h3>
</div>
);
});

export { Timeline, TargetWithNames };
52 changes: 26 additions & 26 deletions packages/playground/src/examples/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, {
ReactHTMLElement,
useCallback,
useEffect,
MutableRefObject,
} from 'react';
import styled from 'styled-components';
import { Tween, Timeline, SplitWords, SplitChars, Controls, PlayState } from 'react-gsap';
Expand Down Expand Up @@ -123,18 +124,28 @@ const TimelineComponent = () => (
</TimelineStyled>
);

const TargetWithNames = forwardRef((props, targets: any) => (
<div>
<div ref={div => targets.set('div1', div)}>first</div>
<SplitChars
ref={(div: ReactElement) => targets.set('div2', [div])}
wrapper={<span style={{ display: 'inline-block' }} />}
>
second
</SplitChars>
<div ref={div => targets.set('div3', div)}>third</div>
</div>
));
const TargetWithNames = forwardRef((props, ref: any) => {
const div1 = useRef(null);
const div2 = useRef<MutableRefObject<any>[]>([]);
const div3 = useRef(null);
useImperativeHandle(ref, () => ({
div1,
div2,
div3,
}));
return (
<div>
<div ref={div1}>first</div>
<SplitChars
ref={(charRef: MutableRefObject<any>) => div2.current.push(charRef)}
wrapper={<span style={{ display: 'inline-block' }} />}
>
second
</SplitChars>
<div ref={div3}>third</div>
</div>
);
});

const TimelineTargets = () => {
return (
Expand All @@ -148,18 +159,7 @@ const TimelineTargets = () => {

//export default TimelineTargets;

const Component = forwardRef(({ children }, targets) => {
return (
<div>
<div ref={div => targets?.set && targets.set('div1', div)}>
<span>{children}</span>
</div>
<div ref={div => targets?.set && targets.set('div2', div)}>Div 2</div>
</div>
);
});

const Component2 = forwardRef(({ children }, ref?) => {
const Component = forwardRef(({ children }, ref?) => {
const div1 = useRef(null);
const div2 = useRef(null);
useImperativeHandle(ref, () => ({
Expand Down Expand Up @@ -235,9 +235,9 @@ const Out = () => {
<div ref={divRef2} style={{ width: 200, height: 200, background: 'fuchsia' }} />
</Tween>

<Tween to={{ x: '200px' }} target="div2" position="0" />
<Tween to={{ x: '200px' }} target="div3" position="0" />
<Tween to={{ x: '200px' }} target="div1" position="0.5" />
<Tween to={{ x: '200px' }} target="div3" position="1" stagger={0.1} />
<Tween to={{ x: '200px' }} target="div2" position="1" stagger={0.1} />
</Timeline>
</div>
);
Expand Down
8 changes: 5 additions & 3 deletions packages/react-gsap/src/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,13 @@ class Timeline extends Provider<TimelineProps> {
if (this.targets.has(key)) {
const targets = this.targets.get(key);
if (Array.isArray(targets)) {
this.targets.set(key, [...targets, ...target]);
return;
this.targets.set(key, [...targets, target]);
} else {
this.targets.set(key, [targets, target]);
}
} else {
this.targets.set(key, target);
}
this.targets.set(key, target);
}
}

Expand Down
38 changes: 20 additions & 18 deletions packages/react-gsap/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,24 +160,26 @@ const getRefProp = (child: any, addTarget: (target: any) => void) => {

const getTargetRefProp = (child: any, setTarget: (key: string, target: any) => void) => {
return {
// ref: (target: any) => {
// const { ref } = child;
//
// if (target) {
// Object.keys(target).forEach(key => {
// const elementRef = target[key];
// if (typeof elementRef === 'object' && elementRef.current) {
// setTarget(key, elementRef.current);
// }
// });
// }
//
// if (typeof ref === 'function') ref(target);
// else if (ref) ref.current = target;
// },
// Old version: Can we make it work for both variants?
ref: {
set: setTarget,
ref: (target: any) => {
const { ref } = child;

if (target) {
Object.keys(target).forEach(key => {
const elementRef = target[key];
if (typeof elementRef === 'object' && elementRef.current) {
if (Array.isArray(elementRef.current)) {
elementRef.current.forEach((singleRef: React.RefObject<any>) => {
setTarget(key, singleRef);
});
} else {
setTarget(key, elementRef.current);
}
}
});
}

if (typeof ref === 'function') ref(target);
else if (ref) ref.current = target;
},
};
};
Expand Down

0 comments on commit cf6f257

Please sign in to comment.