Some Thoughts on Function Components in React

Functions First Approach

A. Sharif
JavaScript Inside
5 min readNov 6, 2016

--

When writing components in React, it can be confusing to decide what a component should do sometimes. Especially for developers coming from the backend or from classic MVC JavaScript libraries. Defining the boundaries can be a complicated undertaking.

There are a number of options to choose from when it comes to building React components, including createClass, functional stateless components, ES6 class components and other concepts like containers or higher order components. There is even a page dedicated to the different ways we can build a component.

These options make it hard to figure out how to write components efficiently and when and why to choose which pattern.

The following is a collection of ideas in regards to writing React components and may or may not apply in your specific case. These are not best practices, but a personal reflection on how to approach building components. All ideas should also be applicable with other DOM based libraries
like Preact or Inferno f.e.
I might be adding updates to this post based on feedback and new learnings.

To see a detailed example of these ideas, check Form Validation As A Higher Order Component (part1, part2).

View and Logic

Separating view from logic can be achieved by defining presentational and container components, sometimes also referred to as stateful and pure and other names. Separating the two aspects is relatively easy to achieve in React by following this pattern.

That being said it’s alright to start out by adding some logic into a view component and then move the logic up the hierarchy tree as soon as we have a clear idea on how the component actually functions. Also, we need to keep in mind that we can’t always separate these two concepts and that a mix of the two is always possible to occur.

In best case, view components are actually simple functions, receiving props and returning a declaration. Which leads us to the next point.

Functions First

Why functions? Where do we put local state? How do we access refs? what about performance? There are probably a couple of questions that need answering here. One of the advantages of using functions includes a very clear separation of view and logic (see the first point), because there is no room for any logic.

The absence of the this keyword and thus lacking the ability to add functions that do internal state handling and logic further enforces this separation. Furthermore, stateless function components, as they are also called in the React world, prevent treating state as an afterthought. We can’t use setState as a quick solution. Whenever local state is an issue, one can fallback to wrapper components as we will see later on.

Refactoring

View functions make the need for refactoring easier to detect. You can start out big and break into smaller parts when needed. It’s also possible to start breaking things up inside the same file before moving everything into it’s own files. If you’re still designing, it’s easier to have everything in place then jumping around between files.

Testable

Ensure your components are testable. In best case we have small components that can be easily tested. Passing the same data in, will always result in the same data out. Small units, wether they’re view or logic related, are easier to test. Needing a complex setup to test your component is a good indicator for the need to break things up into smaller functions.

Reusability

Again, small units are simpler to reuse. The more context agnostic, the easier we can share components inside and between projects. This can be a step by step progress, where we might start off with code duplication and later on start recognizing certain patterns that we can reuse. This shouldn’t be our main concern when starting out, but we need to keep it in mind.

Higher Order Components

Use higher order components whenever we need to keep local state or need optimizations. A typical case would be when working with forms for example. We don’t want to submit any form inputs until a certain user action, like a submit, has occurred. We can use a library like Recompose and create a higher order component that takes care of this.

Take a look at the form validation higher order component for a detailed example.

Optimization

There is the assumption that stateless functional components are more performant due to the lack of lifecycle hooks. The lack of aforementioned hooks makes it impossible to optimize stateless functions once performance becomes an issue.

The problem can be mitigated by introducing pure component wrappers for example and even refactoring to a class based approach is simple, by converting the view function into a render method.

There are solutions to these problems, but they shouldn’t be a first priority when designing components. Design for readability and testability first, optimize later.

Utility Libraries

Use utility libraries like Recompose, Ramda or lodash for common tasks instead of rewriting utility functions from scratch over and over again.

Also use compose for building large blocks of functions. Avoid wrapping a stateless component inside a higher order component, export the plain function and compose for optimization when needed. This ensures that in one project we can use the low level function and in another use the higher order function via composition.

Summary

To keep it short. Embracing functions has a number of advantages but also a couple of setbacks that we need to consider.

  • Clear separation of logic and view. View functions should always be simple functions by default.
  • The absence of any methods or setState enforces thinking about application state early on.
  • Functions should either be concerned with logic or representation. This helps to add focus.
  • Testable: view functions make it easier to test, as there is no logic involved. The view only renders according to the passed in props, no need to recreate a certain state to be able to test the component.
  • Focus on reusability but start out large if necessary. Abstracting early on and hitting the wrong abstraction is harder to revert than the other way round.
  • Use Recompose, Ramda, lodash etc.
  • User Higher Order Components for pure render optimization and local state handling when possible.
  • In an ideal setup we could even use advanced testing techniques like property-based testing as we’re only working with stateless functions.
  • Use flowtype or propTypes whenever possible.
  • Documentation is easier when components are small.

For a detailed example of these concepts read Form Validation As A Higher Order Component (part1, part2).

If you have any feedback and other best practices please leave a comment here or on Twitter. 

Find me on Twitter

--

--

A. Sharif
JavaScript Inside

Focusing on quality. Software Development. Product Management.