Skip to content

Fix getRectForRef - current not set error #193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
dist/

# OSX
#
.DS_Store
Expand Down Expand Up @@ -34,4 +32,7 @@ npm-debug.log

# yarn
#
/.yarn/install-state.gz
/.yarn/install-state.gz

# Jetbrains
.idea
142 changes: 114 additions & 28 deletions src/AdaptivePopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class AdaptivePopover extends Component<AdaptivePopoverProps, Ada
defaultDisplayArea: null,
displayAreaOffset: null,
showing: false
}
};

getUnshiftedDisplayArea(): Rect {
return this.props.displayArea ||
Expand Down Expand Up @@ -117,7 +117,8 @@ export default class AdaptivePopover extends Component<AdaptivePopoverProps, Ada
changedProps.includes('displayArea') ||
(
this.displayAreaStore &&
!this.getDisplayArea().equals(this.displayAreaStore)
!this.getDisplayArea().
equals(this.displayAreaStore)
)
) {
this.debug('componentDidUpdate - displayArea changed', this.getDisplayArea());
Expand Down Expand Up @@ -161,7 +162,10 @@ export default class AdaptivePopover extends Component<AdaptivePopoverProps, Ada
this.debug('setDefaultDisplayArea - displayAreaOffset', displayAreaOffset);
await new Promise(resolve => {
this.setState(
{ defaultDisplayArea: newDisplayArea, displayAreaOffset },
{
defaultDisplayArea: newDisplayArea,
displayAreaOffset
},
() => resolve(null)
);
});
Expand Down Expand Up @@ -210,16 +214,39 @@ export default class AdaptivePopover extends Component<AdaptivePopoverProps, Ada
async calculateRectFromRef(): Promise<void> {
const { fromRef }: Partial<AdaptivePopoverProps> = this.props;
const initialRect = this.state.fromRect || new Rect(0, 0, 0, 0);
const displayAreaOffset = this.state.displayAreaOffset ?? { x: 0, y: 0 };
const displayAreaOffset = this.state.displayAreaOffset ?? {
x: 0,
y: 0
};

this.debug('calculateRectFromRef - waiting for ref');

// If no fromRef is provided, exit early
if (!fromRef) {
this.debug('calculateRectFromRef - no fromRef provided');
return;
}

let count = 0;
while (!fromRef?.current) {
// Wait for ref.current to be available, but don't block forever
while (!fromRef.current) {
await new Promise(resolve => {
setTimeout(resolve, 100);
});
// Timeout after 2 seconds
if (count++ > 20) return;
if (count++ > 20) {
this.debug('calculateRectFromRef - timed out waiting for ref.current');
// If we can't get a ref after waiting, use a default rect in the center of the screen
if (this._isMounted) {
const {
width,
height
} = Dimensions.get('window');
const defaultRect = new Rect(width / 2, height / 2, 0, 0);
this.setState({ fromRect: defaultRect });
}
return;
}
}

const shouldAdjustForAndroidStatusBar =
Expand All @@ -233,42 +260,101 @@ export default class AdaptivePopover extends Component<AdaptivePopoverProps, Ada
const horizontalOffset = -displayAreaOffset.x;

this.debug('calculateRectFromRef - waiting for ref to move from', initialRect);
let rect: Rect;
let rect: Rect | undefined;
count = 0;
do {
rect = await getRectForRef(fromRef);
if ([rect.x, rect.y, rect.width, rect.height].every(i => i === undefined)) {
this.debug('calculateRectFromRef - rect not found, all properties undefined');
return;
}
rect = new Rect(rect.x + horizontalOffset, rect.y + verticalOffset, rect.width, rect.height);

if (count === 0 && AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect)) {
break;
}
try {
do {
try {
rect = await getRectForRef(fromRef);

// Check if we got a valid rect
if (rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0) {
// This is our default rect from getRectForRef, meaning the ref wasn't ready
this.debug('calculateRectFromRef - got default rect, waiting for valid rect');
await new Promise(resolve => setTimeout(resolve, 100));
if (count++ > 20) {
// If we can't get a valid rect after waiting, use a default rect
this.debug('calculateRectFromRef - timed out waiting for valid rect');
if (this._isMounted) {
const {
width,
height
} = Dimensions.get('window');
rect = new Rect(width / 2, height / 2, 0, 0);
this.setState({ fromRect: rect });
}
return;
}
continue;
}

await new Promise(resolve => {
setTimeout(resolve, 100);
});
// Timeout after 2 seconds
if (count++ > 20) return;
if ([rect.x, rect.y, rect.width, rect.height].every(i => i === undefined)) {
this.debug('calculateRectFromRef - rect not found, all properties undefined');
return;
}

rect = new Rect(rect.x + horizontalOffset, rect.y + verticalOffset, rect.width, rect.height);

if (count === 0 && AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect)) {
break;
}

} while (!AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect));
await new Promise(resolve => {
setTimeout(resolve, 100);
});
// Timeout after 2 seconds
if (count++ > 20) {
this.debug('calculateRectFromRef - timed out waiting for satisfying rect');
break;
}

this.debug('calculateRectFromRef - calculated Rect', rect);
if (this._isMounted) this.setState({ fromRect: rect });
} catch (error) {
this.debug(`calculateRectFromRef - error getting rect: ${error}`);
// If we encounter an error, wait a bit and try again
await new Promise(resolve => setTimeout(resolve, 100));
if (count++ > 20) {
this.debug('calculateRectFromRef - timed out after errors');
return;
}
}
} while (!rect || !AdaptivePopover.hasRetrievedSatisfyingRect(rect, initialRect));

this.debug('calculateRectFromRef - calculated Rect', rect);
if (this._isMounted) this.setState({ fromRect: rect });
} catch (error) {
this.debug(`calculateRectFromRef - unexpected error: ${error}`);
// If we encounter an unexpected error, use a default rect in the center of the screen
if (this._isMounted) {
const {
width,
height
} = Dimensions.get('window');
const defaultRect = new Rect(width / 2, height / 2, 0, 0);
this.setState({ fromRect: defaultRect });
}
}
}

static hasRetrievedSatisfyingRect = (rect: Rect, initialRect: Rect): boolean =>
/*
* Checking if x and y is less than -1000 because of a strange issue on Android related
* to the "Toggle from" feature, where the rect.y is a large negative number at first
*/
!(rect.equals(initialRect) || rect.y < -1000 || rect.x < -1000)
!(rect.equals(initialRect) || rect.y < -1000 || rect.x < -1000);

render(): ReactNode {
const { onOpenStart, onCloseStart, onCloseComplete, fromRef, ...otherProps } = this.props;
const { fromRect, showing } = this.state;
const {
onOpenStart,
onCloseStart,
onCloseComplete,
fromRef,
...otherProps
} = this.props;
const {
fromRect,
showing
} = this.state;

// Don't render popover until we have an initial fromRect calculated for the view
if (fromRef && !fromRect && !showing) return null;
Expand Down
70 changes: 51 additions & 19 deletions src/Utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ type RefType = RefObject<{
export function getRectForRef(ref: RefType): Promise<Rect> {
return new Promise((resolve, reject) => {
if (ref.current) {
ref.current.measureInWindow(
(x: number, y: number, width: number, height: number) =>
resolve(new Rect(x, y, width, height))
);
try {
ref.current.measureInWindow(
(x: number, y: number, width: number, height: number) =>
resolve(new Rect(x, y, width, height))
);
} catch (error) {
// If measureInWindow fails, return a default rect
console.warn('getRectForRef - measureInWindow failed:', error);
resolve(new Rect(0, 0, 0, 0));
}
} else {
reject(new Error('getRectForRef - current is not set'));
// Instead of rejecting, resolve with a default rect
console.warn('getRectForRef - current is not set');
resolve(new Rect(0, 0, 0, 0));
}
});
}
Expand All @@ -27,23 +35,47 @@ export async function waitForChange(
// Failsafe so that the interval doesn't run forever
let count = 0;
let first, second;
do {
first = await getFirst();
second = await getSecond();
await new Promise(resolve => {
setTimeout(resolve, 100);
});
count++;
if (count++ > 20) {
throw new Error('waitForChange - Timed out waiting for change (waited 2 seconds)');
}
} while (first.equals(second));
try {
do {
try {
first = await getFirst();
second = await getSecond();
} catch (error) {
console.warn('waitForChange - error getting rects:', error);
await new Promise(resolve => {
setTimeout(resolve, 100);
});
count++;
if (count > 20) {
console.warn('waitForChange - Timed out waiting for valid rects (waited 2 seconds)');
return;
}
continue;
}

await new Promise(resolve => {
setTimeout(resolve, 100);
});
count++;
if (count > 20) {
console.warn('waitForChange - Timed out waiting for change (waited 2 seconds)');
return;
}
} while (first && second && first.equals(second));
} catch (error) {
console.warn('waitForChange - unexpected error:', error);
}
}

export async function waitForNewRect(ref: RefType, initialRect: Rect): Promise<Rect> {
await waitForChange(() => getRectForRef(ref), () => Promise.resolve(initialRect));
const rect = await getRectForRef(ref);
return rect;
try {
await waitForChange(() => getRectForRef(ref), () => Promise.resolve(initialRect));
const rect = await getRectForRef(ref);
return rect;
} catch (error) {
console.warn('waitForNewRect - error:', error);
return initialRect; // Return the initial rect if there's an error
}
}

export function sizeChanged(a: Size | null, b: Size | null): boolean {
Expand Down