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
reactpackage for components, hooks and reconciliation. The difference is entirely in the renderer:react-domtargets 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.
| React (web) | React Native | |
|---|---|---|
| Target platform | Browsers | iOS and Android native UI |
| Shared layer | react package | The same react package |
| Renderer layer | react-dom | React Native's renderer |
| Host components | div, p, img, input | View, Text, Image, TextInput |
| Styling | CSS, CSS-in-JS, utility classes | JavaScript style objects, usually via StyleSheet.create |
| User input | DOM events such as onClick | Press handlers such as onPress on Pressable |
| Bundler | Vite, webpack, or similar | Metro, which React Native's docs name as the tool it uses to build JavaScript and assets |
| Component testing | jsdom plus a DOM testing library | Jest with React Native's preset, plus React Native Testing Library |
| End to end testing | Browser drivers | Detox, Appium or Maestro, per the React Native docs |
| Getting it to users | Deploy a site | Ship 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 variableuseEffectand its cleanup function, plus the dependency array rulesuseMemo,useCallbackanduseRefuseContextand 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.memoand 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 Native | Rough 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.
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
fireEventand query APIs. You press withfireEvent.pressand type withfireEvent.changeText. - Those same docs mark React's Test Renderer as deprecated, so do not reach for
react-test-rendererdirectly 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.