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.

: 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.

: 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!