Adjusting Text’s font size in react native across multiple components
We all know by know about the “adjustsFontSizeToFit” prop for the text component in react native. This prop allows us to create a responsive text that adjusts its size to fit inside its container, which is, granted, a very nice addition to RN.
But what if, we wanted to fit multiple Text components situated inside different containers, and have them all have the same font size?
Obviously, the eventual font size should be the smallest of the multiple Text components that fits the most overflowing Text inside of its container.
Let’s create a custom hook that allows us to do just that:
const useAdaptiveFontSizes = (baseFontSize: number) => {
const [fontSize, setFontSize] = useState<number>(baseFontSize);
const [containerLayout, setContainerLayout] = useState<LayoutRectangle[]>([]);
const onContainerLayout = (index: number, layout: LayoutRectangle) => {
setContainerLayout((cur) => replaceElement(index, layout, cur));
};
const [textLayout, setTextLayout] = useState<LayoutRectangle[]>([]);
const onTextLayout = (index: number, layout: LayoutRectangle) => {
setTextLayout((cur) => replaceElement(index, layout, cur));
};
// fit text to containers
useEffect(() => {
// find the font sizes that fit the containers in case the text overflows
const fontSizes = textLayout.map((_l, i) => {
if (!textLayout[i]?.height || !containerLayout[i]?.height) return fontSize;
// text overflowing height
if (textLayout[i]!.height > containerLayout[i]!.height) {
return Math.ceil(
baseFontSize * (containerLayout[i]!.height / textLayout[i]!.height),
);
}
// text overflowing width
if (textLayout[i]!.width > containerLayout[i]!.width) {
const arbitraryWidthFactor = 0.8; // factor deemed necessary for width to fit
return Math.ceil(
baseFontSize * (containerLayout[i]!.width / textLayout[i]!.width) * arbitraryWidthFactor,
);
}
return fontSize;
});
// of the texts, pick the smaller font size
const theSmallerFontSize = Math.min(...fontSizes);
setFontSize(Math.min(theSmallerFontSize, fontSize));
}, [fontSize, textLayout, containerLayout, baseFontSize]);
return {
fontSize,
onContainerLayout,
onTextLayout,
};
};
Basically, what i’m trying to do here, is to get the width and height of each container by adding a listener to the onLayout prop for each container. I then repeat the same process for the Text components––get their width and height. I then compare the width and height of each text component with its container, and if it’s overflowing, prorate the base font size [desired font size to be used when text doesn’t overflow] so it can fit.
Let’s pass the onLayout listeners to the components:
const {
fontSize, onTextLayout, onContainerLayout, setFontSize,
} = useAdaptiveFontSizes(42); // 42 being my preferred font size.return (
<View>
{/* First Container */}
<View
onLayout={(({ nativeEvent }) => onContainerLayout(1, nativeEvent.layout))}
>
<Text
style={{ fontSize, lineHeight: fontSize * 1.25 }}
onLayout={({ nativeEvent }) => onTextLayout(1, nativeEvent.layout)}
>
1st Text Goes here, it might be longer than the 2nd
</Text>
</View> {/* Second Container */}
<View
onLayout={(({ nativeEvent }) => onContainerLayout(2, nativeEvent.layout))}
>
<Text
style={{ fontSize, lineHeight: fontSize * 1.25 }}
onLayout={({ nativeEvent }) => onTextLayout(2, nativeEvent.layout)}
>
Second Text Here
</Text>
</View>
</View>
);
Pay attention to the index passed to each onLayout listener. The container’s index must match its Text’s index, and the same index should not be assigned twice to two different containers.
I would also advise you to alter the Text’s lineHeight. In the example above, i’m setting it as a function to fontSize. Since we’re working with the unknown, it’s probably a better idea to tie it to the fontSize since a lineHeight that is too large or too small would end up affecting your layout.
This should help you the next time you have constrained containers [either through width/height or flex] and an overflowing text which length you cannot predict.