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

Scary Halloween Linux Commands

Even though it's not Halloween, it's wort...

Demystifying the HTML 5 Working Draft

The World Wide Web Consortium (W3C) has released a...

Solution to SNMP4J server connection timeout problem

Our network management center serves as the manag...

Detailed explanation of JavaScript prototype chain

Table of contents 1. Constructors and instances 2...

jQuery+swiper component realizes the timeline sliding year tab switching effect

Result: Implementation code: Need to be used with...

MySQL transaction, isolation level and lock usage example analysis

This article uses examples to describe MySQL tran...

How to calculate the frame rate FPS of web animations

Table of contents Standards for smooth animation ...

The difference and choice between datetime and timestamp in MySQL

Table of contents 1 Difference 1.1 Space Occupanc...

MySQL 5.7.33 installation process detailed illustration

Table of contents Installation package download I...

How to deploy services in Windows Server 2016 (Graphic Tutorial)

introduction Sometimes, if there are a large numb...

Three ways to copy MySQL tables (summary)

Copy table structure and its data The following s...

Practical operation of using any font in a web page with demonstration

I have done some research on "embedding non-...

jQuery implements Table paging effect

This article shares the specific code of jQuery t...