Knowing When To Componentize Your Code

Samuel Guo
6 min readJan 20, 2020

--

Background

A fundamental concept of React is that you should always be componentizing. This means that the application should always be broken up into many smaller components rather than one giant component. This will help with maintainability and scaleability in the long run versus putting all your code on one file in the short run. Although it is easy on how to componentize your application, an issue I had was when I should be componentizing. This happened on my Group Randomizer project where I dumped the majority of my functions and states into my App.js file.

Recreating The Issue

Let’s re-create the baseline of this issue. Although I won’t be using the same code as my project, I will abstract the logic with pseudocode.

//App.jsfunction App() {
hook for stateOne
hook for stateTwo
hook for stateThree
hook for stateFour
hook for stateFive
function alpha (stateOne)
function bravo (stateOne, stateTwo)
function charlie (stateThree)
function delta (stateFour)
function echo (stateThree, stateFive)
return (
<div>
renders something based on the output of function alpha
renders something based on the output of function bravo
renders something based on the output of function charlie
renders something based on the output of function delta
renders something based on the output of function echo
</div>
)
}

In the App.js file written in pseudo code, there are five hooks to maintain state, five functions that use one or two states as their parameters, and a return that renders something based on the output of their respective function.

Always Be Componentizing

In the return portion of App.js, there are five things that are rendering. Each of those renders can be broken up into a component, so there should be five new components. For the sake of demonstration purposes, I am going to pseudocode for two components as the process will be very similar, if not the same, for the rest of the components.

The psuedocode would look like the following below.

//App.jsimport ComponentOne from './ComponentOne'function App() {
hook for stateTwo
hook for stateThree
hook for stateFour
hook for stateFive
function bravo (stateOne, stateTwo)
function charlie (stateThree)
function delta (stateFour)
function echo (stateThree, stateFive)
return (
<div>
<ComponentOne />
renders something based on the output of function bravo
renders something based on the output of function charlie
renders something based on the output of function delta
renders something based on the output of function echo
</div>
)
}//ComponentOne.jsexport default function ComponentOne() {
hook for stateOne
function alpha (stateOne) return (
<div>
renders something based on the output of function alpha
</div>
)
}

Essentially, I moved the hook for stateOne and function alpha from App.js into ComponentOne.js because they directly affected the first component. So far this doesn’t seem so bad. Let’s move onto the second component.

//ComponentTwo.jsexport default function ComponentTwo() {
hook for stateOne
hook for stateTwo
function bravo (stateOne, stateTwo) return (
<div>
renders something based on the output of function bravo
</div>
)
}

Well, we already hit our first snag. The stateOne hook is already defined in ComponentOne.js. Replicating the same hook in a different file would violate the single responsibility principle and potentially cause a ton of side effects/bugs in the future.

We would need to move stateOne back to the App component because it is used for both ComponentOne and ComponentTwo and pass it down to those two components as a prop. Assuming only ComponentOne is the only component that changes the state of stateOne , then a callback function will also need to be passed down to that component.

//App.jsimport ComponentOne from './ComponentOne'function App() {
hook for stateOne
hook for stateThree
hook for stateFour
hook for stateFive
function charlie (stateThree)
function delta (stateFour)
function echo (stateThree, stateFive)
function updateStateOneCallback() return (
<div>
<ComponentOne stateOne={stateOne} updateStateOneCallback={updateStateOneCallback}/>
<ComponentTwo stateOne={stateOne} />
renders something based on the output of function charlie
renders something based on the output of function delta
renders something based on the output of function echo
</div>
)
}//ComponentOne.jsexport default function ComponentOne(props) { function alpha (props.stateOne) return (
<div>
renders something based on the output of function alpha
<button onClick={props.updateStateOneCallback}>Click Me</button>
</div>
)
}//ComponentTwo.jsexport default function ComponentTwo(props) {
hook for stateTwo
function bravo (props.stateOne, stateTwo) return (
<div>
renders something based on the output of function bravo
</div>
)
}

Scaling the Issue

Notice how many changes we needed to make it in order to accommodate componentizing ComponentTwo . Although it wasn’t difficult, it was a lot to keep track of. Missing one of those steps would cause bugs and potentially make debugging a nightmare. Imagine scaling that effort directly proportional to the number of components. At that point, it would be a tedious management task rather than coding new features.

Also, we were passing props and callback functions between App , ComponentOne , and ComponentTwo , where App is the parent component of ComponentOne and ComponentTwo . We had to do this because stateOne affected ComponentOne and ComponentTwo . But what if stateOne , or any other state, affected nested components, as in a component that is the great-great-children of App ? We would need to pass props and callback functions proportionate to how deeply nested the component is.

App (where stateOne lives)
|
|- ComponentOne
| |
| |- ComponentOneChild
| |
| |- ComponentOneGrandChild
| |
| |- ComponentOneGreatGrandChild
| (needs to use stateOne)
|- ComponentTwo
|
|- ComponentThree

The diagram above is how a nested component may look like for stateOne .

In this scenario, we can easily see how it can get extremely messy and this can turn into more of a book keeping exercise rather than developing features or functionality of the application. Luckily, these are the type of scenarios where Redux is perfect to implement.

If we were to implement Redux, then we would need to create the reducers, actions, and dispatchers as necessary. Although this may seem slightly more work upfront in comparison to creating props and callback functions, this will save time in the long run because the structure of keeping track of various states in the Redux store will be established. From personal experience, debugging state in Redux is far easier than debugging state that was passed via props and callback functions.

Back to the Original Issue

So far, I’ve explained componentizing the application and the potential issues of what happens if you scale/develop your application before componentizing it. But I never answered the question: when do you start componentizing the application?

I believe you should start componentizing when:

  1. A new state is introduced that may affect multiple components
  2. A portion of the code has one specific purpose (e.g., title, navigation bar, graphs, etc.)

To circle back to the App example and to emphasize the second point, a portion of the code renders the result of function alpha . Since function alpha does not affect the rest of the code, it makes logical sense to componentize rendering the results of function alpha and the function itself, hence the creation of ComponentOne , and use Redux to keep track of stateOne because stateOne also affects function bravo , which lives in ComponentTwo .

If there was a case in which the results of a function, say function alpha, from one component affects the rendering or results of a different component, a solution to that could be storing the results of function alpha in the Redux store and access it when needed.

Key Takeaways

  1. Componentize as necessary as explained in the previous section.
  2. Wire frame the application before starting to code.

This is a common step during the development stages of any application. The wire frame lays out the format and the locations of each component, providing an idea on what components you will need and when to start componentizing. I know you may eager to dive straight into code and begin developing it but knowing where the components are will provide a development trajectory of the application.

3. Use Redux, or any state management tools, as soon as possible to assist you while componentizing.

Applications always scale. By putting the additional effort to set up the structure of your code earlier on, it will save you time and effort to debug or re-structuring in the future due to scaling reasons. This may not be helpful in the beginning of the application but after a certain point, the proper structuring will be more efficient than structuring the code as you go.

--

--

Samuel Guo

Full stack software developer with experience in Javascript, React, Redux, and Ruby on Rails and a background in Mechanical Engineering.