Frontend Development 11 min read

How to Write Clean, Maintainable React Components: Proven Tips

This article reviews practical techniques for writing cleaner React components, covering anti‑patterns like prop spreading, using object parameters, curried event handlers, preferring map over if/else, leveraging custom hooks, and splitting components with wrappers, separation of concerns, and dedicated files to improve readability and scalability.

KooFE Frontend Team
KooFE Frontend Team
KooFE Frontend Team
How to Write Clean, Maintainable React Components: Proven Tips

Avoid Using Spread Operator to Pass Props

Spreading props (e.g.,

{...props}

) may speed up development but makes bugs hard to locate, reduces confidence in components, and complicates refactoring.

Wrap Function Parameters in an Object

When a function receives multiple arguments, bundle them into a single object. Example:

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

Advantages:

You no longer need to remember the order of arguments, avoiding bugs caused by mismatched ordering.

Editors with intelligent suggestions can auto‑fill parameters, improving developer experience.

Return Event Handlers from Functions (Currying)

Inspired by functional programming, you can pre‑configure handlers by returning a function. 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>
  );
}

This keeps the component tree concise.

Render Components with map Instead of if/else

When rendering based on custom logic, prefer

map

over

if/else

. Below is an

if/else

example:

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

And the same logic 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

map

makes the component tree clearer and easier to extend.

Hook Components

Encapsulate reusable stateful logic in custom hooks. Example hook for a 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 };
}

Usage:

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

function Client() {
  const { Dialog, onOpen } = useConfirmationDialog({
    headerText: "Delete this record?",
    bodyText: "Are you sure you want 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;

Component Splitting

Three practical ways to split components for better maintainability.

Use a Wrapper

Encapsulate drag‑and‑drop logic in a wrapper component:

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... */}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    </div>
  );
}

Wrapper component:

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

Separation of Concerns

Separate data fetching and state logic from presentation using custom hooks.

import React, { useState, useEffect } 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>
  );
}

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 in Its Own File

Moving sub‑components like

ItemDisplay

to separate files reduces coupling and eases future extensions.

Following these patterns helps you write clean, readable, and scalable React code.

ReactBest Practiceshookscomponent designCode Cleanliness
KooFE Frontend Team
Written by

KooFE Frontend Team

Follow the latest frontend updates

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.