React-native-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-web was created to take those React Native applications and make them run in browsers again
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-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 , , or , 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,
, or, 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-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-web has been with us for almost two years, and it still struggles to spread, especially among web developers.
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-web app.
On the one hand, we have React Native libraries. We should be able to take these libraries and plug them into our react-native-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-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.
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-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.
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-web apps are really influenced by best practices for mobile. At the moment, when we create a react-native-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-web apps when it should be pretty basic.
React-native-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.
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-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!
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.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.