Functional React — Get your App outta my Component

Adam Terlson
10 min readAug 2, 2016

--

Alternative titles I should have used to get more readers:

“5 simple code hacks for component reuse the backend devs don’t want you to know about!”

“This article proves you’ve been reacting wrong your entire life!”

“Using children is a GOOD thing? First you’ll be shocked, then you’ll be inspired!”

Introduction to Functional React Series

When I started writing React apps, I approached components as if they were “just the V in MVC!” Seriously, we’ve all heard it.

I have found this to be an inferior way of thinking about and building React applications. It makes people treat React as a drop-in replacement for something like a Backbone or Angular 1.x View. In other words, people treat it like a glorified template system with partials and don’t harness the power of its functional paradigms.

Unfortunately, I find many people continue to operate under this line of thinking, and blog posts and guide articles about “Best Practices” subtly lend their support by, in but one example, using HTML controls and CSS in every presentational component they ever show.

This series is about a functional way to write and conceptualize React applications.

In this series-of-indeterminate-length, I will articulate what I have come to find to be a superior approach to building React applications — treating them as functions, not templates.

I will describe how to:

  • Separate the application you’re building and the platform its on from the components you write (Hey, that’s this one! Read on!).
  • Inject state into your application, and what a “container” aka “connected” aka “smart” component really is.
  • Implement more complex features by leveraging Higher Order Components and a compose function — it’s awesome.
  • Make your application UI understandable by looking at a single file, not dozens.
  • Make your codebase flat without sacrificing grokability.

It’s not ambitious or anything.

This series could be useful for people of any React experience. The lessons will focus on creating a more expressive, maintainable, and reusable codebase than one would get by treating React like it’s the “V” in “MVC”.

Part 1: Get your App outta my Component

Imagine you’re building the new MyAwesomeCalculator app. The functions that are required to make the MyAwesomeCalculator work (add(), subtract(), clearInput(), submit(), etc) describe what the calculator does, but they aren’t coupled to it. For example, some other app that needs to add() could reuse that function, even if it’s not to build another calculator app.

So I say, the usefulness of the components you write should be similarly powerful and reusable beyond the application you’re building.

A New Approach: Your app as the Product

In its purest form, React is described as being “functional,” and stateless components are even defined by merely the “function” keyword (or arrow function).

Think of your app’s UI, features, routing, everything as the product of invoking these functions with some arguments. Your application isn’t what invokes the functions, your app is what gets returned! Those same functions could be invoked with different arguments in a different order and the result could be a totally different app.

Your application isn’t what invokes the functions, your app is what gets returned!

Just like the MyAwesomeCalculator’s functional capabilities are described by and not coupled to add(), subtract(), etc, its UI should be similarly descriptive, yet decoupled.

To demonstrate, I will introduce you to the Real Life Story(tm) of a project I built using React, and the utter failure I had trying to reuse two simple components because they were written like Views.

Then I’ll refactor them step-by-step.

The App: “Event Management”

I made (with React Native & Redux) a ticketing/event management application. You know the type, show a list of events you manage, pull up a list of people who are attending, check them in with QR code ticket scanning, etc. It’s the sort of app people running events would want to use, not the people attending them.

There are things called “Events” where “Users” can sign up to attend. When they do so they become “Guests” and are on the list to be checked in at the door.

The Components I want to Reuse

One screen of the app shows information related to someone attending an event (a “Guest”). I made this by creating a component called “GuestDetails” and routed to it using the Navigator component provided by Facebook.

Top half of the GuestDetailsScreen. In blue, the GuestHeader component. The circle within is the GuestProfilePhoto.

Code of GuestHeader and GuestProfilePhoto

As I was filling out my GuestDetails component, at some point I realized that my code was getting a bit long. It was time to split some stuff out and make some More Awesome React Components!

// Note: These components are a case study in what NOT to do.// Some things have been simplified for code clarity and I've put
// two components in the same block here for brevity.
import React from ‘react’;
import { View, Text, Image } from ‘react-native’;
import { GuestShape } from ‘./entityTypes’; // Validation for Guests
// GuestProfilePhoto.jsfunction GuestProfilePhoto({ guest }) {
return (
<Image source={guest.userImage} style={...} /> // Rounded Img
);
}
GuestProfilePhoto.propTypes = {
guest: GuestShape.isRequired
};
// GuestHeader.jsfunction GuestHeader({ guest }) {
return (
<View>
<View style={{ flex:1, ... }}> // Centered container
<GuestProfilePhoto guest={guest} />
</View>
<Text style={{ fontSize: 20 }}>{guest.fullName}</Text>
<Text style={{ fontSize: 10 }}>{guest.countryOfOrigin}</Text>
</View>
);
}
GuestHeader.propTypes = {
guest: GuestShape.isRequired
};

Look! I’ve got reuse in mind already! I needed to use the same PropTypes for GuestHeader and GuestProfilePhoto without repeating the validation, so I stored the PropType shape validation for `guest` centrally and imported it from any component that took the `guest` as props.

I’m sure that’ll work out well….

Let’s Play “Reuse the GuestHeader”

Update 8/3: Though the original version remains intact below, based on feedback I’ve received I wish to clarify for the reader that “rules” in articles such as this are fickle things, and should not be considered strictly. I use the word here to mean something more like “shit that’s worked for me.” Onward!

To enumerate all the ways I’m an idiot and GuestHeader, GuestProfilePhoto and all components like it suck, let’s try to reuse them.

The Naming is Wrong

What if I wanted a similar looking header on the screen for users that aren’t attending an event? They’re not Guests anymore, they’re just Users at this point. Okay, so how about something more generic like “UserHeader” and “UserProfilePhoto”?

But, what if I want to reuse these components for displaying event information?

Rule #1: Do not name your components after the part of state they connect to.

Rule #2: Do not name your components for the role they play in YOUR application.

The moment a component is named for a particular use case, well then that’s the only use case it can be used for! In order to be reusable, presentational components must be named as if they can be connected to any piece of state and display any information.

So how about…

// Rename GuestHeader to:
function ScreenHeaderWithImage({ guest }) { ... }
// Rename GuestProfilePhoto to:
function CircularImage({ guest }) { ... }

Ship it! Oh, wait.

The Props Are Wrong

Our component is no longer named in such a way as to couple it to a particular screen’s usage, but those props sure are. My components can only work if I pass a prop called “guest” and that’s hardly reusable.

Further, looking at the cases where `guest.something` exists in the ScreenHeaderWithImage component, `guest` must have at least the following shape:

// To satisfy ScreenHeaderWithImage, Guest's shape must include:
{
fullName: '',
countryOfOrigin: ''
}

But there’s more.

...
<CircularImage guest={guest} />
...

The entire Guest object is passed to a child component for displaying the Guest’s photo. Now the GuestHeader must be passed whatever props this thing needs in the appropriate shape or this child will break!

So, looking into that component I found that it needs a single property called `userImage` on the `guest` object. Adding that to our required props we need:

// To satisfy GuestHeader and children, Guest's shape must include:
{
fullName: '',
countryOfOrigin: '',
userImage: ''
}

What a maintenance headache! If the shape of this were to change, it breaks two different components, and it’s totally unclear from the outside what props are even used or how!

Rule #3: A component should be given ONLY the props it requires. This structure should* be flat.

* One exception is when your individual parts of data are meaningless by themselves (e.g. tuples, e.g. an XY coordinate).

So, we could break up the props then and pass each individually:

function ScreenHeaderWithImage({ 
fullName,
countryOfOrigin,
userImage
}) { ... }

This is undoubtedly a step in the right direction. But we can do better. What if I don’t want to display a userImage, but instead an image of my favorite cat (Jasper)?

Rule #4: Props should not be named according to the application state that gets passed in. Instead, they should be named according to their usage within the component itself.

This is just like any function you write, but is sometimes overlooked on React components. In the end, the prop is in reality any string that’s used as the source for an image, and so should be named accordingly. With that rule in mind, we get:

function ScreenHeaderWithImage({ 
primaryHeadline,
secondaryHeadline,
imageSourceUrl
}) { ... }

The PropType Validation is Wrong

Let’s look back at the original validations being used:

// GuestShape was imported in the previous component code snippet, 
// but is shown inline here for stupidity demonstration purposes.
const GuestShape = PropTypes.shape({
id: PropTypes.number,
fullName: PropTypes.string,
isPremium: PropTypes.bool,
userImage: PropTypes.string,
countryOfOrigin: PropTypes.string,
countryCodeOfOrigin: PropTypes.string,
gender: PropTypes.string
});
GuestHeader.propTypes = {
guest: GuestShape.isRequired
};
GuestProfilePhoto.propTypes = {
guest: GuestShape.isRequired
};

Nothing is `required` in GuestShape!

I was trying to reuse the same validation shape across multiple components, and different components needed different parts of it, so nothing could be required!

By attempting to reuse PropType validation on application-specific state used as props, my state shape could change, break my app, and I wouldn’t even get so much as a warning!

Now that we’ve changed up our component’s prop structure though, we can easily fix this critical point of failure:

// Rest easy, friend.  I got ya.ScreenHeaderWithImage.propTypes = {
imageSourceUrl: React.PropTypes.string.isRequired,
primaryHeadline: React.PropTypes.string.isRequired,
secondaryHeadline: React.PropTypes.string // Optional
}

Glad that’s gone….

The Component Coupling is Wrong

We still have one problem, and it’s a subtle one that by itself warrants its own article. It’s that the ScreenHeaderWithImage component is coupled to the CircularImage component. What if I want a SquareImage? Or no image at all?

One solution would be to pass in a prop or twenty for this:

function ScreenHeaderWithImage({ 
primaryHeadline,
secondaryHeadline,
imageSourceUrl,
hasImage
}) {
if (hasImage) { ... }
else { ... }
...
}

… but this is not sustainable and is still limiting. Instead, React gives us a far more powerful tool to handle the situation where a component wishes to not care about what child component(s) are used.

Composition!

Rule #5: Composition is superior to import statements at the top and is the primary mechanism which allows your component to focus on doing ONE thing. Use it wherever, and whenever you can.

By composing, a parent can become decoupled from its children and this allows for its own usage and function to become more granular and clear.

Wherever you are taking in some props and merely passing them unmodified to a child component, that’s also a good indication of a spot to use composition instead.

function ScreenHeader({ 
primaryHeader,
secondaryHeader,
children
}) {
return (
<View>
<View style={{ flex:1, ... }}> // Centered container
{children}
</View>
<Text style={{ fontSize: 20 }}>{primaryHeader}</Text>
<Text style={{ fontSize: 10}}>{secondaryHeader}</Text>
</View>
);
}

The ScreenHeader component is now the epitome of reusable!!!

Assuming you’re using React Native….

The Platform Coupling is Wrong(ish)

Let’s go crazy. What if I want to display this on the web? I can’t use this component because it’s using React Native components (View & Text) as well as styling that doesn’t exist outside of React Native….

The good news is that React is just the tool for the job.

// Replace the "View" with a generic container component
import
Container from './Container';
// Replace the "Text" with more semantic components
import PrimaryHeadline from './PrimaryHeadline';
import SecondaryHeadline from './SecondaryHeadline';
function ScreenHeader({
primaryHeader,
secondaryHeader,
children
}) {
return (
<Container>
<Container centered>
{children}
</Container>
<PrimaryHeadline>{primaryHeader}</PrimaryHeadline>
<SecondaryHeadline>{secondaryHeader}</SecondaryHeadline>
</Container>
);
}

The bad news is that the problem is just pushed forward. The new components look something like this:

// Container.js -- Depends on React Native :(import { View } from 'react-native';const center = {         
flex:1,
flexDirection:'row',
alignItems:'center',
justifyContent:'center'
};
function Container({ centered }) {
let style = {};
if (centered) {
Object.assign(style, center);
}
return (
<View style={style} />
);
}
------// Text.js -- Depends on React Native :(import Text from 'ReactNative';
function RNTextWrapper({ size }) {
return <Text style={{size}} />;
}
------// PrimaryHeadline.js -- Semantic titles, requires only Text aboveimport Text from './Text';
function PrimaryHeadline({ children }) {
return <Text size={20}>{children}</Text>;
}
------// SecondaryHeadline.jsimport Text from './Text';
function SecondaryHeadline({ children }) {
return <Text size={10}>{children}</Text>;
}

But, the good news is that all you need to do now is rewrite these TWO components (Container and Text) to support multiple platforms!

In theory :)

TL;DR All the guidelines in one place

The idea: Presentational React components should not know about your company, its product, or the platform it’s built for. None of them exist.

#1: Do not name your components after the part of state they connect to.

#2: Do not name your components for the role they play in YOUR application.

#3: A component should be given ONLY the props it requires. This structure should be flat.

#4: Props should not be named according to the application state that gets passed in. Instead, they should be named according to their usage within the component itself.

#5: Composition is superior to import statements at the top and is the primary mechanism which allows your component to focus on doing ONE thing. Use it wherever, and whenever you can.

Here are some common ways presentational components “know” about a product:

  • By using names for a particular part of the application’s state (e.g. EventStats)
  • By using names based on the role the component plays in the application (e.g. RegistrationConfirmation)

And some common ways presentational components “know” about a platform:

  • By styling via CSS or RN style definitions rather than with components and semantic props (e.g. assigning props which dictate presentation is fine, just do `color={”success”}` rather than `color={‘#00cc66’}`)
  • By using controls provided by React or React Native (e.g. div, button, View, Text, etc)
  • By doing anything that depends on a browser or native app context (e.g. urls, accessing window, checking screen width, etc)

I will expand much further on this topic, talk about state, and many more tricks the backend devs don’t want you to know (!) in future parts of this series, so ❤ for updates.

--

--

Adam Terlson

A passionate software engineer from the US. Loves building things. Bad at social media.