skip to content

In React Native, why does a layout sized with Dimensions.get at module scope stay stuck at launch size, and how does useWindowDimensions fix it?

level: middleimportance: must knowfreq 62%

answer

  1. evaluated once, at import
  2. rotation, folds and split-screen resize
  3. 'change' event, subscription.remove()
  4. hook re-renders on width, height, scale, fontScale
  5. static styles apart from sizes

basics

~20 s

Dimensions.get returns a snapshot, and a StyleSheet built at module scope captures it once, so rotation, unfolding or split-screen never update it. useWindowDimensions subscribes to Dimensions change events and re-renders the component with the new window size.

solid answer

~40 s

`Dimensions.get('window')` returns the window's size **at the moment you call it**. Code like `StyleSheet.create({ card: { width: Dimensions.get('window').width / 2 } })` runs once when the module is imported, so after rotating, unfolding a foldable or entering split-screen the cards keep their launch width. The window does change, and React Native emits a `change` event, but nothing re-reads it. `useWindowDimensions()` is the component-side fix: it reads the window, subscribes to `Dimensions.addEventListener('change', ...)`, and updates state when `width`, `height`, `scale` or `fontScale` differ, which re-renders the component with fresh values. Outside components you can subscribe yourself and call `remove()` on the returned subscription. Keep static styles in `StyleSheet.create` and compute window-dependent sizes during render.

code

tsx · 15 lines
tsx
import { StyleSheet, Text, View, useWindowDimensions } from 'react-native';

export function StepCard({ step }: { step: string }) {
  const { width } = useWindowDimensions();
  const cardWidth = width >= 600 ? (width - 48) / 2 : width - 32;
  return (
    <View style={[styles.card, { width: cardWidth }]}>
      <Text>{step}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  card: { padding: 16, borderRadius: 8, borderWidth: 1 },
});

go deeper

for a junior

Recall that Dimensions.get is a snapshot and that useWindowDimensions re-renders the component when the window changes.

for a middle

Explain why module-scope styles freeze, how the hook subscribes and compares values, and how to subscribe and remove a listener outside components.

for a senior

Find frozen-size bugs across a codebase, choose flexbox over window reads where possible, and keep window-driven re-renders from cascading through heavy screens.

for a principal

Set team conventions for responsive layout: which decisions may read the window, where that read lives, and how rotation, split-screen and foldables are tested.

## The bug: a snapshot frozen at import time A recipe app shows its steps as cards, two per row, and someone writes: ```tsx const { width } = Dimensions.get('window'); const styles = StyleSheet.create({ card: { width: width / 2 - 24 } }); ``` It looks right on first launch. Rotate the tablet, unfold the phone or open the app in split-screen, and the cards keep their old width: too narrow on the big window, overflowing on the small one. The reason is timing. Module-level code runs **once**, when the file is first imported. `Dimensions.get('window')` returned the launch-time size, and the style object captured that number forever. The docs make the same point: window dimensions are available immediately but can change, so logic or styles that depend on them should read them on every render instead of caching them. ## Dimensions.get and useWindowDimensions compared | | `Dimensions.get('window')` | `useWindowDimensions()` | |---|---|---| | What it is | A function returning the current value | A hook for function components | | Updates when the window changes | No, you must call it again | Yes, re-renders the component | | Usable outside components | Yes | No | | Fields | `width`, `height`, `scale`, `fontScale` | The same four | | Recommended for components | No | Yes | Both report sizes in **points**: the source divides the physical pixel size by the scale before storing it. ## How the hook works The implementation in `Libraries/Utilities/useWindowDimensions.js` is short: 1. It initialises state with `Dimensions.get('window')`. 2. In an effect, it subscribes with `Dimensions.addEventListener('change', handler)`. 3. It immediately re-checks the current value, in case the window changed between render and subscription. 4. The handler calls `setDimensions` only when `width`, `height`, `scale` or `fontScale` actually differ. 5. On unmount, it calls `remove()` on the subscription. So a component using it re-renders exactly when one of those four values changes, including a user text-size change, which updates `fontScale`. ## Subscribing outside a component Sometimes a non-React module needs the size, for example an analytics layer or a store. Then subscribe directly: - `Dimensions.addEventListener('change', ({ window, screen }) => { ... })` returns a **subscription**. - Call `subscription.remove()` to stop listening; there is no `removeEventListener` on `Dimensions`. - The event does not fire for the initial value, so read `Dimensions.get('window')` once at start as well. ## window versus screen `Dimensions.get` takes `'window'` or `'screen'`. The **window** is the area your app actually gets; the **screen** is the whole display. They differ when the app shares the display, such as split-screen or multi-window, and on Android the window's reported height can differ from the screen's depending on the system bars. Layout should almost always follow the window. ## Where static styles still belong The fix is not to abandon `StyleSheet.create`: - Keep colours, padding, borders and typography in a static `StyleSheet.create` object. - Compute window-dependent values, such as a card width or column count, in the component body from `useWindowDimensions()`. - Combine them in a style array: `style={[styles.card, { width: cardWidth }]}`. - Prefer flexbox and percentages when they can express the layout, because Yoga then re-lays it out on any size change with no JavaScript at all; read the window only for decisions flexbox cannot make, such as switching from one column to two. ## Auditing a codebase for frozen sizes Frozen-size bugs hide in a few recognisable shapes: - `Dimensions.get(...)` at the top level of a module, outside any function. - A constants file exporting `SCREEN_WIDTH` or similar, imported everywhere. - Window values captured in a `useMemo` or `useCallback` with an empty dependency list. - A `StyleSheet.create` call whose values reference any of the above. Each can move into a component body that calls `useWindowDimensions()`, or be replaced by flexbox and percentages. ## Checking the fix - Rotate the device with the screen open and confirm cards resize without navigating away. - Try split-screen or an iPad multitasking size, where the window shrinks while the screen does not. - Change the system text size and confirm components reading `fontScale` update.

  • Why does useWindowDimensions re-check the size right after subscribing?
    There is a gap between the first render, which read `Dimensions.get('window')`, and the effect that subscribes. A rotation in that gap would emit its `change` event before anyone listened. Calling the handler once after subscribing catches that case, and if nothing changed the hook does not update state.
  • Can a component re-render from useWindowDimensions even if its width did not change?
    Yes. The hook updates when any of `width`, `height`, `scale` or `fontScale` changes, so a text-size change or a height-only change re-renders every component using it. For expensive subtrees, derive the one decision you need, such as a column count, and pass that down so memoized children only re-render when the decision changes.

saying these in an interview costs you the question

  • StyleSheet.create re-evaluates its values when the device rotates.
  • Dimensions.get('window') updates in place when the window changes.
  • Remove a Dimensions listener with Dimensions.removeEventListener.
  • The screen size is the right input for layout in split-screen.
  • useWindowDimensions only updates on rotation, not on folds or text-size changes.