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

Commit ed7a332

Browse files
author
Michael Dougall
committed
feat(motion): adds dev logging to improve dx
1 parent 9e68258 commit ed7a332

5 files changed

Lines changed: 205 additions & 13 deletions

File tree

packages/docs/src/5-troubleshooting/docs.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,26 @@ Double check your [Motion](/motion) elements.
3131
```
3232

3333
> ** Tip -** Can't find what you're looking for? [Raise an issue on Github](https://github.com/madou/element-motion/issues/new).
34+
35+
## Styled Components
36+
37+
Using v3? Make sure to use `innerRef`,
38+
like so:
39+
40+
```js
41+
import styled from 'styled-components';
42+
43+
const StyledDiv = styled.div``;
44+
45+
<Motion>{({ ref, ...motion }) => <StyledDiv innerRef={ref} {...motion} />}</Motion>;
46+
```
47+
48+
Using v4? Just use `ref` thanks to `forwardRef()`!
49+
50+
```js
51+
import styled from 'styled-components';
52+
53+
const StyledDiv = styled.div``;
54+
55+
<Motion>{motion => <StyledDiv {...motion} />}</Motion>;
56+
```

packages/utils/.size-limit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
module.exports = [
22
{
3-
limit: '3.84 KB',
3+
limit: '3.86 KB',
44
path: 'dist/esm/index.js',
55
},
66
{
7-
limit: '2.72 KB',
7+
limit: '2.73 KB',
88
path: 'dist/esm/Motion/index.js',
99
},
1010
{

packages/utils/src/Motion/__snapshots__/test.tsx.snap

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,56 @@ exports[`<Motion /> should render markup in a portal created in a motion 1`] = `
185185
</Portal>
186186
`;
187187

188+
exports[`<Motion /> should throw destination ref is not found 1`] = `
189+
"@element-motion
190+
191+
The destination ref was not set when trying to store data, check that a child element has a ref passed.
192+
This needs to be set so we can take a snapshot of the origin DOM element!
193+
Try setting \\"innerRef\\" or \\"ref\\" depending if the component is using React.forwardRef().
194+
195+
<Motion name=\\"throw-no-ref-dest\\">
196+
{props => <div ref={props.ref} />}
197+
</Motion>
198+
"
199+
`;
200+
201+
exports[`<Motion /> should throw if destination ref is not a DOM element 1`] = `
202+
"@element-motion
203+
204+
The destination ref was not a DOM element - double check your ref to make sure it's being passed to a real DOM element!
205+
You might need to use \\"innerRef\\" if the component isn't using React.forwardRef().
206+
207+
<Motion name=\\"throw-non-ref-dest\\">
208+
{props => <div ref={props.ref} />}
209+
</Motion>
210+
"
211+
`;
212+
213+
exports[`<Motion /> should throw if origin ref is not a DOM element 1`] = `
214+
"@element-motion
215+
216+
The origin ref was not a DOM element - double check your ref to make sure it's being passed to a real DOM element!
217+
You might need to use \\"innerRef\\" if the component isn't using React.forwardRef().
218+
219+
<Motion name=\\"throw-non-ref-origin\\">
220+
{props => <div ref={props.ref} />}
221+
</Motion>
222+
"
223+
`;
224+
225+
exports[`<Motion /> should throw origin ref is not found 1`] = `
226+
"@element-motion
227+
228+
The origin ref was not set when trying to store data, check that a child element has a ref passed.
229+
This needs to be set so we can take a snapshot of the origin DOM element!
230+
Try setting \\"innerRef\\" or \\"ref\\" depending if the component is using React.forwardRef().
231+
232+
<Motion name=\\"throw-no-ref-origin\\">
233+
{props => <div ref={props.ref} />}
234+
</Motion>
235+
"
236+
`;
237+
188238
exports[`<Motion /> should update markup created in a motion in after animate phase 1`] = `
189239
<Portal
190240
containerInfo={

packages/utils/src/Motion/index.tsx

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,17 @@ export default class Motion extends React.PureComponent<MotionProps, MotionState
5757
);
5858
}
5959

60-
if (this.element && componentIn === undefined && store.has(name)) {
60+
if (store.has(name) && componentIn === undefined) {
61+
if (process.env.NODE_ENV === 'development') {
62+
this.throwIfElementUndefinedOrNotDOMElement(this.element, 'destination');
63+
}
64+
6165
// A child has already been stored, so this is probably the matching pair.
62-
if (this.element.tagName === 'IMG' && !(this.element as HTMLImageElement).complete) {
66+
if (
67+
this.element &&
68+
this.element.tagName === 'IMG' &&
69+
!(this.element as HTMLImageElement).complete
70+
) {
6371
const remove = eventListener(this.element, 'load', () => {
6472
remove();
6573
this.execute();
@@ -169,6 +177,34 @@ export default class Motion extends React.PureComponent<MotionProps, MotionState
169177
setTimeout(() => store.remove(name), timeToWaitForNext);
170178
}
171179

180+
throwIfElementUndefinedOrNotDOMElement(element: HTMLElement | null, location: string) {
181+
if (process.env.NODE_ENV === 'development') {
182+
const buildMessage = (msg: string) => `${msg}
183+
184+
<${Motion.displayName} name="${this.props.name}">
185+
{props => <div ref={props.ref} />}
186+
</${Motion.displayName}>
187+
`;
188+
189+
throwIf(
190+
!element,
191+
buildMessage(
192+
`The ${location} ref was not set when trying to store data, check that a child element has a ref passed.
193+
This needs to be set so we can take a snapshot of the origin DOM element!
194+
Try setting "innerRef" or "ref" depending if the component is using React.forwardRef().`
195+
)
196+
);
197+
198+
throwIf(
199+
!(element instanceof HTMLElement),
200+
buildMessage(
201+
`The ${location} ref was not a DOM element - double check your ref to make sure it's being passed to a real DOM element!
202+
You might need to use "innerRef" if the component isn't using React.forwardRef().`
203+
)
204+
);
205+
}
206+
}
207+
172208
snapshotDOMData(action: 'store' | 'return' = 'store'): store.MotionData | null {
173209
// If there is only a Motion target and no child motions
174210
// data will be undefined, which means there are no motions to store.
@@ -177,15 +213,7 @@ export default class Motion extends React.PureComponent<MotionProps, MotionState
177213
}
178214

179215
if (process.env.NODE_ENV === 'development') {
180-
throwIf(
181-
!this.element,
182-
`The ref was not set when trying to store data, check that a child element has a ref passed. This needs to be set so we can take a snapshot of the origin DOM element.
183-
184-
<${Motion.displayName} name="${this.props.name}">
185-
{props => <div ref={props.ref} />}
186-
</${Motion.displayName}>
187-
`
188-
);
216+
this.throwIfElementUndefinedOrNotDOMElement(this.element, 'origin');
189217
}
190218

191219
const elementBoundingBox = getElementBoundingBox(this.element as HTMLElement);
@@ -230,6 +258,10 @@ If it's an image, try and have the image loaded before mounting or set a static
230258
const container = typeof getContainer === 'function' ? getContainer() : getContainer;
231259
let aborted = false;
232260

261+
if (process.env.NODE_ENV === 'development') {
262+
this.throwIfElementUndefinedOrNotDOMElement(this.element, 'destination');
263+
}
264+
233265
if (DOMSnapshot) {
234266
const { collectorData, elementData } = DOMSnapshot;
235267
this.executing = true;

packages/utils/src/Motion/test.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable react/no-multi-comp */
12
import * as React from 'react';
23
import { mount, ReactWrapper, shallow } from 'enzyme'; // eslint-disable-line import/no-extraneous-dependencies
34
import { MemoryRouter, Link } from 'react-router-dom';
@@ -486,4 +487,90 @@ describe('<Motion />', () => {
486487
});
487488
});
488489
});
490+
491+
it('should throw if origin ref is not a DOM element', () => {
492+
process.env.NODE_ENV = 'development';
493+
class ReactComponent extends React.Component {
494+
render() {
495+
return <div />;
496+
}
497+
}
498+
const TestMotion = utils.createTestMotion();
499+
const wrapper = mount(
500+
<utils.MotionUnderTest
501+
from={start => (
502+
<Motion name="throw-non-ref-origin" in={!start}>
503+
<TestMotion>{(props: {}) => <ReactComponent {...props} />}</TestMotion>
504+
</Motion>
505+
)}
506+
to={<Motion name="throw-non-ref-origin">{(props: {}) => <div {...props} />}</Motion>}
507+
start={false}
508+
/>
509+
);
510+
511+
expect(() => wrapper.setProps({ start: true })).toThrowErrorMatchingSnapshot();
512+
});
513+
514+
it('should throw if destination ref is not a DOM element', () => {
515+
process.env.NODE_ENV = 'development';
516+
(getElementBoundingBox as jest.Mock).mockReturnValue(utils.domData());
517+
class ReactComponent extends React.Component {
518+
render() {
519+
return <div />;
520+
}
521+
}
522+
const TestMotion = utils.createTestMotion();
523+
const wrapper = mount(
524+
<utils.MotionUnderTest
525+
from={start => (
526+
<Motion name="throw-non-ref-dest" in={!start}>
527+
<TestMotion>{(props: {}) => <div {...props} />}</TestMotion>
528+
</Motion>
529+
)}
530+
to={
531+
<Motion name="throw-non-ref-dest">{(props: {}) => <ReactComponent {...props} />}</Motion>
532+
}
533+
start={false}
534+
/>
535+
);
536+
537+
expect(() => wrapper.setProps({ start: true })).toThrowErrorMatchingSnapshot();
538+
});
539+
540+
it('should throw origin ref is not found', () => {
541+
process.env.NODE_ENV = 'development';
542+
const TestMotion = utils.createTestMotion();
543+
const wrapper = mount(
544+
<utils.MotionUnderTest
545+
from={start => (
546+
<Motion name="throw-no-ref-origin" in={!start}>
547+
<TestMotion>{() => <div />}</TestMotion>
548+
</Motion>
549+
)}
550+
to={<Motion name="throw-no-ref-origin">{props => <div {...props} />}</Motion>}
551+
start={false}
552+
/>
553+
);
554+
555+
expect(() => wrapper.setProps({ start: true })).toThrowErrorMatchingSnapshot();
556+
});
557+
558+
it('should throw destination ref is not found', () => {
559+
process.env.NODE_ENV = 'development';
560+
(getElementBoundingBox as jest.Mock).mockReturnValue(utils.domData());
561+
const TestMotion = utils.createTestMotion();
562+
const wrapper = mount(
563+
<utils.MotionUnderTest
564+
from={start => (
565+
<Motion name="throw-no-ref-dest" in={!start}>
566+
<TestMotion>{props => <div {...props} />}</TestMotion>
567+
</Motion>
568+
)}
569+
to={<Motion name="throw-no-ref-dest">{() => <div />}</Motion>}
570+
start={false}
571+
/>
572+
);
573+
574+
expect(() => wrapper.setProps({ start: true })).toThrowErrorMatchingSnapshot();
575+
});
489576
});

0 commit comments

Comments
 (0)