I'm going to showcase the React Native architecture we use in our new mobile app at Product Hunt. What we learned, among the way. How we moved what we know from web to mobile. Topics will be designing reusable React components, GraphQL, routing in the app, application lifecycle, keyboard controls, toast messages, and others.
React Native Architecture at Product Hunt
AI Generated Video Summary
The speaker discusses their React Native architecture and how they structured their app, choosing React Native over native development due to limited resources and the need for UI experimentation. The architecture consists of support, components, and screens, with a focus on code organization and extensibility. The speaker explains the screen creation process, including the use of React Native Navigation and a state machine pattern. They also highlight the use of GraphQL for data retrieval and navigation between screens. The talk covers the structure of common components, utilities for styling, and support for dark mode. Overall, the speaker emphasizes the importance of isolating dependencies and utilizing a directory structure approach.
1. Introduction to React Native Architecture
Hello, everybody. My name is Radoslav Stankov, and I'm coming from Bulgaria. Today I'm going to talk about our React Native architecture and how we have structured our React app. Produhunt is this website. When we decided to rebuild our mobile app, we had two choices: go native with Swift or go React Native. We had a terrible experience with React Native in the past, but we only had two developers with Swift experience and no Android experience. We also needed to do UI experimentation and support Android, so we chose React.
Hello, everybody. My name is Radoslav Stankov, and I'm coming from Bulgaria. I'm here in Bulgaria, sitting right now. And I'm head of engineering at Produhunt.
And today I'm going to talk a bit about our React Native architecture and how we have structured our React app. But before we get into the technical details, for every good architecture, you have to get the context, like where the team is coming from, what the requirements are, and how the stuff is built.
So Produhunt is this website. This is how we look on the web. And I have given previous presentations around the Produhunt architecture. I won't go into details there. But when we decided to rebuild our mobile app early this year, actually we released it this year, we started a bit later than that. We were wondering, OK, now we should do a big reboot into our mobile app. And for our cases, we have basically two choices.
So early in 2020, we were making mobile app experiments for YourStack. So early in 2000, we worked on two apps for a couple of our products and experimentation. One was YourStack, the other was called StackCamp. They were both not released because they were just embedded. We tested some ideas there. So, yeah, don't tell anybody. So from them, we were able to compare the ecosystems from React and Swift. And we decided to go with React just because we needed Android.
2. React Native Architecture Overview
Our team already knew React Native and decided to use it along with GraphQL and Apollo. We prioritize making common operations easy, organizing code, isolating dependencies, and ensuring extensibility and usability. Our architecture consists of three pillars: support, components, and screens. The directory structure of our mobile app reflects this architecture. We start with the top layer, the screens, which are defined in the app GS and utilize React Native Navigation.
The way I'm thinking about the parts of the architecture, I'm seeing three pillars. The support, the components and the screens. And they look like that in my head. We have a big support area which helps us build the core reusable components and then we build the screens on top of that. So for somebody more usual, this is the way you can imagine it. You have the pilots, you put them in the lines and they make Voltron and make it really nice. So if you go and see our directory, this is how the directory structure of the mobile app looks. And it represents the architecture of this app. And if you see, if I color code it here, we have support, components and the screens.
So again, talk chip, show me the code. So let's start with the top layer because it actually helps us understand the layers of both if we start reverse. From the screens. So all the screens are defined in the app GS. And here, what we do is we use the react native navigation.
3. React Native Screen Creation and Structure
We experimented with different navigation options, but React Native Navigation fit our working style the best. Our main screen consists of a main stack that attaches the screens. The screens are organized in a screens folder, and each screen is exported from an index file. Each screen has its own directory, which includes private components, a graph query for data retrieval, utilities, and the index JS file. Screens follow a state machine pattern, starting with a layout, transitioning to a loading state, and handling error and loaded states. We extracted a utility called CreateScreen to simplify screen creation by defining the screen's name, query, query variables, component, and type.
We experiment with the others navigation, but this one was the one that fit our working style the best. It's a really good library and we didn't have almost any issues with it.
So this is how our main screen looks like, like a summarized version of it. So we have this main stack that we are attaching the screens. And notice that here we are just doing a spread on the screens. So the spread of the screens, the screens come from this screens folder and it's in index, what we do is we just export all the different screens. So we have a central place where a developer can actually trace where their screen is. It's like a map and for screens, this is how a screen looks like.
We basically have a flat list to where, on the root we have the screen name. Then we have a directory where we have everything related to the screen. Like for example, in this screen, we have two private components, which search and the fit item. So those are components only used in the screen. We have the graph query, the query that will get the data for the screen. We have some utilities and the index JS is the screen.
If we think about the screen, the screen is actually a state machine and this state machine starts with a layout. Then it goes to the loading state inside of this layout. If something bad happens, there is an error state and this error can be a server error, not found error, authorization, authentication, and similar errors. On the other side, we can have the loaded state. So when the screen is loaded, there is a couple of operations we always need to do. We need to render the screen. And we notice that we have a lot of this state code in our system.
So what we did was we extracted an utility we have on the web called CreateScreen. And this CreateScreen allows us to define, okay, what's the name of the screen? So it's very good, useful for debugging. What's the query, which would be used to go to the data. What are the query variables for this query? Which is the component this screen is going to render, and we just pass with this component the data and the params. What type of screen is this? In our application we have three types of screen. We have a screen which is like push screen, which we just push it back. We have an action sheet, which just opens like an action sheet. And we have an overlay, which is an overlay. Also there is a couple of other options like the screen background.
4. React Native Screen Creation and Navigation
Different screens in our system have different backgrounds and titles in the navigation. We handle different screens by using a create screen command. Our app architecture involves screens, queries, components, and hooks. GraphQL connects everything in our app, providing type safety and the ability to nest fragments. We also utilize a cool trick called routes, which allows easy navigation between screens.
Different screens in our system has different backgrounds, what's the title in the navigation. And there is this safe area, which is the iPhone X style of no home button. And how do we do in different screens? So every developer can just build a screen with calling this command.
So what we have is we have the screen, which is create screen, it goes through the screen index and then goes through the app. And here's an example screen. This is kind of the home screen we are having. We just have a query with its variable. And then we have the component and we use two hooks to set up the live navigation. We set up the push notifications and we render an infinite scroll of items. And again, GraphQL is the thing which also connects everything in our app. So in the backend, we use Rails and we export the GraphQL. The GraphQL goes to Apollo and Apple will help us generate TypeScript. So using the Apollo code gen, we can take the fragments from GraphQL and converse them into types and TypeScript. And we can use them in our pages. And this gives us this type safety. And the nice thing about it is we can nest those. We can attach fragments with more fragments. And this is how we start. The way we start is from the query from the page and then it gets the fragments and it goes down to the component level. And for me that's still magic. It's very magical to go to the back end, test the change, and the mobile app complains that this field doesn't exist anymore or this is null, but you expected not to be null. And this connects the whole app.
Another cool trick we use is called routes. I haven't seen this in React Native Apps, so I think it might be very useful for everybody. So what we have is we have this file where we just explore this routes list of, okay, we basically say our screens are routes. So, for example, we have a log in profile route because in our app on every screen you can go to pretty much any other screen. You can just say, okay, this is the description where this thing would go. And we have those hooks to navigate between the different screens. And you can say, okay, I have a route, go back, log in screen, profile screen. And I can use this in a button.
5. React Native Button and Component Structure
I have a button that navigates to a product with ID5. We have a core button component with built-in functionality. Our components are used in multiple screens and organized in a directory structure. We have common components, such as text and buttons, with built-in functionality. We also have layout components, including flex row, flex column, and helpers like flex expand and grid. We have utility, styling, and domain components, with deep nesting and a focus on atomic design. Lastly, we have utility functions for various purposes.
I can say I have a button and it navigates to the product with ID5. And this is a very common thing. So what we do is we have built into our core button component. And our core button component just has this.
Well, the next step I'm going to talk about is the components. Those are basically things that are used in more than two screens. And more than one screen, actually. So we use the same component as directory where the root folder shows all the public stuff. The private stuff is all nested. Some components even have, like, 45 levels of components. We keep the graphQL here, the mutation, the index, is where we export. And we have pretty common components. We have our own version of text, so we have consistent text sizing. We have this spacing structure. The other thing we have is, again, the usual buttons. And with us, buttons are not only style, but buttons are also functionality. So we can say, okay, button can link to a page. If the function, which is clicked on, press returns a promise, the button doesn't allow you to tap it again. We also have built-in confirmation, require login, and also it's built-in with GraphQL. So the button can execute a mutation and handle the mutation results. Also, we have packages for buttons. We have a button which is just text, a button which is an icon, a solid, and an outline button.
For layouts, we have extracted out the built-in flex system in Apollo in React Native because we basically got tired of writing the same style over and over again. We have this flex row and flex column and a couple of helpers like flex expand, which expands the flex to the call screen, a grid, which is those dots, and a text, which is basically a hack to have texts float properly with React Native. And as I mentioned, we have utility, styling, and domain, so we actually have also domain components. Like this is the homepage, and if we see the post item, it has a bold button. And this bold button uses the button component, but also the post item uses the bold button and the button, so we have this deep nesting of utility components and domain components. It's kind of like an atomic design, just kind of, with our case, we just say, OK, we have the generic components, and then we have the domain components, which are related to a domain entity. Usually, differences, one have fragments, the other don't. And the final thing I wanted to share is the utilities.
6. Utilities and Styling in React Native
I want to tell about some of the utilities we have in our system, focusing on styling. We added a helper for margin and padding, which proved to be very useful. Another feature we implemented was support for dark mode using React Native Dynamic. We have built-in support for dark mode and have a core set of components, as well as domain components that utilize GraphQL. Overall, we have isolated our dependencies and used a directory of construction approach.
I want to tell about some of the utilities we have in our system, and they just live in utilities, in hooks, and in styles. We split it into three folders, because for engineers we have found out it's good to have a dedicated place for things like hooks, and we know that the files there are very special. I want to focus on styling.
One problem which we were writing a lot of styling for was margins and padding. So what we did was we added this helper which we use in our core component, which is, we have those properties for margin and padding. So here is something that on the web, we use the flex-gap property, but this is not something we can implement with React Native. And because of this, we just have this styling helper where we have margin, bottom, padding, and all the associations. And every component can have this spacing prop, so we can add every spacing to those components, and this was very useful.
The other thing which was part of the new feature set was having support for dark mode, which is a cool feature. So we use this package called React Native Dynamic, and we created a wrapper around it where you can use this dynamic style sheet, and depending on the color, you decide what color you are using. And we have a couple of helpers and style themes like, for example, use color background. And we have this color map, which is the dynamic value. So on text with this color, the subtile text is with this color, and every color in our app is defined to this color and has to go to React Native Dynamic. So we actually have built-in support for dark mode. And this is basically how the whole application combines. Like, we built on the foundations, which are the utilities. And we have a lot more smarter utilities. They are a lot more internal helpers. But the one I showed you, I think, are the most memorable and most useful, and I haven't seen a lot of those used in other applications so far. And then on top of them, we build this core set of components, which is we have this core set, which is things like buttons, text, infinite scroll, stuff that they don't care about the domain logic. And then we have the domain component, which is like post button, post button, post item, post thumbnail, which are basically things with fragments, things that take stuff from GraphQL. And then this, the whole thing got combined into the pages, which are wrapped with this help or create page. So to recap that, if you want to take some takeaways from my talk is, yeah, GraphQL is awesome. It helps a really nice to design a system which uses the GraphQL everywhere. From screens, create screen is something which I'm surprised I haven't seen many people use a pattern like that or the screen scalper. And for components, notice how we have isolated a lot of our dependencies. We use directory of construction and we have this concept of domain component. So, yeah, thank you very much. And that's all from me.