Skip to content

Commit cf6f257

Browse files
author
Jan Fischer
committed
feat: use forwardRef with useImperativeHandle for Timeline target
1 parent aee8eb3 commit cf6f257

File tree

5 files changed

+114
-60
lines changed

5 files changed

+114
-60
lines changed

packages/docz/src/components/Timeline.mdx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,40 @@ If you don't add a target you transform all target components.
8080

8181
## Advanced multiple targets
8282

83+
If you need to target individual elements you can use a special forwardRef component with useImperativeHandle hook.
84+
85+
In this way these component can be better reused and the refs not only work in a Timeline target context.
86+
87+
You can also pass an array ref like seen with div2. In this way you can use the `stagger` prop.
88+
89+
```javascript
90+
const TargetWithNames = forwardRef((props, ref: any) => {
91+
const div1 = useRef(null);
92+
const div2 = useRef([]);
93+
const div3 = useRef(null);
94+
useImperativeHandle(ref, () => ({
95+
div1,
96+
div2,
97+
div3,
98+
}));
99+
return (
100+
<div style={{ textAlign: 'center' }}>
101+
<h3 ref={div1}>THIS</h3>
102+
<SplitChars
103+
ref={charRef => div2.current.push(charRef)}
104+
wrapper={<h3 style={{ display: 'inline-block' }} />}
105+
>
106+
TEST
107+
</SplitChars>
108+
<h3 ref={div3}>IS A</h3>
109+
</div>
110+
);
111+
});
112+
113+
```
114+
115+
For version < 3:
116+
83117
If you need to target individual elements you can use a special forwardRef function.
84118
The `targets` parameter provide the `set` function, which you can use to set a ref to a certain key.
85119

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
1-
import React, { forwardRef, ReactElement } from 'react';
1+
import React, {
2+
forwardRef,
3+
MutableRefObject,
4+
ReactElement,
5+
useImperativeHandle,
6+
useRef,
7+
} from 'react';
28
import Timeline, { TimelineProps } from './../../../react-gsap/src/Timeline';
39
import { SplitChars } from './../../../react-gsap/src';
410

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

9-
const TargetWithNames = forwardRef((props, targets: any) => (
10-
<div style={{ textAlign: 'center' }}>
11-
<h3 ref={div => targets.set('div1', div)}>THIS</h3>
12-
<SplitChars
13-
ref={(div: ReactElement) => targets.set('div2', [div])}
14-
wrapper={<h3 style={{ display: 'inline-block' }} />}
15-
>
16-
TEST
17-
</SplitChars>
18-
<h3 ref={div => targets.set('div3', div)}>IS A</h3>
19-
</div>
20-
));
15+
const TargetWithNames = forwardRef((props, ref: any) => {
16+
const div1 = useRef(null);
17+
const div2 = useRef<MutableRefObject<any>[]>([]);
18+
const div3 = useRef(null);
19+
useImperativeHandle(ref, () => ({
20+
div1,
21+
div2,
22+
div3,
23+
}));
24+
return (
25+
<div style={{ textAlign: 'center' }}>
26+
<h3 ref={div1}>THIS</h3>
27+
<SplitChars
28+
ref={(charRef: MutableRefObject<any>) => div2.current.push(charRef)}
29+
wrapper={<h3 style={{ display: 'inline-block' }} />}
30+
>
31+
TEST
32+
</SplitChars>
33+
<h3 ref={div3}>IS A</h3>
34+
</div>
35+
);
36+
});
2137

2238
export { Timeline, TargetWithNames };

packages/playground/src/examples/Timeline.tsx

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import React, {
77
ReactHTMLElement,
88
useCallback,
99
useEffect,
10+
MutableRefObject,
1011
} from 'react';
1112
import styled from 'styled-components';
1213
import { Tween, Timeline, SplitWords, SplitChars, Controls, PlayState } from 'react-gsap';
@@ -123,18 +124,28 @@ const TimelineComponent = () => (
123124
</TimelineStyled>
124125
);
125126

126-
const TargetWithNames = forwardRef((props, targets: any) => (
127-
<div>
128-
<div ref={div => targets.set('div1', div)}>first</div>
129-
<SplitChars
130-
ref={(div: ReactElement) => targets.set('div2', [div])}
131-
wrapper={<span style={{ display: 'inline-block' }} />}
132-
>
133-
second
134-
</SplitChars>
135-
<div ref={div => targets.set('div3', div)}>third</div>
136-
</div>
137-
));
127+
const TargetWithNames = forwardRef((props, ref: any) => {
128+
const div1 = useRef(null);
129+
const div2 = useRef<MutableRefObject<any>[]>([]);
130+
const div3 = useRef(null);
131+
useImperativeHandle(ref, () => ({
132+
div1,
133+
div2,
134+
div3,
135+
}));
136+
return (
137+
<div>
138+
<div ref={div1}>first</div>
139+
<SplitChars
140+
ref={(charRef: MutableRefObject<any>) => div2.current.push(charRef)}
141+
wrapper={<span style={{ display: 'inline-block' }} />}
142+
>
143+
second
144+
</SplitChars>
145+
<div ref={div3}>third</div>
146+
</div>
147+
);
148+
});
138149

139150
const TimelineTargets = () => {
140151
return (
@@ -148,18 +159,7 @@ const TimelineTargets = () => {
148159

149160
//export default TimelineTargets;
150161

151-
const Component = forwardRef(({ children }, targets) => {
152-
return (
153-
<div>
154-
<div ref={div => targets?.set && targets.set('div1', div)}>
155-
<span>{children}</span>
156-
</div>
157-
<div ref={div => targets?.set && targets.set('div2', div)}>Div 2</div>
158-
</div>
159-
);
160-
});
161-
162-
const Component2 = forwardRef(({ children }, ref?) => {
162+
const Component = forwardRef(({ children }, ref?) => {
163163
const div1 = useRef(null);
164164
const div2 = useRef(null);
165165
useImperativeHandle(ref, () => ({
@@ -235,9 +235,9 @@ const Out = () => {
235235
<div ref={divRef2} style={{ width: 200, height: 200, background: 'fuchsia' }} />
236236
</Tween>
237237

238-
<Tween to={{ x: '200px' }} target="div2" position="0" />
238+
<Tween to={{ x: '200px' }} target="div3" position="0" />
239239
<Tween to={{ x: '200px' }} target="div1" position="0.5" />
240-
<Tween to={{ x: '200px' }} target="div3" position="1" stagger={0.1} />
240+
<Tween to={{ x: '200px' }} target="div2" position="1" stagger={0.1} />
241241
</Timeline>
242242
</div>
243243
);

packages/react-gsap/src/Timeline.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,13 @@ class Timeline extends Provider<TimelineProps> {
185185
if (this.targets.has(key)) {
186186
const targets = this.targets.get(key);
187187
if (Array.isArray(targets)) {
188-
this.targets.set(key, [...targets, ...target]);
189-
return;
188+
this.targets.set(key, [...targets, target]);
189+
} else {
190+
this.targets.set(key, [targets, target]);
190191
}
192+
} else {
193+
this.targets.set(key, target);
191194
}
192-
this.targets.set(key, target);
193195
}
194196
}
195197

packages/react-gsap/src/helper.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -160,24 +160,26 @@ const getRefProp = (child: any, addTarget: (target: any) => void) => {
160160

161161
const getTargetRefProp = (child: any, setTarget: (key: string, target: any) => void) => {
162162
return {
163-
// ref: (target: any) => {
164-
// const { ref } = child;
165-
//
166-
// if (target) {
167-
// Object.keys(target).forEach(key => {
168-
// const elementRef = target[key];
169-
// if (typeof elementRef === 'object' && elementRef.current) {
170-
// setTarget(key, elementRef.current);
171-
// }
172-
// });
173-
// }
174-
//
175-
// if (typeof ref === 'function') ref(target);
176-
// else if (ref) ref.current = target;
177-
// },
178-
// Old version: Can we make it work for both variants?
179-
ref: {
180-
set: setTarget,
163+
ref: (target: any) => {
164+
const { ref } = child;
165+
166+
if (target) {
167+
Object.keys(target).forEach(key => {
168+
const elementRef = target[key];
169+
if (typeof elementRef === 'object' && elementRef.current) {
170+
if (Array.isArray(elementRef.current)) {
171+
elementRef.current.forEach((singleRef: React.RefObject<any>) => {
172+
setTarget(key, singleRef);
173+
});
174+
} else {
175+
setTarget(key, elementRef.current);
176+
}
177+
}
178+
});
179+
}
180+
181+
if (typeof ref === 'function') ref(target);
182+
else if (ref) ref.current = target;
181183
},
182184
};
183185
};

0 commit comments

Comments
 (0)