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.
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
mapover
if/else. Below is an
if/elseexample:
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
mapmakes 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
ItemDisplayto separate files reduces coupling and eases future extensions.
Following these patterns helps you write clean, readable, and scalable React code.
KooFE Frontend Team
Follow the latest frontend updates
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.