Tips for writing concise React components

Tips for writing concise React components

This article is based on the translated article Simple tips for writing clean React components, the original author is Iskander Samatov

In this post, we’ll review some simple techniques that will help us write cleaner React components and scale our projects better.

Avoid using the spread operator to pass props

First, let's start with an anti-pattern that should be avoided. Unless there is a clear reason to do so, you should avoid passing props through the component tree using the spread operator, e.g. { ...props } .

Passing props this way does make writing components faster. But this also makes it difficult for us to locate bugs in the code. It will make us lose confidence in the components we write, make it more difficult to refactor components, and may cause bugs that are difficult to debug.

Encapsulate function parameters into an object

If a function accepts multiple parameters, it is best to encapsulate them into an object. For example:

export const sampleFunction = ({ param1, param2, param3 }) => {
  console.log({ param1, param2, param3 });
}

There are several significant advantages to writing function signatures in this way:

  1. You no longer have to worry about the order in which parameters are passed. I have made several mistakes that caused bugs due to the order in which function parameters were passed.
  2. For editors configured with smart prompts (most of them now have them), automatic completion of function parameters can be done very well.

For event processing functions, use the processing function as the return value of the function

If you are familiar with functional programming, this programming technique is similar to function currying because some parameters are already set in advance.

Let’s look at this example:

import React from 'react'

export default function SampleComponent({ onValueChange }) {

  const handleChange = (key) => {
    return (e) => onValueChange(key, e.target.value)
  }

  return (
    <form>
      <input onChange={handleChange('name')} />
      <input onChange={handleChange('email')} />
      <input onChange={handleChange('phone')} />
    </form>
  )
}

As you can see, writing the handler function in this way keeps the component tree concise.

Component rendering uses map instead of if/else

When you need to render different elements based on custom logic, I recommend using map instead of if/else statements.

Here is an example using if/else:

import React from 'react'

const Student = ({ name }) => <p>Student name: {name}</p>
const Teacher = ({ name }) => <p>Teacher name: {name}</p>
const Guardian = ({ name }) => <p>Guardian name: {name}</p>

export default function SampleComponent({ user }) {
  let Component = Student;
  if (user.type === 'teacher') {
    Component = Teacher
  } else if (user.type === 'guardian') {
    Component = Guardian
  }

  return (
    <div>
      <Component name={user.name} />
    </div>
  )
}

Here is an example using map:

import React from 'react'

const Student = ({ name }) => <p>Student name: {name}</p>
const Teacher = ({ name }) => <p>Teacher name: {name}</p>
const Guardian = ({ name }) => <p>Guardian name: {name}</p>

const COMPONENT_MAP = {
  student: Student,
  teacher: Teacher,
  guardian: Guardian
}

export default function SampleComponent({ user }) {
  const Component = COMPONENT_MAP[user.type]

  return (
    <div>
      <Component name={user.name} />
    </div>
  )
}

Using this simple little strategy, you can make your components more readable and easier to understand. And it also makes logical expansion easier.

Hook Components

This mode is very useful as long as it is not abused.

You may find yourself using many components in your application. If they require a state to function, you can wrap them in a hook that provides that state. Some good examples of these components are popups, toast notifications, or simple modal dialogs. For example, here is a hook component for a simple confirmation dialog:

import React, { useCallback, useState } from 'react';
import ConfirmationDialog from 'components/global/ConfirmationDialog';

export default function useConfirmationDialog({
  headerText,
  bodyText,
  confirmationButtonText,
  onConfirmClick,
}) {
  const [isOpen, setIsOpen] = useState(false);

  const onOpen = () => {
    setIsOpen(true);
  };

  const Dialog = useCallback(
    () => (
      <ConfirmationDialog
        headerText={headerText}
        bodyText={bodyText}
        isOpen={isOpen}
        onConfirmClick={onConfirmClick}
        onCancelClick={() => setIsOpen(false)}
        confirmationButtonText={confirmationButtonText}
      />
    ),
    [isOpen]
  );

  return {
    Dialog,
    onOpen,
  };
}

You can use the hook component like this:

import React from "react";
import { useConfirmationDialog } from './useConfirmationDialog'

function Client() {
  const { Dialog, onOpen } = useConfirmationDialog({
    headerText: "Delete this record?",
    bodyText:
      "Are you sure you want to delete this record? This cannot be undone.",
    confirmationButtonText: "Delete",
    onConfirmClick: handleDeleteConfirm,
  });

  function handleDeleteConfirm() {
    //TODO: delete
  }

  const handleDeleteClick = () => {
    onOpen();
  };

  return (
    <div>
      <Dialog />
      <button onClick={handleDeleteClick} />
    </div>
  );
}

export default Client;

Extracting components in this way saves you from having to write a lot of boilerplate code for state management. If you want to learn more about React hooks, check out my post.

Component separation

The following three tips are about how to split components cleverly. In my experience, keeping your components simple is the best way to keep your project manageable.

Using a Wrapper

If you’re struggling to find a way to break down a complex component, look at the functionality provided by each element of your component. Some elements offer unique functionality, such as drag and drop.

Here is an example of a component that uses react-beautiful-dnd to implement drag and drop:

import React from 'react'
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
export default function DraggableSample() {
  function handleDragStart(result) { 
    console.log({ result });
  }
  function handleDragUpdate({ destination }) { 
    console.log({ destination });
  }
  const handleDragEnd = ({ source, destination }) => { 
    console.log({ source, destination });
  };
  return (
    <div>
      <DragDropContext
        onDragEnd={handleDragEnd}
        onDragStart={handleDragStart}
        onDragUpdate={handleDragUpdate}
      >
        <Droppable 
          droppableId="droppable"
          direction="horizontal"
        >
          {(provided) => (
            <div {...provided.droppableProps} ref={provided.innerRef}> 
              {columns.map((column, index) => {
                return (
                  <ColumnComponent
                    key={index}
                    column={column}
                  />
                );
              })}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    </div>
  )
}

Now, take a look at the component after we moved all the drag logic into the wrapper:

import React from 'react'
export default function DraggableSample() {
  return (
    <div>
      <DragWrapper> 
      {columns.map((column, index) => { 
        return (
          <ColumnComponent key={index} column={column}/>
        );
      })}
      </DragWrapper>
    </div>
  )
}

Here is the code for the wrapper:

import React from 'react'
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
export default function DragWrapper({children}) {
  function handleDragStart(result) { 
    console.log({ result });
  }
  function handleDragUpdate({ destination }) { 
    console.log({ destination });
  }
  const handleDragEnd = ({ source, destination }) => { 
    console.log({ source, destination });
  };
  return (
    <DragDropContext 
      onDragEnd={handleDragEnd}
      onDragStart={handleDragStart} 
      onDragUpdate={handleDragUpdate}
    >
      <Droppable droppableId="droppable" direction="horizontal"> 
        {(provided) => (
          <div {...provided.droppableProps} ref={provided.innerRef}> 
            {children}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  )
}

Therefore, it is more intuitive to see the function of the component at a higher level. All the functionality for dragging is inside the wrapper, making the code easier to understand.

Separation of concerns

This is my favorite way to break down larger components.

From a React perspective, separation of concerns means separating the part of a component that is responsible for fetching and changing data from the part that is purely responsible for displaying elements.

This separation of concerns is the main reason for introducing hooks. You can use custom hooks to encapsulate logic connected to all methods or global state.

For example, let's look at the following component:

import React from 'react'
import { someAPICall } from './API' 
import ItemDisplay from './ItemDisplay'
export default function SampleComponent() { 
  const [data, setData] = useState([])
  useEffect(() => { 
    someAPICall().then((result) => { setData(result)})
  }, [])
  function handleDelete() { console.log('Delete!'); }
  function handleAdd() { console.log('Add!'); }
  const handleEdit = () => { console.log('Edit!'); };
  return (
    <div>
      <div>
        {data.map(item => <ItemDisplay item={item} />)} 
      </div>
      <div>
        <button onClick={handleDelete} /> 
        <button onClick={handleAdd} /> 
        <button onClick={handleEdit} /> 
      </div>
    </div>
  )
}

Here is its refactored version, using the code split by the custom hook:

import React from 'react'
import ItemDisplay from './ItemDisplay'
export default function SampleComponent() {
  const { data, handleDelete, handleEdit, handleAdd } = useCustomHook()
  return (
    <div>
      <div>
        {data.map(item => <ItemDisplay item={item} />)} 
      </div>
      <div>
        <button onClick={handleDelete} /> 
        <button onClick={handleAdd} /> 
        <button onClick={handleEdit} /> 
      </div>
    </div>
  )
}

Here is the code for the hook itself:

import { someAPICall } from './API'
export const useCustomHook = () => { 
  const [data, setData] = useState([])
  useEffect(() => { 
    someAPICall().then((result) => { setData(result)})
  }, [])
  function handleDelete() { console.log('Delete!'); }
  function handleAdd() { console.log('Add!'); }
  const handleEdit = () => { console.log('Edit!'); };
  return { handleEdit, handleAdd, handleDelete, data }
}

Each component is packaged as a separate file

Usually people write code like this:

import React from 'react'
export default function SampleComponent({ data }) {
  const ItemDisplay = ({ name, date }) => ( 
    <div>
      <h3>{name}</h3>
      <p>{date}</p>
    </div> 
  )
  return (
    <div>
      <div>
        {data.map(item => <ItemDisplay item={item} />)}
      </div>
    </div> 
  )
}

While there is nothing wrong with writing React components this way, it is not a good practice. Moving the ItemDisplay component to a separate file can make your component loosely coupled and easier to extend.

In most cases, writing clean and neat code requires paying attention and taking the time to follow good patterns and avoid anti-patterns. So if you take the time to follow these patterns, it helps you write clean React components. I find these patterns very useful in my projects, and I hope you do too!

The above are the details of the tips for writing concise React components. For more information on tips for writing React components, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • React error boundary component processing
  • Detailed explanation of react setState
  • React implements the sample code of Radio component
  • Example of using setInterval function in React
  • Andrew Ng's machine learning exercise: SVM support vector machine

<<:  Docker uses dockerfile to start node.js application

>>:  mysql join query (left join, right join, inner join)

Recommend

MySql COALESCE function usage code example

COALESCE is a function that refers to each parame...

Summary of the use of element's form elements

There are many form elements. Here is a brief sum...

What are the advantages of MySQL MGR?

MGR (MySQL Group Replication) is a new feature ad...

Detailed steps to install python3.7 on CentOS6.5

1. Download Python 3 wget https://www.python.org/...

Several ways to set the expiration time of localStorage

Table of contents Problem Description 1. Basic so...

How to deploy MySQL master and slave in Docker

Download image Selecting a MySQL Image docker sea...

A brief discussion on the mysql execution process and sequence

Table of contents 1:mysql execution process 1.1: ...

Detailed steps to build the TypeScript environment and deploy it to VSCode

Table of contents TypeScript environment construc...

Usage of the target attribute of the html tag a

1: If you use the tag <a> to link to a page,...

Script example for starting and stopping spring boot projects in Linux

There are three ways to start a springboot projec...

Discussion on the problem of iframe node initialization

Today I suddenly thought of reviewing the producti...

Introduction to MySQL overall architecture

The overall architecture of MySQL is divided into...