I'm experiencing an odd issue in one of my components where both props and local state are not showing up in an event handler function.
export default function KeyboardState({layout, children}) {
// Setup local component state
const [contentHeight, setContentHeight] = useState(layout.height)
const [keyboardHeight, setKeyboardHeight] = useState(0)
const [keyboardVisible, setKeyboardVisible] = useState(false)
const [keyboardWillShow, setKeyboardWillShow] = useState(false)
const [keyboardWillHide, setKeyboardWillHide] = useState(false)
const [keyboardAnimationDuration, setKeyboardAnimationDuration] = useState(INITIAL_ANIMATION_DURATION)
// set up event listeners
useEffect(()=> {
let subscriptions = []
if (Platform.OS === 'ios') {
subscriptions = [
Keyboard.addListener('keyboardWillShow', keyboardWillShowHandler),
// other listeners
]
}
return ()=> subscriptions.forEach(subscription => subscription.remove())
}, [])
// ODD BEHAVIOR
console.log(layout) // object: {height: 852, width: 414, x: 0, y: 44}
console.log(contentHeight) // 550
const keyboardWillShowHandler = (event) => {
console.log(layout) // object: {height: 0, width: 0, x: 0, y: 0}
console.log(contentHeight) // 0
setKeyboardWillShow(true)
measureEvent(event)
}
What could possibly be causing this to occur? Within event handlers, we should have access to both the local component props and state, so I can't think of an explanation.
I have confirmed that this component is not receiving an updated layout prop during this time (by using useEffect and passing the layout as a dependency to watch). To me, what's especially odd is that logging within the event handler provides the correct object structure for layout only with values of 0, while the incoming layout prop is unchanged.
Edit:
It appears the issue is with how I am initializing my custom hook that returns layout. I had been initializing it to an object w/ properties height, width, x and y of 0. The inelegant, device-specific solution would be to initialize it to an object with values matching the dimensions of an iPhone X. But how would I dynamically achieve this? And why is my layout changing back to initial state within KeyboardState within event handlers when the component detects no new incoming prop values?
/*UseComponentSize.js (custom hook) */
const useComponentSize = (initial = initialState) => {
const [layout, setLayout] = useState(initial)
const onLayout = useCallback(event => {
console.log(event)
const newLayout = {...event.nativeEvent.layout}
newLayout.y = newLayout.y + (Platform.OS === 'android' ? Constants.statusBarHeight : 0)
setLayout(newLayout)
}, [])
return [layout, onLayout]
}
/*App.js*/
const [layout, onLayout] = useComponentSize()
return (
<View style={styles.container}>
<View style={styles.layout} onLayout={onLayout}>
<KeyboardState layout={layout}>
...
</KeyboardState>
</View>
</View>
)
Edit 2:
I fixed the problem by initializing layout in my custom hook to null, while revising my App.js to return on the condition that layout was not null. That is:
/*App.js*/
const [layout, onLayout] = useComponentSize()
return (
<View style={styles.container}>
<View style={styles.layout} onLayout={onLayout}>
{layout && (
<KeyboardState layout={layout}>
...
</KeyboardState>
)}
</View>
</View>
)
I still don't know why my KeyboardState component returned the layout prop correctly outside an event handler, while returning the useComponentSize() initial state when calling the layout prop within the event handler.