Reputation: 3114
I want to measure the size of a React Native View every time it renders, and save it to state. If element layout didn't change the effect should not run.
It's easy to do with a class based component, where onLayout can be used. But what do I do in a functional component where I use React Hooks?
I've read about useLayoutEffect. If that's the way to go, do you have an example of how to use it?
I made this custom hook called useDimensions. This is how far I've got:
const useDimensions = () => {
const ref = useRef(null);
const [dimensions, setDimensions] = useState({});
useLayoutEffect(
() => {
setDimensions(/* Get size of element here? How? */);
},
[ref.current],
);
return [ref, dimensions];
};
And I use the hook and add the ref to the view that I want to measure the dimensions of.
const [ref, dimensions] = useDimensions();
return (
<View ref={ref}>
...
</View>
);
I've tried to debug ref.current
but didn't find anything useful there. I've also tried measure() inside the effect hook:
ref.current.measure((size) => {
setDimensions(size); // size is always 0
});
Upvotes: 25
Views: 31099
Reputation: 1428
As a refinement to matto1990's answer, and to answer Kerkness's question - here's an example custom hook that supplies the x, y position as well as the layout size:
const useComponentLayout = () => {
const [layout, setLayout] = React.useState(null);
const onLayout = React.useCallback(event => {
const layout = event.nativeEvent.layout;
setLayout(layout);
}, [])
return [layout, onLayout]
}
const Component = () => {
const [{ height, width, x, y }, onLayout] = useComponentLayout();
return <View onLayout={onLayout} />;
};
Upvotes: 6
Reputation: 3856
If you could like a more self-contained version of this here is a custom hook version for React Native:
const useComponentSize = () => {
const [size, setSize] = useState(null);
const onLayout = useCallback(event => {
const { width, height } = event.nativeEvent.layout;
setSize({ width, height });
}, []);
return [size, onLayout];
};
const Component = () => {
const [size, onLayout] = useComponentSize();
return <View onLayout={onLayout} />;
};
Upvotes: 60
Reputation: 9787
You had the right idea, it just needed a couple of tweaks... mainly, handing in the element ref and using elementRef (not elementRef.current
) in the useEffect
dependency array.
(Regarding useEffect
vs useLayoutEffect
, as you're only measuring rather than mutating the DOM then I believe useEffect
is the way to go, but you can swap it out like-for-like if you need to)
const useDimensions = elementRef => {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
const el = elementRef.current;
setDimensions({ width: el.clientWidth, height: el.clientHeight });
}, [elementRef]);
return [dimensions];
};
Use it like this:
function App() {
const divRef = useRef(null);
const [dimensions] = useDimensions(divRef);
return (
<div ref={divRef} className="App">
<div>
width: {dimensions.width}, height: {dimensions.height}
</div>
</div>
);
}
Edited to Add React Native version:
For React Native you can use useState
with onLayout
like this:
const App=()=>{
const [dimensions, setDimensions] = useState({width:0, height:0})
return (
<View onLayout={(event) => {
const {x, y, width, height} = event.nativeEvent.layout;
setDimensions({width:width, height:height});
}}>
<Text}>
height: {dimensions.height} width: {dimensions.width}
</Text>
</View>
);
}
Upvotes: 6