React Native for Web Developers: A Practical Guide to Mobile UI, Performance & Pixel Density

timeToRead13 min read

Author
Sergey Kualkov is a seasoned software engineer with over a decade of experience in designing and developing cutting-edge solutions. His expertise spans multiple industries, including Healthcare, Automotive, Radio, Education, Fintech, Retail E-commerce, Business, and Media & Entertainment. With a proven track record of leading the development of 20+ products, Sergey has played a key role in driving innovation and optimizing business processes. His strategic approach to re-engineering existing products has led to significant growth, increasing user bases and revenue by up to five times
React Native for Web Developers: A Practical Guide to Mobile UI, Performance & Pixel Density

Why React Native Is a Great Choice for Web Developers Moving from React

React Native is an excellent choice for web developers with React experience who want to build their first mobile app.

Although React and React Native share a lot in common, there are important differences between web platforms and native apps. This article highlights the most common mistakes and questions developers face when transitioning from React to React Native.

Why Choose React Native?

Let’s start by exploring the reasons why React Native is a solid choice for mobile app development.

Familiar Technologies

With React Native, almost the entire app code is written in JavaScript (or more often, TypeScript). This means developers can leverage many of the programming patterns and libraries they already know from web development.

Native Code Under the Hood

Even though most of the app is written in JavaScript, React Native apps are essentially native. React code is mapped to real native primitives for each platform. This is crucial for app performance and appearance.

Cross-Platform Code

React Native allows you to create both iOS and Android apps from the same codebase. Using tools like Expo Router and API routers, you can configure redirects to websites and servers.

Why Choose Expo

Expo is a React Native framework, similar to how Next.js is a framework for React.

React Native lets you run React code directly on iOS and Android platforms. It also provides fundamental components like Text, View, and TextInput. However, many features most apps need — such as navigation, push notifications, device camera access, data persistence between app launches, or publishing to app stores — are not included out of the box. This is intentional to keep the core framework lightweight and maintainable by a small team.

Since key features aren’t bundled with React Native, developers used to rely on numerous community libraries. Over almost 10 years, the React Native ecosystem has grown massively, making it challenging to choose the right libraries — especially when starting a new project without prior experience.

Ideally, you want a set of well-supported, commonly used tools — and that’s exactly what Expo provides.

Expo is a platform built around React Native. It offers a comprehensive set of tools and services bridging the gap between React Native’s core features and everything needed to build production-ready apps. The Expo SDK is an extended standard library that grants access to a wide range of native APIs not included in React Native itself (camera, video, notifications, and more). Expo also provides a CLI for creating projects, an environment for learning and prototyping, file-based navigation, tools for managing builds, publishing apps to stores, configuring updates, and even creating custom native modules. Plus, you can still use most other third-party React Native libraries.

It’s no surprise that React Native officially recommends using frameworks like Expo for new projects. The reasoning is simple: either use a ready-made framework or build your own. And building your own from scratch is rarely the best option for most React Native teams.

UI Primitives in React and React Native

React Native code looks a lot like React code, but there are some differences, especially regarding UI components.

Here’s how you might render text on the web:

export function MyTextComponent() {
  return (
    <div>Hello, web!</div>
  );
}

React Native Core UI Components and Their Web Equivalents

View

Closest web equivalent — div.

In React Native, View is equivalent to a div and is used similarly: to create layouts (containers) and apply styling.

ScrollView

Closest web equivalent — div.

Web pages scroll by default, but native apps do not. To make a page scrollable in React Native, wrap it in a ScrollView (or other virtualized lists like FlatList or SectionList).

Text

Closest web equivalent — p.

Any text displayed in React Native must be wrapped in a Text component.

Image

Closest web equivalent — img.

The Image component lets you render both remote and local images. The API differs slightly from the web. For example, loading an image from a URL looks like this:

import { Image } from "react-native";

export function MyImage() {
  return <Image source={{ uri: "https://domain.com/static/my-image.png" }} />;
}

Loading from a local file looks like this:

import { Image } from "react-native";

const imageSource = require("../assets/my-image.png");

export function MyImage() {
  return <Image source={imageSource} />;
}

In most real-world React Native apps, third-party libraries like FastImage or Expo Image are used instead of this basic component. These provide extra features like styling, caching, and support for image formats.

FlatList Closest web equivalent — array.map().

For rendering large lists of items (like an Instagram feed or email inbox), the web usually maps over an array. On native platforms, this is inefficient and should be avoided. Instead, use FlatList — a virtualized list with built-in optimizations such as deferred rendering of offscreen data and rerendering when data changes.

import { Text, FlatList, View } from "react-native";

const posts = [
  { id: "1", name: "Post 1" },
  { id: "2", name: "Post 2" },
];

export function MyList() {
  return (
    <FlatList
      data={posts}
      renderItem={({ item }) => <Text>{item.name}</Text>}
    />
  );
}

TextInput Closest web equivalent — .

TextInput is used for any text input. The main difference from is that TextInput only accepts text (and numbers). Although TextInput has an onChange handler, typically onChangeText is used, which returns only the updated text string.

import { useState } from "react";
import { TextInput } from "react-native";

export function MyInput() {
  const [value, setValue] = useState("");

  return <TextInput value={value} onChangeText={setValue} />;
}

TouchableOpacity Closest web equivalent — button.

To make UI elements respond to presses, wrap them in TouchableOpacity. This component automatically highlights the pressed area, with the opacity of the highlight adjustable via the activeOpacity prop.

import { TouchableOpacity, Text } from "react-native";

export function MyButton() {
  const onPress = () => {
    console.log("Pressed!");
  };

  return (
    <TouchableOpacity onPress={onPress} activeOpacity={0.8}>
      <Text>Button Text</Text>
    </TouchableOpacity>
  );
}

Additional React Native Components and Styling Basics

Pressable

Closest web equivalent — button.

Like TouchableOpacity, Pressable is used to create buttons. It handles presses and offers more flexibility than TouchableOpacity, such as separate handling of different press states.

Switch

Closest web equivalent — <input type="radio" />

Switch is a toggle component that switches a value between true and false. Its appearance is unique to each mobile platform (iOS or Android) due to platform-specific implementations.

import { useState } from "react";
import { Switch } from "react-native";

export function MySwitch() {
  const [value, setValue] = useState(false);

  return (
    <Switch
      value={value}
      onValueChange={(value) => setValue(value)}
      trackColor={{ true: "pink" }}
    />
  );
}


Styling in React Native
Developers familiar with CSS will find styling in React Native fairly straightforward. Most CSS properties are supported, but some differences exist.

No Global Styles
In React Native, styles are defined directly on components via the style prop. There are no global styles unless you use special styling libraries.

To share styles across components, you can create a separate theme.js file and import styles where needed:

```jsx
import { View, Text, StyleSheet } from "react-native";

export function MyComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.greeting}>Set Reminder</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    padding: 24,
  },
  greeting: {
    fontSize: 24,
  },
});

Flexbox with Nuances React Native uses Flexbox for layout, similar to the web, but with some differences:

All elements default to display: flex

flexDirection defaults to column (not row)

alignContent defaults to flex-start (not stretch)

flexShrink defaults to 0 (not 1)

The flex property accepts only a single numeric value

Styling Libraries React Native’s built-in styling works well for many apps, and many projects avoid third-party styling libraries altogether.

However, some libraries offer convenient alternative approaches:

NativeWind — TailwindCSS for React Native

Styled Components — CSS-in-JS styling

Tamagui — comprehensive cross-platform UI and styling framework

Common Mistakes by React Native Beginners Using array.map() for Rendering Lists This approach is common in web development. For example, rendering a list of posts might look like this:

export function Posts() {
  const posts = usePosts();

  return (
    <div>
      {posts.map(post => <div key={post.id}>{post.name}</div>)}
    </div>
  );
}

However, in native development, this approach is discouraged due to performance issues. Instead, use virtualized list components like FlatList:

import { FlatList, View, Text } from "react-native";

export function Posts() {
  const posts = usePosts();

  return (
    <FlatList
      data={posts}
      renderItem={({ item }) => (
        <View>
          <Text>{item.name}</Text>
        </View>
      )}
    />
  );
}

Virtualized lists automatically optimize performance by not rendering items outside the visible viewport.

An even more optimized alternative to FlatList is FlashList.

Using the React Native Button Component

React Native provides a Button component, but it fits only about 1% of use cases because it does not allow style customization. It’s often better to just forget about using it.

The recommended way to create buttons in React Native is to wrap the button content inside TouchableOpacity or Pressable:

import { TouchableOpacity, Text } from "react-native";

export function MyButton() {
  const onPress = () => {
    console.log("Pressed!");
  };

  return (
    <TouchableOpacity onPress={onPress} activeOpacity={0.8}>
      <Text>Button Text</Text>
    </TouchableOpacity>
  );
}

Do Not Store Secret Data in the App Code

In web development, there is a clear separation between client and server — frontend and backend. Confidential information must never be stored in client-side code because it is accessible to attackers who can study the source code or intercept network traffic.

A React Native app is a client application! Although access to app code and network traffic is less straightforward than in a web browser, it is still possible. Therefore, it is not recommended to embed any sensitive data in the app code. Data such as Firebase configuration or other client identifiers used for app identification in various backend services can be stored in the app code. However, any secret API keys or other critical credentials should never be included in client code.

If your app needs to interact with APIs requiring sensitive data that cannot be embedded, solve this like with websites: deploy an API server and have the app communicate with it.

When using Expo Router, you can write API code inside your React Native app and deploy it separately using API routes.

New Native-Specific Concepts

When moving from web app development to native mobile apps, some new concepts must be learned.

Build Signing

Both iOS and Android have built-in security mechanisms that prevent installation of apps from unknown sources. To install an app on a device, it must be signed with credentials.

The signing process differs between iOS and Android but is fundamentally the same. Android uses a Keystore created locally; iOS requires a Provisioning Profile and Signing Certificate, available only with a paid Apple developer account. Using the EAS CLI for builds automates credential creation and syncing.

Note: For development, you can use Expo Go, which bypasses signing restrictions since it is a publicly released app. This allows fast development without native configuration setup. However, native code cannot be changed in Expo Go, so for production, use dedicated development builds.

Deep Linking

In web development, linking to a specific page is trivial due to unique URLs. In mobile apps, it’s more complex.

A scheme (e.g., myapp://) is registered, which the app listens for. A link like myapp://my/page opens the app and passes my/page as a deep link instruction. The app processes this and routes the user accordingly. Expo Router supports this natively via filesystem-based routing. React Navigation also supports it but requires more setup.

Refer to official docs for details on linking and deep linking.

Gestures and Touches

Native-feeling apps rely on gesture-based interactions: swipe, long press, pinch zoom, etc.

React Native’s TouchableOpacity and Pressable provide props like onLongPress. For more advanced gestures, use libraries like React Native Gesture Handler.

Haptic Feedback

Haptics are subtle vibrations in response to user actions, enhancing the native feel. Use sparingly for key interactions like liking a post or adding an item to cart.

On-Screen Keyboard

Ensure good interaction with the on-screen keyboard:

  • Use KeyboardAvoidingView so inputs remain visible when the keyboard appears.
  • Keyboard appears automatically on input focus but can also be shown/hidden programmatically.
  • Use keyboardShouldPersistTaps on ScrollView to control if taps outside dismiss the keyboard.
  • Use keyboardType on TextInput to specify keyboard layout (e.g., numeric).
  • Other useful props: autoCapitalize, autoComplete, autoCorrect, returnKeyType.

Animation

React Native lacks CSS animations. Instead, use Layout Animation (experimental on Android, use with caution) and the built-in Animated API.

For more complex animations, prefer the Reanimated library, a React hook-based animation solution with a simpler API than Animated.

Pixel Density

Pixel density is a characteristic that shows how many pixels fall on a unit area displayed on the screen. In React Native, traditional units like px, em, or rem are not used for styling. Instead, a special unit called dp (display point) is applied, and the actual number of pixels per dp can vary significantly across devices. You can get information about a device’s pixel density using PixelRatio.

This is especially important when working with images. You need to ensure high image quality while rendering only images sized appropriately for their display area. For example, if an image has a width and height of 100 dp on a modern iPhone with a pixel density of 3, its actual size should be 300x300 px. On an Android device with a density of 1.5, an image of 150x150 px is sufficient. Rendering a larger image in a smaller area works but is resource-intensive (which can reduce performance and cause crashes). Conversely, displaying a too-small image in a large area leads to pixelation.

Therefore, when using remote images, it is recommended to resize them on the server beforehand. For static images bundled with the app, you can use suffixes like @2x and @3x to provide versions for different pixel densities.

Conclusion

Throughout this article, we explored the core principles, structure, and practical aspects of the topic at hand. From foundational concepts to real-world implications, we aimed to provide a clear and accessible overview that bridges theory and practice.

Understanding these ideas is essential for anyone looking to deepen their expertise and apply knowledge effectively in real-world scenarios. Whether you're a beginner or an experienced professional, mastering these fundamentals lays the groundwork for more advanced development and confident decision-making.

As technology continues to evolve, staying informed and continuously learning remains key. We encourage you to experiment, ask questions, and keep building — because progress comes through practice.

Author
Sergey Kualkov is a seasoned software engineer with over a decade of experience in designing and developing cutting-edge solutions. His expertise spans multiple industries, including Healthcare, Automotive, Radio, Education, Fintech, Retail E-commerce, Business, and Media & Entertainment. With a proven track record of leading the development of 20+ products, Sergey has played a key role in driving innovation and optimizing business processes. His strategic approach to re-engineering existing products has led to significant growth, increasing user bases and revenue by up to five times