Skip to content
This repository was archived by the owner on Apr 14, 2020. It is now read-only.

Commit cb67a58

Browse files
committed
fix(baba): replaces usage of render subtree with create portal to pass new context through
1 parent 29625e2 commit cb67a58

4 files changed

Lines changed: 391 additions & 135 deletions

File tree

packages/yubaba/src/Baba/__snapshots__/test.tsx.snap

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,113 @@ Object {
115115
},
116116
}
117117
`;
118+
119+
exports[`<Baba /> should render markup in a portal created by multiple animations 1`] = `
120+
Array [
121+
<Portal
122+
containerInfo={
123+
<div>
124+
<div>
125+
hello world
126+
</div>
127+
</div>
128+
}
129+
>
130+
<div>
131+
hello world
132+
</div>
133+
</Portal>,
134+
<Portal
135+
containerInfo={
136+
<div>
137+
<span>
138+
number two
139+
</span>
140+
</div>
141+
}
142+
>
143+
<span>
144+
number two
145+
</span>
146+
</Portal>,
147+
]
148+
`;
149+
150+
exports[`<Baba /> should render markup in a portal created in an animation 1`] = `
151+
<Portal
152+
containerInfo={
153+
<div>
154+
<div>
155+
hello world
156+
</div>
157+
</div>
158+
}
159+
>
160+
<div>
161+
hello world
162+
</div>
163+
</Portal>
164+
`;
165+
166+
exports[`<Baba /> should update markup created in an animation in after animate phase 1`] = `
167+
<Portal
168+
containerInfo={
169+
<div>
170+
<div>
171+
after animate phase
172+
</div>
173+
</div>
174+
}
175+
>
176+
<div>
177+
after animate phase
178+
</div>
179+
</Portal>
180+
`;
181+
182+
exports[`<Baba /> should update markup created in an animation in animate phase 1`] = `
183+
<Portal
184+
containerInfo={
185+
<div>
186+
<div>
187+
animate phase
188+
</div>
189+
</div>
190+
}
191+
>
192+
<div>
193+
animate phase
194+
</div>
195+
</Portal>
196+
`;
197+
198+
exports[`<Baba /> should update markup in a portal created by multiple animations in animate phase 1`] = `
199+
Array [
200+
<Portal
201+
containerInfo={
202+
<div>
203+
<div>
204+
updated
205+
</div>
206+
</div>
207+
}
208+
>
209+
<div>
210+
updated
211+
</div>
212+
</Portal>,
213+
<Portal
214+
containerInfo={
215+
<div>
216+
<span>
217+
number two
218+
</span>
219+
</div>
220+
}
221+
>
222+
<span>
223+
number two
224+
</span>
225+
</Portal>,
226+
]
227+
`;

packages/yubaba/src/Baba/index.tsx

Lines changed: 100 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import * as React from 'react';
2-
import {
3-
unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer,
4-
unmountComponentAtNode,
5-
} from 'react-dom';
2+
import { createPortal } from 'react-dom';
63
import Collector, {
74
SupplyDataHandler,
85
SupplyRenderChildrenHandler,
@@ -14,6 +11,7 @@ import Collector, {
1411
InlineStyles,
1512
TargetPropsFunc,
1613
AnimationData,
14+
AnimationCallback,
1715
} from '../Collector';
1816
import { getElementBoundingBox } from '../lib/dom';
1917
import defer from '../lib/defer';
@@ -40,6 +38,7 @@ export interface ChildProps {
4038

4139
export interface State {
4240
childProps: ChildProps;
41+
animationsMarkup: React.ReactPortal[];
4342
}
4443

4544
export interface BabaProps extends CollectorChildrenProps, InjectedProps {
@@ -86,6 +85,7 @@ export default class Baba extends React.PureComponent<BabaProps, State> {
8685
};
8786

8887
state: State = {
88+
animationsMarkup: [],
8989
childProps: {},
9090
};
9191

@@ -253,121 +253,97 @@ If it's an image, try and have the image loaded before mounting, or set a static
253253
};
254254

255255
// Loads each action up in an easy-to-execute format.
256-
const actions = collectorData.map(targetData => {
257-
if (targetData.action === CollectorActions.animation) {
258-
// Element will be lazily instantiated if we need to add something to the DOM.
259-
let elementToMountChildren: HTMLElement | null = null;
260-
261-
const mount = (jsx: React.ReactNode) => {
262-
if (!elementToMountChildren) {
263-
elementToMountChildren = document.createElement('div');
264-
// We insert the new element at the beginning of the body to ensure correct
265-
// stacking context.
266-
container.insertBefore(elementToMountChildren, container.firstChild);
267-
}
256+
const actions = collectorData.map((targetData, index) => {
257+
if (targetData.action !== CollectorActions.animation) {
258+
return targetData;
259+
}
260+
261+
// Element will be lazily instantiated if we need to add something to the DOM.
262+
let elementToMountChildren: HTMLElement | null = null;
263+
264+
const mount = (jsx: React.ReactNode) => {
265+
if (!elementToMountChildren) {
266+
elementToMountChildren = document.createElement('div');
267+
// We insert the new element at the beginning of the body to ensure correct stacking context.
268+
container.insertBefore(elementToMountChildren, container.firstChild);
269+
}
268270

269-
// This ensures that if there was an update to the jsx that is animating,
270-
// it changes next frame. Resulting in the transition _actually_ happening.
271-
requestAnimationFrame(
272-
() =>
273-
elementToMountChildren &&
274-
renderSubtreeIntoContainer(
275-
this,
276-
jsx as React.ReactElement<{}>,
277-
elementToMountChildren
278-
)
279-
);
280-
};
281-
282-
const unmount = () => {
271+
// This ensures that if there was an update to the jsx that is animating it changes next frame.
272+
// Resulting in the transition _actually_ happening.
273+
requestAnimationFrame(() => {
283274
if (elementToMountChildren) {
284-
unmountComponentAtNode(elementToMountChildren);
285-
container.removeChild(elementToMountChildren);
286-
elementToMountChildren = null;
287-
}
288-
};
289-
290-
const setChildProps = (props: TargetPropsFunc | null) => {
291-
if (props) {
292-
this.setState(prevState => ({
293-
childProps: {
294-
style: props.style
295-
? props.style(prevState.childProps.style || {})
296-
: prevState.childProps.style,
297-
className: props.className
298-
? props.className(prevState.childProps.className)
299-
: prevState.childProps.className,
300-
},
301-
}));
302-
} else {
303-
this.setState({
304-
childProps: {},
275+
this.setState(prevState => {
276+
const newAnimationsMarkup = prevState.animationsMarkup.concat();
277+
newAnimationsMarkup[index] = createPortal(jsx, elementToMountChildren!);
278+
return {
279+
animationsMarkup: newAnimationsMarkup,
280+
};
305281
});
306282
}
307-
};
308-
309-
return {
310-
action: CollectorActions.animation,
311-
payload: {
312-
beforeAnimate: () => {
313-
if (targetData.payload.beforeAnimate) {
314-
const deferred = defer();
315-
const jsx = targetData.payload.beforeAnimate(
316-
animationData,
317-
deferred.resolve,
318-
setChildProps
319-
);
320-
321-
if (jsx) {
322-
mount(jsx);
323-
}
324-
325-
return deferred.promise;
326-
}
283+
});
284+
};
285+
286+
const setChildProps = (props: TargetPropsFunc | null) => {
287+
if (this.unmounting) {
288+
return;
289+
}
327290

328-
return Promise.resolve();
291+
if (props) {
292+
this.setState(prevState => ({
293+
childProps: {
294+
style: props.style
295+
? props.style(prevState.childProps.style || {})
296+
: prevState.childProps.style,
297+
className: props.className
298+
? props.className(prevState.childProps.className)
299+
: prevState.childProps.className,
329300
},
330-
animate: () => {
331-
const deferred = defer();
332-
const jsx = targetData.payload.animate(
333-
animationData,
334-
deferred.resolve,
335-
setChildProps
336-
);
301+
}));
302+
} else {
303+
this.setState({
304+
childProps: {},
305+
});
306+
}
307+
};
337308

338-
if (jsx) {
339-
mount(jsx);
340-
}
309+
const generatePhase = (cb: AnimationCallback | undefined) => () => {
310+
if (cb) {
311+
const deferred = defer();
312+
const jsx = cb(animationData, deferred.resolve, setChildProps);
341313

342-
return deferred.promise;
343-
},
344-
afterAnimate: () => {
345-
if (targetData.payload.afterAnimate) {
346-
const deferred = defer();
347-
const jsx = targetData.payload.afterAnimate(
348-
animationData,
349-
deferred.resolve,
350-
setChildProps
351-
);
352-
353-
if (jsx) {
354-
mount(jsx);
355-
}
356-
357-
return deferred.promise;
358-
}
314+
if (jsx) {
315+
mount(jsx);
316+
}
359317

360-
return Promise.resolve();
361-
},
362-
cleanup: () => {
363-
unmount();
364-
setChildProps(null);
365-
},
366-
},
367-
};
368-
}
318+
return deferred.promise;
319+
}
369320

370-
return targetData;
321+
return Promise.resolve();
322+
};
323+
324+
return {
325+
action: CollectorActions.animation,
326+
payload: {
327+
beforeAnimate: generatePhase(targetData.payload.beforeAnimate),
328+
animate: generatePhase(targetData.payload.animate),
329+
afterAnimate: generatePhase(targetData.payload.afterAnimate),
330+
cleanup: () => {
331+
if (elementToMountChildren && container.contains(elementToMountChildren)) {
332+
container.removeChild(elementToMountChildren);
333+
}
334+
335+
if (this.unmounting) {
336+
return;
337+
}
338+
339+
this.setState({
340+
animationsMarkup: [],
341+
});
342+
343+
setChildProps(null);
344+
},
345+
},
346+
};
371347
});
372348

373349
const blocks = actions.reduce<AnimationBlock[]>(
@@ -476,21 +452,24 @@ If it's an image, try and have the image loaded before mounting, or set a static
476452
};
477453

478454
render() {
479-
const { childProps } = this.state;
455+
const { childProps, animationsMarkup } = this.state;
480456
const { children } = this.props;
481457

482458
return (
483-
<Collector
484-
topMostCollector
485-
receiveData={this.setData}
486-
receiveRenderChildren={this.setReactNode}
487-
receiveRef={this.setRef}
488-
receiveFocalTargetRef={this.setTargetRef}
489-
style={childProps.style}
490-
className={childProps.className}
491-
>
492-
{typeof children === 'function' ? children : React.Children.only(children)}
493-
</Collector>
459+
<React.Fragment>
460+
{animationsMarkup}
461+
<Collector
462+
topMostCollector
463+
receiveData={this.setData}
464+
receiveRenderChildren={this.setReactNode}
465+
receiveRef={this.setRef}
466+
receiveFocalTargetRef={this.setTargetRef}
467+
style={childProps.style}
468+
className={childProps.className}
469+
>
470+
{typeof children === 'function' ? children : React.Children.only(children)}
471+
</Collector>
472+
</React.Fragment>
494473
);
495474
}
496475
}

0 commit comments

Comments
 (0)