30% offEnding soon

React vs React Native: What Actually Differs

15 min read

React and React Native are not two competing frameworks that happen to share a name. One is a library for describing UI, the other is a way of putting that description on a phone screen, and they run the same core code to do it.

React and React Native both use the same react package for components, hooks and reconciliation. The difference is entirely in the renderer: react-dom targets DOM elements, React Native's renderer targets native platform views. That is what changes the element names, the styling model, the event handlers and the test setup.

The short answer: one library, two renderers

Think of any React app as two layers stacked on top of each other.

The shared layer is the react package. It gives you JSX, function components, props, state, hooks, context, keys, and the reconciliation algorithm that decides what changed between renders. This layer has no idea what a div is. It never touches a screen.

The renderer layer is the package that takes React's output and turns it into something a user can see. On the web that is react-dom, which creates DOM elements in a browser. In a React Native app it is React Native's own renderer, which creates native platform views on iOS or Android. React Native's documentation states that its primitives render to native platform UI, and that an app built this way uses the same native platform APIs other apps do.

Hold onto those two labels. Every question of the form "does X work the same in React Native?" answers itself once you know which layer X lives in.

shared layer: reactJSX, hooks, state, contextkeys, reconciliationrendererreact-domrendererReact Nativehost componentsdiv, p, imginputhost componentsView, TextImage, TextInput
One shared `react` package; the fork happens at the renderer.
React (web)React Native
Target platformBrowsersiOS and Android native UI
Shared layerreact packageThe same react package
Renderer layerreact-domReact Native's renderer
Host componentsdiv, p, img, inputView, Text, Image, TextInput
StylingCSS, CSS-in-JS, utility classesJavaScript style objects, usually via StyleSheet.create
User inputDOM events such as onClickPress handlers such as onPress on Pressable
BundlerVite, webpack, or similarMetro, which React Native's docs name as the tool it uses to build JavaScript and assets
Component testingjsdom plus a DOM testing libraryJest with React Native's preset, plus React Native Testing Library
End to end testingBrowser driversDetox, Appium or Maestro, per the React Native docs
Getting it to usersDeploy a siteShip a build through the app stores

As of August 2026, react.dev lists 19.2 as the latest stable React, and reactnative.dev lists 0.87 as the latest stable React Native. The two version numbers move independently because they are different packages, which is exactly the point.

Same React, different renderer

Open the package.json of a React Native app and you will find react in the dependency list next to react-native. Not a fork, not a lookalike. The same package you install for a web app.

The versions track each other. React Native 0.78 shipped React 19, and the release notes told app authors to adapt to React 19's breaking changes, including the removal of propTypes. That is a React change, not a mobile change, and it landed on mobile because mobile is running React.

What the renderer decides is the set of host components available to you. A host component is an element React does not define itself, one the renderer knows how to create. In react-dom, host components are DOM elements, so <div> in your JSX means "ask the DOM for a div". In React Native, host components are the Core Components, so <View> means "ask the platform for a view". React's reconciler treats both the same way: it compares element types, keeps what matches, replaces what does not, and hands the instructions to whichever renderer is attached.

This is also why the phrase attached to React Native is "learn once, write anywhere" rather than "write once, run anywhere". Your knowledge is portable. Your view code is not, because the leaves of the tree are different components with different props.

The trip can be made in reverse, too. React Native for Web describes itself as a compatibility layer between React DOM and React Native, and it uses React DOM to render React Native compatible JavaScript in a browser, with a JavaScript styling system that converts to CSS. So a team can write <View> and <Text> once and get DOM output on the web and native views on a phone. That is a build-time and library choice, not something React itself does for you.

What transfers: everything above the renderer

If it lives in the react package, it is identical on both platforms. Concretely:

  • JSX syntax, including fragments, conditional rendering and list rendering
  • Props and one-way data flow from parent to child
  • useState, and the fact that state updates are queued rather than applied to your local variable
  • useEffect and its cleanup function, plus the dependency array rules
  • useMemo, useCallback and useRef
  • useContext and context providers
  • Keys, and the reconciliation behaviour that makes a bad key remount a subtree instead of updating it
  • Controlled components, where a value prop plus a change handler make React the source of truth
  • Lifting state up, and choosing where a piece of state should live
  • Custom hooks, and the rule that hooks are called unconditionally at the top level
  • React.memo and the reference-equality traps that make it do nothing

This is not a partial overlap. It is the whole shared layer. A practice problem about how the diffing algorithm decides what to keep is the same problem regardless of what the output ends up being, and a hook like useEventCallback has the same body in either project because it only calls useRef and useCallback.

The same applies to plain language work. Closures, promise ordering, prototypes, array methods and algorithm questions have nothing to do with a renderer, so a JavaScript interview round is platform neutral by construction.

What does not transfer is anything that names a browser API. document.querySelector, window.addEventListener, CSS specificity, media queries, localStorage, the History API. React Native has no DOM, so none of these exist there. Some have replacements with different names and different semantics, and some simply have no equivalent.

What doesn't: elements, styles, events

Three concrete deltas, in the order you will hit them.

Different elements

React Native gives you platform-agnostic Core Components in place of HTML tags. Its docs list the web analogues directly:

React NativeRough web equivalent
<View>a non-scrolling <div>
<Text><p>
<Image><img>
<ScrollView>a scrolling <div>
<TextInput><input type="text">

"Rough" matters. <Text> is stricter than <p>: React Native requires every text node to be wrapped in a <Text>, and a bare string directly under a <View> raises an exception. The upside is that nested <Text> composes styles, so a red <Text> inside a bold one comes out bold and red.

Different styling

Styles are JavaScript objects. Property names are camelCase, so you write backgroundColor rather than background-color. Most core components take a style prop, which accepts an object or an array of objects, with the last entry winning. StyleSheet.create is the usual way to group them once a component grows.

Three things surprise people arriving from CSS:

There is no cascade in the general sense. Style inheritance in React Native is limited to text subtrees, so setting a font on a <View> does not push it down to every descendant. React Native's docs frame this as deliberate isolation: a component looks the same wherever you drop it.

Dimensions are unitless numbers representing density-independent pixels, so there are no px or rem units. Percentage strings do work — width: '50%' resolves against a parent with a defined size — but the plain number is the default. The docs caution that there is no universal mapping from those points to physical units, so a fixed size is not the same physical size on every device.

And the flexbox defaults differ from the web's:

That first line is the single most common "why is my layout wrong" moment for a web developer's first day in React Native. Vertical stacking is free; a row needs flexDirection: 'row' written out.

web (CSS)default: rowABCmain axis runsacrossReact Nativedefault: columnABCmain axisruns downfor a row, write flexDirection: 'row'
The same flex container, no flexDirection set: web lays out across, React Native lays out down.

Different events

There is no DOM event system, so there is no onClick and no bubbling phase you can rely on the way you do in a browser. Touch handling goes through components like Pressable, which exposes onPress, onPressIn, onPressOut and onLongPress, and whose style prop can be a function receiving a { pressed } boolean. Pressable also accepts a children function taking that same pressed state.

Refs behave the same as a React concept and differently as a value. useRef is useRef, but attaching a ref to a <View> gives you a handle on that host component, not a DOM node, so .focus() on a <TextInput> works while .getBoundingClientRect() does not exist.

The same component, written twice

Here is a single-open accordion. First the logic, which belongs entirely to the shared layer:

function useAccordion(initiallyOpen = null) {
  const [openId, setOpenId] = useState(initiallyOpen);
  const toggle = (id) =>
    setOpenId((current) => (current === id ? null : id));
  return { openId, toggle };
}

That hook is byte-for-byte the same file in both projects. It calls useState, returns a value and a function, and never mentions a screen.

The web version, using react-dom host components:

function Accordion({ items }) {
  const { openId, toggle } = useAccordion();
  return (
    <div className="accordion">
      {items.map((item) => (
        <div className="accordion-item" key={item.id}>
          <button
            className="accordion-header"
            aria-expanded={openId === item.id}
            onClick={() => toggle(item.id)}
          >
            {item.title}
          </button>
          {openId === item.id && (
            <p className="accordion-body">
              {item.body}
            </p>
          )}
        </div>
      ))}
    </div>
  );
}

The React Native version, importing View, Text, Pressable and StyleSheet from react-native:

function Accordion({ items }) {
  const { openId, toggle } = useAccordion();
  return (
    <View style={styles.accordion}>
      {items.map((item) => (
        <View style={styles.item} key={item.id}>
          <Pressable
            accessibilityRole="button"
            accessibilityState={{ expanded: openId === item.id }}
            onPress={() => toggle(item.id)}
            style={({ pressed }) => [
              styles.header,
              pressed && styles.headerPressed,
            ]}
          >
            <Text style={styles.headerText}>{item.title}</Text>
          </Pressable>
          {openId === item.id && (
            <Text style={styles.body}>{item.body}</Text>
          )}
        </View>
      ))}
    </View>
  );
}

const styles = StyleSheet.create({
  accordion: { borderRadius: 8, overflow: 'hidden' },
  item: { borderBottomWidth: 1, borderBottomColor: '#e5e5e5' },
  header: { paddingVertical: 12, paddingHorizontal: 16 },
  headerPressed: { backgroundColor: '#f0f0f0' },
  headerText: { fontWeight: '600' },
  body: { paddingHorizontal: 16, paddingBottom: 12 },
});

Read the two side by side and count what changed. The state machine did not. The conditional render did not. The key on the mapped item did not. What changed is four leaves: the element names, className becoming style, onClick becoming onPress, and the body text needing a <Text> wrapper because a bare string cannot sit under a <View>.

Notice what is missing from the native version, too. No CSS transition on the body's height. Animating an accordion open in React Native means reaching for Animated or LayoutAnimation, because there is no stylesheet property that quietly interpolates for you. If you are practising UI components like this one for a web round, the component structure carries over; the polish does not.

Tooling and testing differences

The development loop looks different from the first command.

React Native's docs state that it uses Metro to build your JavaScript code and assets, configured through a metro.config.js that extends @react-native/metro-config or @expo/metro-config. Expo is a widely used toolchain built on React Native, and its config package appears in that same documentation. On the web you are more likely running Vite or webpack and refreshing a browser tab. On mobile you are running a simulator or a device, and a change to native dependencies means a native rebuild rather than a hot reload.

Shipping differs just as much. A web app goes out when you deploy it. A React Native app goes out through the app stores, on their timelines and under their review rules.

Testing is where this matters most for interview practice, because the muscle memory is different:

  • React Native's default template ships Jest preconfigured with a preset tailored to the React Native environment, so mocks and configuration mostly work out of the box.
  • For component tests the React Native docs recommend React Native Testing Library, which adds fireEvent and query APIs. You press with fireEvent.press and type with fireEvent.changeText.
  • Those same docs mark React's Test Renderer as deprecated, so do not reach for react-test-renderer directly in new code.
  • For end to end coverage the docs point to Detox, Appium and Maestro rather than browser-based runners.

On the web the equivalent stack is Jest or Vitest against jsdom with a DOM testing library, and a browser driver for end to end runs. The assertion style rhymes across both, and so do the queries: React Native Testing Library gives you getByRole, getByText and getByLabelText too. What changes is where the semantics come from — the role or accessibilityRole prop rather than an HTML tag — plus native-only helpers like getByPlaceholderText and fireEvent.changeText.

Working against a real test runner rather than eyeballing an answer is the part that transfers regardless of platform, which is why UIReady Premium annual access unlocks the full graded question set with the same Jest feedback loop you will meet in either codebase.

Answering this in an interview

Here is a script you can say in about a minute.

"React is the library that describes UI: components, props, state, hooks, and the reconciler that works out what changed. It doesn't render anything by itself. The renderer is a separate package. On the web that's react-dom, which creates DOM elements. React Native is a different renderer plus a component library, and it creates native platform views on iOS and Android instead. A React Native app installs the same react package a web app does, so hooks, context, keys and reconciliation are identical. What changes is everything the renderer owns: I write View and Text instead of div and p, styles are JavaScript objects with no cascade and no selectors, and I handle input with onPress on a Pressable instead of onClick, because there's no DOM event system to bubble through."

Then have the follow-ups ready.

Is React Native a WebView? No. Its docs say the primitives render to native platform UI and the app uses the same native platform APIs other apps do.

Can you share code between web and native? Logic shares easily, views do not, and how much you actually reuse varies by project. A reducer or a data-fetching hook moves over untouched. An accordion's markup does not. React Native for Web goes the other way, rendering React Native compatible code through React DOM in the browser.

What does "learn once, write anywhere" mean? That the skills are portable and the view code is not. It is deliberately not "write once, run anywhere".

Why doesn't my flex row work? Because flexDirection defaults to column in React Native, along with alignContent at flex-start, flexShrink at 0, and a flex prop that only takes one number.

For preparation, split your practice by layer. Hooks, state design, reconciliation and controlled inputs are shared, so anything you drill for a React interview round counts on both sides, and so does a state-management primitive like useSyncExternalStore. DOM APIs, CSS specificity and browser event behaviour are web only. If the role is React Native, spend the extra hours on the element vocabulary, the flexbox defaults and press handling, and leave the rest of your React preparation exactly as it is.

Frequently asked questions

Is React Native just React for mobile?
Almost. A React Native app installs the same `react` package a web app does, so components, props, state, hooks, context and reconciliation behave identically. What differs is the renderer underneath: `react-dom` produces DOM elements, while React Native's renderer produces native platform views. Every real difference between the two follows from that swap.
Is React Native a WebView wrapper around a website?
No. React Native's own documentation says its primitives render to native platform UI, and that an app built with it uses the same native platform APIs other apps do. A `<View>` becomes a real platform view rather than a `<div>` inside an embedded browser.
Why doesn't my flex row work in React Native?
React Native's flexbox defaults are not the CSS defaults. `flexDirection` defaults to `column` instead of `row`, `alignContent` defaults to `flex-start` instead of `stretch`, `flexShrink` defaults to `0` instead of `1`, and the `flex` prop accepts only a single number. If you want a row, set `flexDirection: 'row'` explicitly.
Can I share code between a React web app and a React Native app?
How much you can share varies a lot by project. Hooks, reducers, validation, formatting and data fetching move across with little or no change because they never touch a renderer. View code does not, since the element names, styling model and event props are different. React Native for Web goes the other direction, using React DOM to render React Native compatible code in a browser.
Should I learn React before React Native?
Yes, and most of that learning counts twice. Everything in the `react` package is the same on both platforms, so time spent on state design, effects, keys and custom hooks transfers unchanged. What you then add for React Native is a new element vocabulary, a styling system without a cascade, and press handlers in place of DOM click events.