React Utility Functions That’ll Make You Say, ‘Why Didn’t I Think of That?

Dan Philip Bejoy
7 min readSep 4, 2024

--

In React development, sometimes the smallest tweaks can make the biggest difference. If you’ve ever found yourself tangled in repetitive tasks or wishing for a simpler way to handle common challenges, you’re not alone. Enter utility functions — the unsung heroes of efficient coding. These clever little tools can transform your React workflow, turning tedious chores into seamless processes, all while keeping your package.json and bundles slim.

In this article, we’ll explore a collection of React utility functions that are so ingenious, you’ll find yourself wondering, “Why didn’t I think of that?” Whether you’re a seasoned developer or just starting out, these utilities are designed to enhance your productivity and make your React projects run smoother than ever. Ready to elevate your coding game? Let’s get started!

Class Names

Tired of your class names looking like a hot mess? Do you find yourself battling with rogue null values and wayward whitespace? Say no more—classBuilder is here to save your sanity!

This nifty little function is like the ultimate bouncer for your CSS class strings, making sure only the best names get in while kicking out the riff-raff. No more null, undefined, or accidental false crashing your styling party. And don't worry about whitespace sneaking in uninvited—classBuilder trims those edges like a pro barber.

export function classBuilder(classNames) {
return classNames
// Execute functions to get their return values
.map((cls) => (typeof cls === 'function' ? cls() : cls))
// Filter out falsy values like null, undefined, false, etc.
.filter(Boolean)
// Trim any whitespace from class names
.map((cls) => cls.trim())
// Filter again to remove any empty strings after trimming
.filter(Boolean)
// Join all class names into a single string with a space separator
.join(' ');
}

Usage

import React from 'react';
import { classBuilder } from './utils';

const Button = ({ isActive, isDisabled, className }) => {
.
.
.
const buttonClasses = classBuilder([
// Base button class
'btn',
// Add 'btn-active' if isActive is true
isActive && 'btn-active',
// Add 'btn-disabled' or 'btn-enabled' based on isDisabled
isDisabled ? 'btn-disabled' : 'btn-enabled',
// Add classes from return blocks of functions
() => someCondition ? 'custom-class': 'another-custom-class',
// Any additional classes passed in via the className prop
className
]);

.
.
.

return (
<button className={buttonClasses} disabled={isDisabled}>
Click Me
</button>
);
};

export default Button;
import React from 'react';
import Button from './Button';

const App = () => {
return (
<div>
<Button
isActive={true}
isDisabled={false}
className="custom-class another-class"
>
Click Me
</Button>
</div>
);
};

export default App;

Render List

Ever tried rendering a list in React and ended up with a bunch of key errors that made you want to pull your hair out? Or maybe you’ve been stuck with repetitive code for mapping over arrays? Enter RenderList—the ultimate utility component that handles your list rendering needs with elegance and flair.

With RenderList, you can dynamically resolve keys, apply custom templates, and keep your code DRY (Don’t Repeat Yourself).

import React, { Fragment } from "react";

const RenderList = ({ data, keyResolver, template, fallback }) => {
if (!data || data.length === 0) {
// Render fallback if provided, otherwise render nothing
return fallback ? fallback() : null;
}

return (
<>
{data.map((item, index) => {
const key =
typeof keyResolver === "function"
? keyResolver(item, index)
: typeof keyResolver === "string"
? item[keyResolver]
: index;

return (
<Fragment key={key}>{template(item, index)}</Fragment>
);
})}
</>
);
};

export default RenderList;

Usage

import React from 'react';
import RenderList from './RenderList';

const data = [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" },
{ id: 3, name: "Item 3" },
];

const keyResolver = "id"; // Or (e) => e.id;

const App = () => {
return (
<div>
<h2>Items List</h2>
<RenderList
data={data}
keyResolver={keyResolver}
template={(item) => (
<div>
<h3>{item.name}</h3>
</div>
)}
// Fallback template for empty data
fallback={() => <p>No items to display.</p>}
/>
</div>
);
};

export default App;

Conditional Render

Ever wanted to show something only when the stars align? Or maybe you’ve got a fallback message ready for those days when things don’t quite go according to plan. Enter RenderIf—the conditional rendering mastermind that decides if your content gets to see the light of day, or if it’s time for the fallback plan to take center stage.

import React from "react";

const RenderIf = ({ condition, children, fallback = null }) => {
// Determine if the content should be rendered
const shouldRender =
typeof condition === "function" ? condition() : condition;

return shouldRender ? children : fallback;
};

Usage

import React from "react";
import RenderIf from './RenderIf';

const App = () => {
const isLoggedIn = true; // Or some condition that could change

return (
<div>
<h1>Welcome to the App</h1>
<RenderIf
condition={isLoggedIn}
fallback={<p>Please log in to continue.</p>}
>
<p>Hello, User! You are logged in.</p>
</RenderIf>
</div>
);
};

export default App;

Element Visibility Check

Ever wondered if that crucial element is actually visible on your user’s screen? Whether you’re triggering animations, lazy-loading images, or just showing some fancy effects, knowing if an element is in the viewport is key. Enter isElementInViewport , a utility function that tells you exactly that, without breaking a sweat.

export const isElementInViewport = (el) => {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};

You can enhance this utility by adding a custom offset to the bottom value, allowing it to trigger just before the element is fully visible. This is particularly useful for preloading content or starting animations slightly earlier, creating a smoother user experience.

Usage

The most effective way to utilize this utility is by turning it into a custom React hook.

import { useState, useEffect } from 'react';

export const useElementInViewport = (elementRef, offset = 0) => {
const [isInViewport, setIsInViewport] = useState(false);

useEffect(() => {
const handleScroll = () => {
if (elementRef.current) {
const rect = elementRef.current.getBoundingClientRect();
setIsInViewport(
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
};

handleScroll(); // Initial check
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleScroll);

return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleScroll);
};
}, [elementRef]);

return isInViewport;
};
import React, { useRef } from 'react';
import { useElementInViewport } from './useElementInViewport';

const MyComponent = () => {
const myElementRef = useRef(null);
// Add the second argument 'offset' to trigger before it is fully visible
const isInViewport = useElementInViewport(myElementRef);

return (
<div>
<h1>Hello, World!</h1>
<div
ref={myElementRef}
style={{
height: '100px',
marginTop: '150vh', // Pushed down to simulate scrolling into view
backgroundColor: isInViewport ? 'green' : 'red',
}}
>
{isInViewport ? "I'm in the viewport!" : "I'm outside the viewport!"}
</div>
</div>
);
};

export default MyComponent;

Error Boundary

Imagine if every time something went wrong in your React app, your whole app just threw up its hands and quit. Not pretty, right? Enter the Error Boundary: your app’s personal safety net. It’s like having a superhero on standby, ready to swoop in and save the day when things go sideways. Instead of your entire app crashing, your Error Boundary steps in, throws on a “Whoops!” sign, and gives users a charming fallback message or UI.

import React, { Component } from 'react';

// Higher-order component for adding error boundary functionality
export const withErrorBoundary = (WrappedComponent, FallbackComponent) => {
return class ErrorBoundary extends Component {
state = { hasError: false };

static getDerivedStateFromError() {
// Update state to indicate an error has occurred
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// Log error details for debugging
console.error('Error caught by ErrorBoundary:', error, errorInfo);
}

render() {
if (this.state.hasError) {
// Render the fallback component if an error occurs
return <FallbackComponent />;
}

// Render the wrapped component if no error
return <WrappedComponent {...this.props} />;
}
};
};

Usage

Wrap your components with withErrorBoundary to handle errors gracefully:

import React from 'react';
import { withErrorBoundary } from './withErrorBoundary';

// Custom fallback component
const CustomFallback = () => (
<div style={{ padding: '20px', border: '1px solid red' }}>
<h2>Oops! Something went wrong.</h2>
<p>Our tech team is on the case. Please refresh or try again later.</p>
</div>
);

const FaultyComponent = () => {
// This will throw an error to demonstrate Error Boundary
throw new Error('Oops! A simulated mishap has occurred!');
return <div>This won’t get shown because of the error!</div>;
};

const SafeComponent = () => {
return <div>All clear here—safe and sound!</div>;
};

// Wrap FaultyComponent with Error Boundary
const FaultyComponentWithErrorBoundary =
withErrorBoundary(FaultyComponent, CustomFallback);

const App = () => {
return (
<div>
<h1>Welcome to the Error-Free Zone!</h1>
<FaultyComponentWithErrorBoundary />
<SafeComponent />
</div>
);
};

export default App;

With these handy helpers, your code will be cleaner, your errors will be more controlled, and your development experience will be smoother than a fresh jar of peanut butter.

Keep coding, keep laughing, and may your React utilities be as reliable as your morning coffee. Here’s to a future of bug-free bliss and smooth sailing in your coding adventures! ☕️🚀

--

--

Dan Philip Bejoy
Dan Philip Bejoy

Written by Dan Philip Bejoy

Frontend engineer by profession, enthusiast by passion.

No responses yet