Created
February 23, 2024 07:18
-
-
Save lunaleaps/148756563999c83220887757f2e549a3 to your computer and use it in GitHub Desktop.
Synchronous measurement and layout updates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import {Pressable, Text, View} from 'react-native'; | |
function calculateX(toolTip, target, rootView) { | |
// Try and center the tooltip | |
let toolTipX = target.x + target.width / 2 - toolTip.width / 2; | |
// If the tooltip is outside of root, we need to move it | |
if (toolTipX < rootView.x) { | |
toolTipX = target.x; | |
} | |
if (toolTipX + toolTip.width > rootView.x + rootView.width) { | |
toolTipX = rootView.x + rootView.width - toolTip.width; | |
} | |
return toolTipX - rootView.x; | |
} | |
function calculateY(toolTip, target, rootView) { | |
// Try and place tooltip above | |
let toolTipY = target.y - toolTip.height; | |
// If the tooltip is outside of root, we need to move it below | |
if (toolTipY < rootView.y) { | |
toolTipY = target.y + target.height; | |
} | |
return toolTipY - rootView.y; | |
} | |
function wait(ms) { | |
const end = Date.now() + ms; | |
while (Date.now() < end); | |
} | |
function ToolTip({position, targetRect, rootRect, children}) { | |
const ref = React.useRef(null); | |
const [rect, setRect] = React.useState(null); | |
React.useLayoutEffect(() => { | |
// Place an artificial delay to illustrate synchronous update | |
wait(200); | |
setRect(ref.current?.getBoundingClientRect()); | |
}, [setRect, position]); | |
let left = 0; | |
let top = 0; | |
if (rect != null && targetRect != null && rootRect != null) { | |
left = calculateX(rect, targetRect, rootRect); | |
top = calculateY(rect, targetRect, rootRect); | |
} | |
return ( | |
<View | |
ref={ref} | |
style={{ | |
position: 'absolute', | |
borderColor: 'green', | |
borderRadius: 8, | |
borderWidth: 2, | |
padding: 4, | |
top, | |
left, | |
}}> | |
{children} | |
</View> | |
); | |
} | |
function Target({toolTipText, targetText, position, rootRect}) { | |
const targetRef = React.useRef(null); | |
const [rect, setRect] = React.useState(null); | |
React.useLayoutEffect(() => { | |
// Place an artificial delay to illustrate synchronous update | |
wait(200); | |
setRect(targetRef.current?.getBoundingClientRect()); | |
}, [setRect, position]); | |
return ( | |
<> | |
<View | |
ref={targetRef} | |
style={{ | |
borderColor: 'red', | |
borderWidth: 2, | |
padding: 10, | |
}}> | |
<Text>{targetText}</Text> | |
</View> | |
<ToolTip position={position} rootRect={rootRect} targetRect={rect}> | |
<Text>{toolTipText}</Text> | |
</ToolTip> | |
</> | |
); | |
} | |
const positions = [ | |
'top-left', | |
'top-right', | |
'center-center', | |
'bottom-left', | |
'bottom-right', | |
]; | |
function Example() { | |
const toolTipText = 'This is the tooltip'; | |
const targetText = 'This is the target'; | |
const ref = React.useRef(null); | |
const [index, setIndex] = React.useState(0); | |
const [rect, setRect] = React.useState(null); | |
React.useEffect(() => { | |
const setPosition = setInterval(() => { | |
setIndex((index + 1) % positions.length); | |
}, 1000); | |
return () => { | |
clearInterval(setPosition); | |
}; | |
}); | |
const position = positions[index]; | |
const style = getStyle(position); | |
React.useLayoutEffect(() => { | |
// Place an artificial delay to illustrate synchronous update | |
wait(200); | |
setRect(ref.current?.getBoundingClientRect()); | |
}, [setRect, position]); | |
return ( | |
<> | |
<Text style={{margin: 20}}>Position: {position}</Text> | |
<View | |
style={{...style, flex: 1, borderWidth: 1}} | |
ref={ref}> | |
<Target | |
toolTipText={toolTipText} | |
targetText={targetText} | |
rootRect={rect} | |
position={position} | |
/> | |
</View> | |
</> | |
); | |
} | |
function getStyle(position) { | |
switch (position) { | |
case 'top-left': | |
return { | |
justifyContent: 'flex-start', | |
alignItems: 'flex-start', | |
}; | |
case 'top-center': | |
return { | |
justifyContent: 'flex-start', | |
alignItems: 'center', | |
}; | |
case 'top-right': | |
return { | |
justifyContent: 'flex-start', | |
alignItems: 'flex-end', | |
}; | |
case 'center-center': | |
return { | |
justifyContent: 'center', | |
alignItems: 'center', | |
}; | |
case 'bottom-center': | |
return { | |
justifyContent: 'flex-end', | |
alignItems: 'center', | |
}; | |
case 'bottom-left': | |
return { | |
justifyContent: 'flex-end', | |
alignItems: 'flex-start', | |
}; | |
case 'bottom-right': | |
return { | |
alignItems: 'flex-end', | |
justifyContent: 'flex-end', | |
}; | |
} | |
} | |
export default Example; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
on React Native 0.74 also getting same error "ref.current?.getBoundingClientRect is not a function (it is undefined)"