DEV Community

Jithin KS
Jithin KS

Posted on • Updated on

Writing (clean) React code

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/>
Enter fullscreen mode Exit fullscreen mode

Case 2

<div>
  <button onClick={handleFilePress}>File</button>
  <button onClick={handleSavePress}>Save</button>
</div>
<Editor/>
<RightMenu/>
Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

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}
  />
)
Enter fullscreen mode Exit fullscreen mode

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} 
/>
Enter fullscreen mode Exit fullscreen mode
// center = {x,y}
<Circle
 center={center}
 radius={radius}
/> 
Enter fullscreen mode Exit fullscreen mode

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}
        />
      ))}
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

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))
  })
}

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
alexeychikk profile image
Alex Zinkevych

Creating objects and passing them as props directly in the render function is a bad practice.
For example, this

<Circle
 center={{x,y}}
 radius={radius}
/> 

will rerender <Circle> component on each render.

Collapse
 
jithinks profile image
Jithin KS • Edited

OK, it should be

// Circle renders if center or radius changes
<Circle
  center={center}
  radius={radius}
/>

Thanks for pointing out

Collapse
 
simonas88 profile image
simonas88

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.

Collapse
 
jithinks profile image
Jithin KS • Edited

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.

const Shapes = (props) => {

  // Here we destructured the respective parameters child components want
  // And passing to the components

  const { lineParams } = props
  const { circleParams } = props

  // Line and circle rerender only if their respective props change

  return (
    <>
      <Line params = {lineParams}/>
      <Circle params = {circleParams}/>
    </>
  )
}
const Shapes=(props) => {

  const { combinedParams } = props

  // Here we have mindlessly combined everything into one
  // So whenever it changes both will rerender which we do not want

  // Props that are combined should be taken out
  // at appropriate levels

  return (
    <>
      <Line params={combinedParams}/>
      <Circle params={combinedParams}/>
    </>
  )
}

<Shapes
  lineParams={lineParams}
  circleParams={circleParams}
/>

I'm just saying that the above is better than the component below :

<Shapes
 lineX1={lineX1}
 lineY1={lineY1}
 lineX2={lineX2}
 lineY2={lineY2}
 circleX={circleX}
 circleY={circleY}
 circleRadius={circleRadius}
/>
Collapse
 
rafaso profile image
Rafael Oliveira

Simple post, but gold. I liked it.

Collapse
 
sankarsanjay profile image
Sanjay Sankar

A really good compilation of all the learnings we have acquired while working with React. Very useful!

Collapse
 
brycedooley profile image
Bryce

All of these are 🔥! Thanks for the article!

Collapse
 
outthislife profile image
Talasan Nicholson

If the prop is the same name as the variable, please, do this:

const data = []
return <Component {... { data } } />
Collapse
 
sagar profile image
Sagar

Hi,

What benefits will give us by wrapping data array inside the object literal?

Collapse
 
juditlehoczki profile image
Judit Lehoczki (she/her)

Great read for newbies like me, thank you! 🙏🏻

Collapse
 
dewofyouryouth_43 profile image
Jacob E. Shore

this is great! very clear and to the point - will be sending links to the dev team :)

Collapse
 
davidgarsan profile image
David

Another big gain in the point 4 is avoiding new memory allocation for the functions each render.

Collapse
 
ridoansaleh profile image
Ridoan

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.

Collapse
 
jithinks profile image
Jithin KS

Corrected