Measure React Native text before render. Pure JS. No native modules. Expo compatible.
Pre-render text height calculation for smooth FlatList virtualization. Calculate text dimensions synchronously using font metrics – no native bridge, no async, works everywhere React Native runs.
React Native cannot measure text before rendering. This forces you to either:
onLayout post-render measurement – causes layout shifts and jankgetItemLayout – recalculates on every scroll, drops framesWith getItemLayout, FlatList can scroll at 60fps with 10K+ items. But you need heights in advance – which RN cannot provide. Until now.
npm install @shipitandpray/pretext-rn
import { useTextHeight } from '@shipitandpray/pretext-rn';
function ChatBubble({ text }) {
const { height } = useTextHeight(text, { fontFamily: 'System', fontSize: 16 }, 280);
return <View style=><Text>{text}</Text></View>;
}
measureText(text, font, maxWidth)Core measurement function. Pure math, synchronous, cached.
import { measureText } from '@shipitandpray/pretext-rn';
const result = measureText('Hello world, this is a long message...', {
fontFamily: 'System',
fontSize: 16,
lineHeight: 22,
}, 300);
console.log(result.height); // 44 (2 lines x 22)
console.log(result.width); // max line width in px
console.log(result.lineCount); // 2
console.log(result.lineBreaks); // [28] (character index of break)
Parameters:
| Param | Type | Description |
|---|---|---|
text |
string |
Text to measure |
font |
FontConfig |
Font family, size, weight, lineHeight, letterSpacing |
maxWidth |
number |
Maximum width in pixels for line wrapping |
Returns: MeasureResult with height, width, lineCount, lineBreaks.
useTextHeight(text, font, maxWidth)React hook for text height. Memoized, synchronous.
const { height, lineCount, measuring } = useTextHeight(text, font, maxWidth);
// measuring is always false (sync computation, kept for future API compat)
useAutoLayout(data, textExtractor, font, maxWidth, extraHeight?)Hook that returns a getItemLayout function for FlatList. Batch-measures all items and builds a prefix-sum array for O(1) offset lookup.
import { useAutoLayout } from '@shipitandpray/pretext-rn';
function ChatScreen({ messages }) {
const { getItemLayout } = useAutoLayout(
messages,
(msg) => msg.body,
{ fontFamily: 'System', fontSize: 16, lineHeight: 22 },
screenWidth - 32,
48 // avatar + padding
);
return (
<FlatList
data={messages}
getItemLayout={getItemLayout}
renderItem={({ item }) => <ChatBubble message={item} />}
keyExtractor={(item) => item.id}
/>
);
}
PreMeasuredFlatListDrop-in FlatList wrapper with automatic getItemLayout.
import { PreMeasuredFlatList } from '@shipitandpray/pretext-rn';
function ChatScreen({ messages }) {
return (
<PreMeasuredFlatList
data={messages}
textExtractor={(msg) => msg.body}
font=
extraItemHeight={48}
renderItem={({ item }) => <ChatBubble message={item} />}
keyExtractor={(item) => item.id}
inverted
/>
);
}
registerFont(fontFamily, metrics)Register custom font metrics for accurate measurement with non-system fonts.
import { registerFont } from '@shipitandpray/pretext-rn';
registerFont('Inter', {
unitsPerEm: 2048,
ascender: 1984,
descender: -494,
lineGap: 0,
glyphWidths: { a: 1024, b: 1060, /* ... */ },
});
clearMeasureCache()Clear the internal LRU cache (10K entries). Call after registering new font metrics.
setPlatform(platform)Override platform detection. Useful for testing or cross-platform SSR.
import { setPlatform } from '@shipitandpray/pretext-rn';
setPlatform('ios'); // 'ios' | 'android' | 'web'
Works out of the box with Expo. No native modules, no linking, no pods.
npx expo install @shipitandpray/pretext-rn
That’s it. No expo prebuild, no native code.
Without getItemLayout, FlatList must render and measure each item to know its position. This causes:
scrollToIndex – cannot jump to items without rendering everything in betweenWith @shipitandpray/pretext-rn, all heights are pre-computed from font metrics. FlatList knows every item’s position before rendering.
| Metric | Without pretext-rn | With pretext-rn |
|---|---|---|
| 10K item scroll FPS | 24-45 fps | 60 fps |
scrollToIndex(5000) |
Renders 0-5000 first | Instant |
| Initial render | ~800ms | ~200ms + 50ms measure |
| Feature | pretext-rn | react-native-text-size | Manual onLayout |
|---|---|---|---|
| Pure JS | Yes | No (native module) | Yes |
| Expo compatible | Yes | No | Yes |
| Pre-render measurement | Yes | Yes | No |
| Sync API | Yes | No (async) | No |
| Cached results | Yes (10K LRU) | No | No |
| Platform support | iOS + Android + Web | iOS + Android | All |
| Bundle size | <15KB gzip | ~50KB + native | 0 |
| Last updated | 2026 | 2021 (abandoned) | N/A |
interface FontConfig {
fontFamily: string; // 'System', 'Roboto', 'Inter', etc.
fontSize: number; // in pixels
fontWeight?: string; // '100' to '900'
lineHeight?: number; // explicit line height (overrides calculated)
letterSpacing?: number; // additional spacing per character
}
registerFont() for custom fonts.maxWidth, respecting word boundaries.ascender + |descender| + lineGap, Android uses ascender + |descender|.How accurate is it? Within 1-2px for system fonts. For custom fonts, accuracy depends on the metrics you register. The built-in system font metrics are based on actual SF Pro and Roboto font tables.
Does it work with custom fonts?
Yes. Use registerFont() to provide your font’s metrics (unitsPerEm, ascender, descender, glyph widths). You can extract these from font files using tools like fontkit or opentype.js.
What about bold/italic text? Bold text uses slightly wider glyphs. For best accuracy with bold text, register separate metrics for each weight. The default metrics approximate regular weight.
Does it handle emoji? Emoji use a fallback average width. For most chat/feed layouts this is close enough. CJK characters also use the fallback width.
What about RTL text? Width calculation works for RTL text since character widths are the same. Line breaking is character-aware and direction-independent.
Can I use it without React?
Yes. measureText() is a pure function with no React dependency. Only the hooks (useTextHeight, useAutoLayout) and PreMeasuredFlatList require React.
| Metric | Result |
|---|---|
measureText() single call |
< 0.05ms |
| Batch measure 10K items | < 200ms |
| Cache hit rate (chat app) | > 85% |
| Memory (10K cache entries) | < 5MB |
| Bundle size | < 15KB gzipped |
MIT – ShipItAndPray