React Native Architecture at Product Hunt


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.


Hello everybody, my name is Radoslav Stankov and I'm coming from Bulgaria, actually not coming, 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 big 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 and I won't go into details there, but when we decided to rebuild our mobile app early this year, actually we released it early this year, we started a bit later than that, we were wondering, okay, now we should do a big reboot into our mobile app. And for our cases we have basically two choices. One was go native with Swift and the other was go react Native. We really had really terrible experience around more than two years ago when initially we started react Native, which was quite a bit of a challenge. On the other side, we have only two developers who have some Swift experience, not very new ones, and we have zero developers with Android experience. And our app, we needed to run it on Android as well. Also, Apple, so we use graphql for all our data transferring and the best way to use it is apollo, in my opinion. apollo for Swift is not as good as apollo for javascript. And our team already knew how to deal with react and apollo. The other thing we needed for this app was to do a lot of UI experimentations. And again, we also needed Android. So early in 2020, we were making a mobile app experiments for your stack. So early in 2000, we work on two apps for a couple of our products and experimentation. One was your stack, the other was called Stack Camp. They were both not released because they were just in beta. 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. Our team already knew react Native and react Native actually become a lot better than two and a half years ago. It got a lot faster, a lot more stable, and we didn't have the same issues as we had before. And the test stack we decided to go was, okay, we can go with react and react Native. We use graphql, we access it to apollo, and the table stakes, typescript, Jest, and Pretier are basically not in the question if we go with the javascript ecosystem. And everything done mobile, we are using Fastlane for tooling. So the things around architecture is if you don't know where to start, you can very easily get into the situation where you get stuck. So we want to move fast and have breakfast, not breaking things fast. So we want to move. So for our architecture, we have four goals. First is to make the common operations easy so the engineers can easily add new things. Then we need to put code organization so we can onboard new team members very easily into the code base. Isolate dependencies like in the javascript world and in basically every modern world we're dealing with external dependencies, and they can very easily get out of sync, out of date, and break a lot of things without our control. And the final thing is extensibility and usability. We want to build things that we can extend and reuse over time. But the most common thing for us is make the common operations easy so everything can develop fast to do their daily day should be very easy for them. And 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 if for somebody more visual, this is the way you can imagine it. You have the pilot, 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 like. 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 is cheap, show me the code. So let's start with the top layer because it actually helps us understand the layers above 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. 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 and we are attaching these 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 where we have on the route, we have the screen name. Then we have a directory where we call, where we have everything related to the screen. Like for example, if in this screen, we have two private components, we search and the feed 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 the 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 noticed 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 create screen. And this create screen 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 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. A screen which is like push screen, which we just push it back. We have an action sheet, which just opens like a action sheet. And we have an overlay, which is an overlay. Also, there is a couple of other options like the screen background. Different screens in our system has different background. 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 to the screen index and then goes to 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 layout 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 to our app. So in the back end, we use Rails. And we export the graphql. The graphql goes to apollo. And apollo can help us generate typescript. So using the apollo Code Gen, we can take the fragments from graphql and convert them into types in 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 are 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 app. So I think it might be very useful for everybody. So what we have is we have this file where we just export this routes list of, OK, we basically say our screens are routes. So for example, we have a login profile around. So because in our app, we have a screen, you can go to pretty much any other screen. You can just say, OK, this is the description where this thing would go. And we have those hooks to navigate between the different screens. And you can say, OK, I have a route. Go back. Login screen, profile screen. And I can use this in a button. 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. So 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. Sorry. So we use the same component as directory, where the route folder shows all the public stuff. The private stuff is all nested. Some components even have like 4 to 5 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. Like with us, buttons are not only style, but buttons are also functionality. So we can say, okay, button can link to a page. Button can, 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 button. We have a button which is just text, a button which is an icon, and solid, and an outline button. For layouts, we have abstracted 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. So we have this flex row and flex column, and a couple of helpers like flex expand, which expands the flex to the cold screen, a grid, which is those, those, those, those, those, and a text, which is basically a hack to have texts float properly with react Native. And as I mentioned, we have utilities, 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, okay, we have the generic components, and then we have the domain components, which are related to a domain entity. Usually, the difference is one has fragments, the other doesn't. And the final thing I wanted to share is the utilities. 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. So I want to focus on styling. One problem which we were writing a lot of styling for was margins and paddings. 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 sub-tile text 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 a built-in support for dark mode. And this is basically how the whole application combines. We built on the foundations, which are the utilities, and we have a lot more smarter utilities. There 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, the 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-roll button, post item, post thumbnail, which are basically things with fragments, things that they start from graphql. And then this, the whole thing gets combined into the pages, which are wrapped with this helper create page. So to recap that, if you want to take some takeaway from my talk is, yeah, graphql is awesome. It helps a really nice to design a system, which use the graphql everywhere. From screens, create screen is something which I am surprised I haven't seen many people use the pattern like that or the screen scalper. And for components, notice how we have isolated a lot of our dependencies. We use directory as construction, and we have this concept of domain component. So yeah, thank you very much. And that's all from me. Thank you.
18 min
14 May, 2021

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic