Technology

Kristina Vragovic

Bringing Rails Convention to the Wild West of JS

Aug. 22, 2018

Necessity is the mother of invention — but Rails is the mother of convention. And sometimes, what you really need is some structure.

Here at 20spokes, we have branched into mobile in a big way, using React Native with Rails APIs. As a consultancy, we have the unique perspective of building projects from scratch often, and we kept coming back to folder structure. Namely, how on earth does anyone find anything in a React Native (or React.js) app as it grows?

Most of the React tutorials we saw out there had three main folders for their files: elements, modules, and components. (Redux projects will sometimes expand that to include an actions folder.) But try getting someone to actually define what an element is, versus a module, versus a component, and the argument quickly becomes circular. Elements are reusable, they say. Well, so are modules. Modules are bigger, though. Sort of.

So we decided to write our own rulebook. Trash it if you like, but it’s worked pretty well for us so far, and going back to maintain projects we built before we established this style? The difference is mind-boggling.


    |_ __tests__
      |_ ...mirrors the src folder
    |_ android
    |_ assets
    |_ ios
    |_ src
      |_ actions
      |_ contexts
        |_ Profile
          |_ modules
            |_ AvatarUploader.js
          |_ views
            |_ EditProfileView.js
            |_ PublicProfileView.js
          ProfileStyles.js
      |_ elements
        |_ Buttons
          |_ ButtonPrimary.js
          |_ ButtonSecondary.js
      |_ helpers
      |_ layouts
      |_ navigators
      |_ utils
    App.js
    index.js

Along with this folder structure, we’ve employed babel-plugin-module-resolver to help with the dreaded strings of ../../../../ before import statements. That way, every time we need a common element in a view within the contexts folder, the import path can be as simple as elements/Buttons/ButtonPrimary.js. Learn more about that plugin here — it’s been key to our success with this structure.

Putting it into contexts

If you’re building any kind of mildly robust mobile application that makes requests to an API, you probably have several "flows" through the app. Settings. Login. Feed. Profile. Onboarding. Any "section" of the app that you’ve probably already chunked out as a feature set is probably also a context. We decided to create a folder for each of these contexts, with all the views in that context along with all of the modules/elements that are particular to that context. [1]

For example, a public profile view, an editable profile view, and an avatar uploading module would all be in a Profile context folder. If an element is reusable outside of that component, stick it in the elements folder instead. The contexts folder was the real heavy-hitter in our redesign. You’re welcome.

Let me lay it out for you

Most of the apps we create have custom navigation bars/drawers or SafeArea wrappers that need to go on every page. We usually call this a ViewWrap.js or TopNav.js, whatever the use case is. Sometimes different kinds of views have different view wraps. The layouts folder is for these — just like in Rails.

Check the GPS

The navigators folder is a handy place for all your navigation-related files.

If you’re familiar with navigation libraries for react like React Navigation, you know that each app "flow" needs a navigator file or something like it. We used to keep these in the junk pile, AKA the top level of the folder structure along with App.js and index.js. But for complex apps with lots of form flows, or different user journeys, having a navigators folder — with subfolders like Admin or Onboarding or other, more specific navigational needs — makes it a lot easier to grok the app’s navigational complexities.

Going off the Rails

As a software consultancy, we have a particular interest in making sure that our code is easy to read and maintain by another team sometime down the line. But let’s be honest — we’ve all read a codebase that we wish had been written with future developers in mind.

Going back to old projects, ones built before we adopted this new folder structure, isn’t a total nightmare (we have always been pretty good at this readability stuff, after all). But it truly is amazing how much of a difference the new structure has made. No longer do I have to wonder which Events folder is going to have EventCard.js in it — src/elements/EventCard.js or src/modules/EventCard.js? There’s only one Events folder, src/contexts/Events. Something reusable like a card will maybe be another folder deep, in a modules folder particular to events. But that’s it. No more digging!

Even if you decide not to implement our folder structure for your next React or React Native project, what I hope you’ll take away from this success story is that it’s invaluable to consider lessons you’ve learned from other frameworks. What makes Rails infinitely easy to jump into and know where to look for things is totally doable in React. So get after it!

[1] We did not know that React 16.4.2 was about to come out with this Context thing. Sorry for any confusion. Call the contexts folder whatever you want?

Kristina Vragovic

What People Are Reading

Operations

8/1/16

Good Founders Make Good Clients - 5 Traits We Look For

“Ideas are easy. Implementation is hard.” – Guy Kawasaki

As a development agency that primarily works with early stage startups, we hear all kinds of business ideas. You could even say that we're in the business of building business ideas; but only one's we believe will succeed, and not on our own. We know that, without a good founder to drive implementation, even the best ideas will surely fail. In fact, most give up at the first sign of real challenge, and their ideas never see the light of day. So while we spend time evaluating the viability of the idea, we must also consider if the founder has what it takes. Here are the 5 traits we look for:

1. Tenacious belief. Some traits can be learned or honed; this is not one of them. Startups are inherently difficult, demanding, and full of unknowns and disappointment. To make it through, a founder needs confidence, determination, passion, and the like - things you can't have without first believing. We're not talking about simply believing you have a good idea. We're also not talking about blind belief to the point of delusion. This is believing strongly enough in your vision that, despite the uncertainties and inevitable struggles in your path, you will do whatever it takes to see it through.

2. Domain expertise. Serious founders need to understand their market as much as possible. Ideally, you can directly relate to the problem you're trying to solve - or - you have extensive experience that gives you insight on your target market. Even then, the best founders do everything they can to consistently learn and absorb new information.

3. Communication. The greatest entrepreneurs are masters of communication. Not all start out that way, but its something to be conscious of and constantly improve. A good founder has the ability to communicate clearly, confidently, and candidly. They can clearly explain their thoughts and ideas. They use confidence to sell themselves and their vision, as well as to lead others. They can candidly express their feelings, while maintaining control of their emotions.

4. Head in the clouds, feet on the ground. Growing a startup requires constant innovation. A good founder has vision; they dream big and consistently ask themselves, "what's next?" However, that vision will never materialize unless a founder can execute in the present. A good founder can keep their eyes to the future while practicing self awareness, focus, patience, and responsible decision making.

5. Flexibility. The survival of a business, like in nature, depends on its ability to adapt. No matter how much a founder plans, new information will arise and circumstances will change; a good founder is prepared and willing to respond. Some changes will inevitably result in failures. A founder must have the resilience to pick themselves up, learn from the failures, and push on.

6. Enjoys the ride. I know I said 5 traits, but this one's pretty important too. Yes, starting a business is difficult, risky, blah blah blah...but what's the point if you can't enjoy it? Having fun will not only make your life (feel) easier; attracting advocates, customers, employees, and investors will be easier as well.

Now these aren't the ONLY things a founder needs to build a successful business, nor do they guarantee success, but we'd bet on you. So if you have a great idea and "what it takes" (see above), we should meet.

Technology

1/6/17

React Lessons for Newcomers

At 20spokes, developers spend a roughly equal amount of time between Ruby on Rails and React. While we enjoy working in both frameworks, they are quite different in approach, and going from a Rails way of thinking to a React way of thinking can be an adjustment.

One major way in which these frameworks are different is that Rails takes care of a lot of architectural issues that React leaves open for interpretation. Coming from a Rails background, I found the openness of React to be a bit anxiety-inducing at first, but I've come to really embrace it, because it's forced me to think more carefully than ever about how other developers would approach my code.

As a team, we've also considered what our best practices should be towards React, as we all want to make our code understandable and friendly to anyone who encounters it. To that end, below is a (growing) list of our approaches to making our React projects not only maintainable, but enjoyable to work with.

Be relentless with components

The basic building block of React is the component. To those new to React, they can best be thought of as modules.

As a developer, I start to get nervous when I see large components that perform various functions. The solution to keeping your files short and sweet is to take every opportunity to break your objects into re-useable components. Having larger components may not seem like a big deal when starting a project from scratch, but if you relentlessly component-ize you'll thank yourself as your project grows.

There are some code smells to recognize when you should create new components. If you see lots of groups of markup within a single div, those groups should probably be their own component.

If we have lots of renderXXX functions within in one component that render more markup, that's usually a code-smell that whatever is being returned from those functions should be their own component.

Make components as reusable as possible by passing dynamic data as props.

Privilege functional components over class-based ones

In many cases, it's overkill to Use React's Component class for every component you create. Not all components need access to the Lifecycle Methods or local state that Component provides. Start with stateless functional components and turn them into React Component instances as needed.

Take advantage of PropTypes

We started the practice of listing out PropTypes at the bottom of every component, so other developers can quickly reference what props are needed or optional. Oftentimes, we'd investigate what data we should expect in a component by looking up examples of that component elsewhere in the codebase. This easily can be avoided by using PropTypes, which provide a quick way to see what data is being passed, and of what type that data should be. Here's an example from our reusable Button component:

Button.propTypes = {  
  text: PropTypes.string.isRequired,
  onPress: PropTypes.func.isRequired,
  disabled: PropTypes.bool,
  icon: PropTypes.string,
}

We're pointing this out because, while Facebook already notes it as a best practice in their documentation, it's something that's easy to skip over or forget to do. But for something that's not hard to do at all, it provides a lot of value when developing and maintaining your app.

Use Lifecycle Methods with care

Despite being incredibly powerful, lifecycle methods (like ComponentDidUpdate) can cause a lot of headaches to those new to React. Be careful when updating state or props within these methods, as it may cause infinite looping.

For this reason, I prefer to place lifecycle methods at the very top of a component declaration, so I can see all of that logic together when debugging.

Check out part 2 of this series, Redux Lessons for Newcomers.