Building simple software
We generally think that building complex software is hard. But the hardest thing is building complex software in a way that is as simple as possible.
Even a simple software can turn into a complicated mess if we don't put a conscious effort into keeping it simple.
One of the main metrics of simplicity in a piece of software is how easily an outside developer can understand the codebase and work on top of it to extend and improve it.
In this post, I'm going to explain some of the things that I've learned in the past couple of months about writing simple and clean React code.
1. Make your React component as short as possible
Compared to the time taken to write code, developers spend almost 10x time to read and understand it. Our goal should be to make this process as effortless as possible for them. As the size of the code increases, the readability of the code drops drastically.
If you do not keep a check on the size of the React component, it can quickly grow beyond your control. If the component is really large, the person who wrote it might be familiar with all of its parts and understand its working, but it will never be easy for another developer to understand it. Components with 500 or 1000 lines of code will be a nightmare for an outside developer.
Always see if you can refactor the code into separate files. Break down your component into appropriate sub components. Put helper functions used within the component into separate JS files and import them. This helps improve reusability as a good side effect.
2. Components which are at the same level of abstraction should be together
If you're familiar with Clean Code by the infamous Uncle Bob, you might be familiar with this concept in terms of functions. Functions that are at the same level of abstraction should be present together. This makes the code easy to read. The same applies for components. Components that are at the same level of abstraction should occur together.
Case 1
<TopMenu/>
<TextEditor/>
<RightMenu/>
Case 2
<div>
<button onClick={handleFilePress}>File</button>
<button onClick={handleSavePress}>Save</button>
</div>
<Editor/>
<RightMenu/>
The first piece of code immediately gives the reader a high level idea about the text editor app. But the second piece of code is not as obvious as the first.
3. Reduce the number of props to as minimum as possible
In the book clean code, it is advised that no. of parameters of a function should be as minimum as possible. Why? Because as the number of parameters increases, what the function does will become less and less obvious(Props to a React component can be considered to be its parameters)
For example, consider the code
Case 1
<Table
height={100}
width={20}
scrollable
resizable
cellColor='grey'
headerColor='black'
font='Roboto'
data={data}
/>
Case 2
const config={
height:100,
width:20,
scrollable:true,
resizable:true,
}
const styles = {
cellColor: 'grey',
headerColor: 'black',
font: 'Roboto'
}
return (
<Table
config={config}
data={data}
styles={styles}
/>
)
The second code snippet looks more neat and understandable when compared to the first because when a developer looks at the component, in his mind, a mental picture is immediately formed, that the Table component has three high-level props which are data, style, and config. Then if he wants, he can dig deeper into any of them.
In the first code snippet, the developer is bombarded with a lot of information immediately and it is really easy for him to get lost here.
If needed, you should abstract the props as done in creating the config object. It is really easy to add more prop to the component every time you need to add one. But it takes some care and attention to stop, think and refactor to make the code look better.
Let me give you another example
<Circle
x={x}
y={y}
radius={radius}
/>
// center = {x,y}
<Circle
center={center}
radius={radius}
/>
I've abstracted the x and y into a center object which makes the code more neat and clean.
4. Abstract application-specific API calls using Promises
Rather than writing the API calls directly in the component. Abstract the fetch calls into functions.
Below is the code for rendering a list of comments.
Case 1
const CommentList = ({ postId }) => {
const {commentList, setCommentList} = useState([])
useEffect(()=>{
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
body: { postId }
})
.then(response => response.json())
.then(commentList => setCommentList(commentList))
}, [])
return (
<>
{comments.map(comment=>(
<Comment
author={comment.author}
text={comment.text}
/>
))}
</>
)
}
Case 2
const CommentList = ({ postId }) => {
const {commentList, setCommentList} = useState([])
useEffect(()=>{
getCommentList(postId).then((commentList)=>{
setCommentList(commentList)
})
}, [])
return (
<>
{comments.map(comment=>(
<Comment
author={comment.author}
text={comment.text}
/>
))}
</>
)
}
const getCommentList = (postId) => {
return new Promise((resolve) => {
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
body: { postId }
})
.then(response => response.json())
.then(commentList => resolve(commentList))
.catch(err=>console.log(err))
})
}
In case 2, the React component for rendering comments in a post looks way cleaner when the details of the fetch call are taken out and abstracted into a new function.
To get your code even more organized, you can put all these application-specific API calls into a separate file that will reduce the clutter in the react component.
Conclusion
Rather than jumping in without a second thought and writing code, spend sufficient time in designing the components, their responsibilities, the hierarchy between them, props, state, etc. It will save a lot of time and effort for you as well as the other developers in your team.
So keep it short, sweet and simple and you should be okay :)
PS: Please feel free to add points that are not covered here in the comments
Top comments (15)
Creating objects and passing them as props directly in the render function is a bad practice.
For example, this
will rerender
<Circle>
component on each render.OK, it should be
Thanks for pointing out
Wrapping props in an object is risky business. You have to understand well how that particular prop travels down the components and where it is created and how often updated. Otherwise you'll make your app do unnecessary updates and re-renders which may impact the performance significantly.
Yeah, I think that is true. I'm not saying that you mindlessly combine every props into an object. Props that are really related to each other can be combined into an individual unit.
I can give you an example. Suppose that there is a Shapes component that renders a line and a circle.
I'm just saying that the above is better than the component below :
Simple post, but gold. I liked it.
A really good compilation of all the learnings we have acquired while working with React. Very useful!
All of these are 🔥! Thanks for the article!
If the prop is the same name as the variable, please, do this:
Hi,
What benefits will give us by wrapping data array inside the object literal?
Great read for newbies like me, thank you! 🙏🏻
this is great! very clear and to the point - will be sending links to the dev team :)
Another big gain in the point 4 is avoiding new memory allocation for the functions each render.
Point number 4 in Case 2 will create infinite loop of API calls. You should consider adding an empty array as the dependency of useEffect.
Corrected