Understanding React Native's onLayout
Attribute (a bit)
The <KeyboardAvoidingView>
is one component provided by react-native, which (magically) handles the screen to re-layout when the virtual keyboard opens on ones phone.
This component comes not without challenges, I had actually written about it and I still keep finding things, which make me dig deeper into it to understand and eventually (fix and) use it properly.
Dive in
Let's talk about onLayout
first
Multiple components, among them the <View>
component of react-native have an argument called
onLayout
.
The <View>
component is used inside the <KeyboardAvoidingView>
to re-layout the screen when the screen opens. The onLayout
callback is used to re-layout (see the source) when the keyboard opens (or closes).
When does onLayout
fire?
While trying to hunt for a certain bug we have in our project, I figured out I need to understand when onLayout
fires. I started by passing style parameters to the <KeyboardAvoidingView>
which just passes them to a standard react-native <View>
component, so I can also try it on that component.
To test if onLayout
triggers, I created a very simple app, like so:
export const App = () => {
// Generate a value that continuously changes.
const [changingValue, setChangingValue] = React.useState(0);
React.useEffect(() => {
const t = setTimeout(() => { setChangingValue(changingValue > 50 ? 0 : changingValue + 1); }, 200);
return () => clearTimeout(t);
}, [changingValue]);
// Make a change in the `layout` value re-render.
const [layout, setLayout] = React.useState('');
const propertyName = 'borderWidth'; // Play with this value, e.g. "padding", "margin", "top", "left", ...
return (
<View
style={{flex: 0, height: 200, backgroundColor: 'green', [propertyName]: changingValue}}
onLayout={(e) => { setLayout(JSON.stringify(e.nativeEvent.layout, null, 4)) }}
>
<TextL style={{color: 'white', padding: 10}}>{layout}</TextL>
</View>
);
};
This app renders some current layout values, provided by onLayout
, every time onLayout
fires.
Playing around with the attributes was definitely very teaching for me.
Note: I use the background color to also visually see the change of the <View>
, the edges, borders and its position.
What I learned is, when the following values change onLayout
fires:
margin
left
,top
,right
,bottom
height
,width
,maxHeight
,maxWidth
aspectRatio
Changing the values of the following props do NOT fire onLayout
:
padding
,padding*
borderWidth
,border*Width
,borderRadius
margin
fires onLayout
- Why?
When I first changed the value of margin
and saw that this triggers a onLayout
, I was surprised. Why would changing a margin change the box size, which I thought was responsible for triggering onLayout
? When I started printing the values of the LayoutEvent
(provided by onLayout
's parameter, via e.nativeEvent.layout
) I realized that a margin is rendered by just making the box smaller and repositioning it absolutely. That makes a lot of sense, instead of artificially doing some layout magic and adding some "virtual" margin, just to be web-like. Nice.
Diving into the react-native code
Reading the react-native source code a bit I found the following:
Simple test to verify that layout events (onLayout) propagate to JS from native.
This comment in the code
sounds to me as if all native onLayout
events are propagated to react-native. Which means one needs understand when the underlying platform sends layout events. Good to know.
One more finding, the onLayout
dispatching seems to be even device dependent (potentially), because this Android code in the file "uimanager/ReactShadowNodeImpl.java"
determines when a layout change is detected, which I assume triggers an onLayout
. I didn't find the connection from this file to firing onLayout
but while digging into the code I somehow (magically) "saw" the connection ;).
This code does also very well reflect the learning from above. The code:
int newAbsoluteLeft = Math.round(absoluteX + layoutX);
int newAbsoluteTop = Math.round(absoluteY + layoutY);
reveals that intention quite well. The variables absoluteX
and absoluteY
do already, by using "absolute", let me assume this is how the box's position is calculated and that, when they change, fires the
onLayout
.