Ramblings About React and Redux Architecture

Or, how I structure components.

kurtiskemple
6 min readAug 24, 2016

First and foremost, can we please stop with the blanket statements about XYZ tool/lib/module/framework. A lot of hard work goes into these tools that we `npm install` so flippantly.

The truth is, all frameworks or modules have benefits and tradeoffs. A good programmer understands these benefits and tradeoffs and applies that knowledge to project requirements. This type of programmer can make informed decisions about when to use a tool and when not too.

On top of that, in many cases the same job can be completed with a multitude of tools so in the end it really comes down to what works for the project and what works for you and your team.

It’s also important to only use what you need. In most cases I can get by with just React (at least to start), and then I will add a data management tool only once an application’s logic becomes too spread out to manage without one.

If you are like me and have found React and Redux to be extremely useful tools for larger apps, then this article may be an enjoyable read for you. If you hate React or Redux then you should read this anyway and maybe I can change your mind!

Component Architecture

So you may have noticed that this section is titled “Component Architecture” and not “Application Architecture”. This is because this article is not concerned with how you structure your application overall, only with how you structure your components and how you manage your data. Application architecture is too varying to tie down to a single way. There are many factors that go into play in building an application that live outside the bound of React and Redux. React and Redux are just tools to help you visualize, manipulate, and manage data.

Component Types

I find that React components generally fall into one of three categories:

  • Provider Components: These components provide data access or API access to a component tree.
  • Behavioral Components: These components control application behavior such as hotkeys, modals, search, forms, etc and are often stateful.
  • Presentational Components: These components control how data is displayed to the end user.

Presentational components work best when they are as “dumb” as possible. If you can reduce a component down to pure props it’s basically like a pure function. You can expect that given the same props, it will always render the same output.

This can’t be said for stateful components which means you can’t expect a certain behavior without more in depth knowledge of how the component manages state. I essentially want to cull complexity as I move down the component tree. I find this makes my code easier to work with as well.

Let’s look at an example for a catalog view:

First we create the provider that will connect our React components to the other layers of our application.

/* views/catalog/index.js */
import Catalog from './main'
import actions from './action-creators'
class CatalogProvider extends Component {
render () {
return <Catalog { ...this.props } />
}
}
function mapStateToProps(state) {
return {
...state // pull what data we need
...actions // add any API access we need
}
}
export default connect(mapStateToProps)(CatalogProvider)

Then we create the main component that will house our child views. This is a behavior component that manages state for its children.

/* catalog/main.js */
import CatalogSearch from './search'
import CatalogListView from './list-view'
class Catalog extends Component {
constructor () {
super(...arguments)
this.state = { searchTerm: '' }
this.handleSearch = this.handleSearch.bind(this)
}
render () {
// props provided by catalog provider
const { catalogItems, addToCart, favorite } = this.props
const { searchTerm } = this.state
const visibleItems = this.getVisibleItems(
catalogItems,
searchTerm
)
return (
<div className="catalog-search">
<CatalogSearch onSearch={this.handleSearch} />
<CatalogListView
items={visibleItems}
onAddToCart={addToCart}
onFavorite={favorite}
/>
</div>
)
}
handleSearch (searchTerm) {
this.setState({ searchTerm })
}
getVisibleItems (items, searchTerm) {
return items.filter(i => ~i.indexOf(searhTerm))
}
}

Now we can create our search component which is another behavioral component that manages state.

/* catalog/search.js */
class CatalogSearch extends Component {
constructor() {
super(...arguments)
this.state = { searchTerm: '' }
this.handleOnChange = this.handleOnChange.bind(this)
}
render () {
const { searchTerm } = this.state
return (
<input
type="search"
className="catalog-search"
value={searchTerm}
onChange={this.handleOnChange}
ref={(ref) => { this.input = ref }}
/>
)
}
handleOnChange () {
const { onSearch } = this.props
const { value } = this.input
// set our state and then give value to parent
this.setState({ searchTerm: value }, () => { onSearch(value) })
}
}
CatalogSearch.defaultProps = {
onSearch () {
console.log('no handler for onSearch!', value)
}
}

Finally we get to the bottom of our render tree with a catalog list view. This view is purely presentational.

/* catalog/list-view.js */
class CatalogListView extends Component {
constructor() {
super(...arguments)
this.handleOnAddToCart = this.handleOnAddToCart.bind(this)
this.handleOnFavorite = this.handleOnFavorite.bind(this)
}
render () {
const { items, onAddToCart, onFavorite } = this.props
return (
<ul className="catalog-list-view">
{
items.map(i => (
<li className="catalog-list-view-item">
<h2>{i.title}</h2>
<img src={i.imgUrl} />
<button onClick={this.handleOnAddToCart(i)}>
Add to Cart
</button>
<button onClick={this.handleOnFavorite(i)}>
Favorite
</button>
</li>
))
}
</ul>
)
}
handleOnAddToCart (item) {
const { onAddToCart } = this.props
return () => onAddToCart(item)
}
handleOnFavorite (item) {
const { onFavorite } = this.props
return () => onFavorite(item)
}
}
CatalogListView.defaultProps = {
onAddToCart (item) {
console.log('no handler for onAddToCart!', item)
},
onFavorite (item) {
console.log('no handler for onFavorite!', item)
}
}

As you can see, by the time we get down to the presentational level, the logic around our component has been greatly simplified. Using this stacking technique allows me to quickly scaffold out views connected to the action creators and data needed to work with Redux, while allowing components to manage their own internal state and not be bound to Redux. This greatly increases their reusability.

Data Types

In any given React application there are really two kinds of data. You have application data (data that may be used in other parts of the app) and component state (data that is only needed within that component and it’s children).

Redux is a great solution for your application data, but it’s probably overkill for handling component state. React has a great system in place for dealing with the state of a component. Using Redux to handle the same thing is like buying a toolset and then buying another hammer instead of using the one that came with your toolset.

You already have a tool for that job, remember, you only need one hammer.

Hammer Time!

On the flip side to that, React is just a view library and doesn’t really provide a way to handle data that is not bound to a React component. We are left to our own devices to manage our data.

This is why I augment with Redux. It provides a layer of data manipulation and management that can sit on the outskirts of React. The real problem is when you allow nested components access to data and APIs. You’re allowing them to reach for the sun and we all know how that ends…

Conclusion

  • There is a hierarchy to components and the more nested your components get the “dumber” they should be.
  • Don’t allow child components access to APIs and data.
  • Separate Data and API Provisioning, Behavioral Logic, and Presentational Logic (this will reduce the amount of headaches you have greatly).
  • Remember that all tools have a purpose and that you don’t need to use a new tool when you already have one that can do that job.
  • Don’t judge frameworks by how they are used by others or by some of the things you don’t love about it. It’s pretty snarky.

I wrote another article with some more tips for working effectively with React and Redux, if you enjoyed this one, you may find that one useful as well!

--

--

kurtiskemple

Web / Mobile / GraphQL enthusiast 🙌 Co-organizer of @NYCGraphQL 🗓 Technical Writer 🖋 Mentor 🎓 Fine Dancer🕺