React Native for Web is one of the most amazing ideas I’ve stumbled upon in a while. For UI developers, it makes a longtime dream a reality: the ability to create an application that runs on both phones and browsers with just one codebase.
The path we followed to arrive at this point is also really interesting:
- First, React appeared and changed the way we think about creating web apps
- Finally, React Native for Web was created to take those React Native applications and make them run in browsers again
React Native as a universal UI language
The need for the final step above is a bit unclear at first. We already have React, which was initially created to build web apps. Why would we use something else that wasn’t made to fit that explicit purpose?
The first reason is that React Native uses a kind of subset of React to generate the UI. If we want a code that runs in both mobile and web, we should stick with the more restrictive one; in this case, it’s React Native. As long as we don’t use modules that require some native functionality, a React Native app should work in browsers through React Native for Web out of the box.
The second reason — and what really makes React Native superior to React for creating universal apps — is that React Native is a pure UI language. It defines some base components that define UI primitives, and those are thought to be independent of the platform that runs them. All the components we can create in React Native are based on primitives like
<Image>, which are basic elements that make sense to any visual interface, no matter where it is run.
On the other hand, React’s primitives are just DOM nodes — HTML tags like
<a>, which are not pure UI. They weren’t created to define a visual language; rather, they’re meant to structure and make sense of the hypertext. React’s primitives have meaning beyond the interface, and that meaning doesn’t make much sense outside of browsers.
Nonetheless, it’s possible to translate React Native primitives to the DOM language by using HTML tags — that (and more) is what React Native for Web does for us.
At this point, we have made sense of why it’s a good idea to use React Native as the common language for web and native. I am convinced it’s the way to go, but React Native for Web has been with us for a while, and it still struggles to spread, especially among web developers.
Two ecosystems for one platform
Using React Native for creating web apps isn’t simple, and not just because of the restrictions we need to fit it — it’s like native and web are still two separate worlds. Even if it’s already possible to use one codebase and run it everywhere, that code is full of conditions that can be run in one environment but not in another.
The same could be said of the libraries we can use to develop a React Native for Web app.
React Native libraries
On the one hand, we have React Native libraries. We should be able to take these libraries and plug them into our React Native for Web project with no issue (unless they run native code or have native dependencies).
Even when we find a React Native library that’s compatible with web, the process of making it work in our web app is not straightforward. In order to build our web apps, we use webpack as the bundler (more on this topic later), and it usually doesn’t transpile the files inside our
node_modules folder. React Native libraries don’t need to be bundled to work for mobile, so we need to add exceptions into our webpack’s module configuration in order to take them into the bundling.
If you are a React Native library developer, please 🙏, show some love for React Native for Web and ship a transpiled version in your libraries. Nowadays there are tools that make it really easy, like microbundle or bili.
On the other hand, we have React libraries, which are generally thought to work in browsers. They make use of HTML tags to structure the UI; thus, if we use them in our universal app, they will break the mobile version.
If you are a React library developer, and you think your library would make sense for mobile environments, you should know it’s possible to make it work in React Native as well by using the same primitives as React Native through react-primitives.
In the beginning, it’s a bit tedious to get in the flow, but when you get used to the primitives and Flexbox, it can even help improve the organization of your code. Moreover, you will be learning React Native, which is very handy to have in your tool belt.
Building a universal app with React Native
Another pain we must suffer when creating a universal application is managing multiple bundlers. The standard method of developing and building apps in React Native is by using the Metro bundler. Made by Facebook, Metro allows us to build and test apps locally with almost no need for configuration. It’s shipped when you create a React Native application using Expo.
When we want to create the web version of our app, React Native for Web recommends using webpack as the bundler. Don’t get me wrong — webpack is great, but the need for two different build systems with two different types of configuration is a pain for my one head.
Merging the worlds of mobile and web
I’ve been talking about how React Native and ReactDOM developers can adapt their libraries to make them friendly with each other, and that should be just the first step.
Ideally, we would move toward unifying both worlds into a universal-react developers community. This is more than just adapting libraries not to break in any environment; rather, it’s creating tools that cover the needs of both worlds and add value to them.
As mentioned before, React Native for Web apps are really influenced by best practices for mobile. At the moment, when we create a React Native for Web app, we more or less adapt mobile apps to work in browsers. But do we really want a phone app, with its full-width layout elements and collapsible drawers, running in a big-screen desktop browser? Probably not.
That’s why the responsive layout revolution started in the mid-2000s: to make our designs adapt to different screen sizes. This is now a basic feature for any web project. In mobile apps, responsive UIs aren’t very common. But wouldn’t it be great to build apps that adapt to phones, tablets, and desktop without the need to code twice?
Responsive layouts are just one example of concepts from the web that can add value to mobile development. Another good one would be URLs. We don’t need to use URLs in our mobile apps, but the concept of assigning a unique identifier to our screens and direct access to some point is really powerful. This, too, is uncommon in mobile apps, which is why it’s rather difficult to handle URLs in React Native for Web apps when it should be pretty basic.
React Native for Web makes a great effort to take mobile goodies to web development, but there is not much in the other direction yet. In general, we should start thinking purely in terms of UI, no matter what platform we are addressing — and we are not at that point yet.
It’s hard to define hover interactions in React Native because when it was created, we thought they wouldn’t be necessary. For that same reason, gesture interactions in browsers are no piece of cake. All these cases should be covered with ease by a language that aims to be the universal approach to defining UIs.
Welcome to the new world
Creating universal applications is a dream that came true, but like most things that are just starting out, there is still much to do to make it easy for everybody.
This is, as yet, a kind of unexplored world. As a library developer, I think it’s a great opportunity for us to build the next set of tools and libraries that will be used by the community of tomorrow. Wouldn’t you like to be the creator of the “next Redux”?
I want to make a special call to the Expo team to add support for React Native for Web to the Expo environment since I think they could play a major role in the transition. It would be a huge push for universal applications that the most famous development platform for mobile shipped web bundles out of the box.
Making Metro work for web would open the web development door for Expo (a huge community there), but educating React Native developers on mocking their native dependencies to work on the web will guarantee that your projects eject when you want them to. A win-win situation for everybody!
Is it worth creating a universal app using React Native?
Definitely yes! Trying to run the same project across all platforms is certainly still challenging, and there are many gaps left to fill. But we can already use React Native as the one language to create the UI for different versions of our app, which reuse most of the same code even if they target different environments.
If you feel like helping solve some of the issues above, you will be pushing the dream of universal app development forward, and the whole community will be grateful to you.
If you just want to maximize your code reusability, set up a Lerna project, with mobile and web versions of your app as separate packages, and start creating your components using React Native. You will be reusing the whole UI, but you will be able to tackle platform-specific problems individually — no need to worry about compatibility.
If you are interested in this approach, I strongly recommend you read the article Building Cross-Platform Applications with a Universal Component Library from Lucas McGartland.
LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.