Neat Little React Tricks

Published Last updated v2.1.3

Here are some React tricks that are too small to warrant a separate blogpost, neat nonetheless.

In React, state should be kept as close to where relevant as possible 1. Placing state unnecessarily high in the component tree can lead to , even more so when that state updates often.

When performance suffers, a component can be decomposed to isolate frequently updating state and by consequence, renders. That can mean moving some oft-changing state and the parts that need it down into a new component, a . It’s a way of colocating state and the UI elements that depend on it.

If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common parent component.

Thinking in React: Identify where your state should

Let’s see what this means in practice. We got a <NavBar/> component with all the usual navbar stuff and an app-wide loading indicator, a spinner that pops up whenever something is being fetched (f.e. using react-query’s useIsFetching).


_25
function NavBar({ currentUser, sidebarOpen, toggleSidebar }) {
_25
const isFetching = useIsFetching();
_25
const [openMenu, setOpenMenu] = useState(null);
_25
_25
return (
_25
<nav>
_25
<SidebarButton sidebarOpen={sidebarOpen} onClick={toggleSidebar} />
_25
_25
<Logo />
_25
_25
{Boolean(isFetching) ? <Spinner /> : null}
_25
_25
<NotificationIcon onClick={() => setOpenMenu("notifications")}/>
_25
_25
{openMenu === "notifications" && <Notifications setOpenMenu={setOpenMenu} />}
_25
_25
<div onClick={() => setMenuOpen("user")}>
_25
Logged in as {currentUser.name}
_25
<Avatar image={currentUser.img} />
_25
</div>
_25
_25
{openMenu === "user" && <UserMenu setOpenMenu={setOpenMenu} />}
_25
</nav>
_25
);
_25
}

Note that the often changing isFetching state is not relevant to the other children of the navbar. It is, however, causing them to rerender everytime it’s toggled. Only <Spinner/> needs to know about isFetching.

To isolate the renders, make a new component and move <Spinner/> and isFetching down into it. Admittedly it’s very small, normally it wouldn’t be worth making a component for it. In this case, extracting it to a component is worth it because it holds the oft-changing isFetching state and therefore frequent rerenders in the <Navbar/> are avoided.


_8
// Component just to hold `isFetching` state, a State Container Component!
_8
function FetchingSpinner() {
_8
const isFetching = useIsFetching();
_8
_8
return Boolean(isFetching) ? (
_8
<Spinner />
_8
) : null;
_8
}

Decomposing doesn’t exclusively mean moving down to a new, small leaf component like the example here, it comes in many shapes. Keep an eye open for unrelated states in a single component and try to find the fault lines between states and the JSX it ends up in, compose components accordingly.

JSX can ’dynamic’, in the sense that the component type can be determined at runtime as long the variable name is capitalized 2. It’s just how JSX .

This can be useful when having to switch between two different components that take the same props, like variants of styled components:


_7
import { LeftSidebar, RightSidebar } from "./sidebars";
_7
_7
function Sidebar({ position, size, children }) {
_7
const SidebarComponent = position === "left" ? LeftSidebar : RightSidebar;
_7
_7
return <SidebarComponent size={size}>{children}</SidebarComponent>;
_7
}

Another real world use case is conditionally replacing an old component with a newly refactored one:


_16
// class-based or perhaps just wonky
_16
import { UserPermission as OldUserPermission } from "./old-user-permissions";
_16
// functional and clean
_16
import { UserPermission as NewUserPermission } from "./new-user-permissions";
_16
_16
function UsersManagement({ users, isNewAPI }) {
_16
// ...
_16
_16
// capitalized variable name!
_16
const UserPermissions = isNewAPI ? UserPermissions : NewUserPermissions;
_16
_16
return (<UserPermissions
_16
permission={permission}
_16
users={users}
_16
currentUser={currentUser}
_16
/>)

Yeah you could conditionally render the element with a as well but this is less work when the component has a lot of props and the props are the same for both.

Have some state that only ever updates in one particular way? Consider using a single-action useReducer over useState!

Take for example, a boolean in useState that can only be toggled. Maybe you’re already using a state updater function to avoid reading the state variable from component scope: setIsOpen((prevIsOpen) => !prevIsOpen). Knock it off and put that logic in a reducer instead, then just call the dispatch without any arguments.


_4
const [isOpen, toggleOpen] = useReducer((prevIsOpen) => !prevIsOpen, false);
_4
_4
// Flip the switch
_4
toggleOpen();

A nice benefit is that the is , whereas a callback for setting state is not. If a state setter-wrapping callback is called in a useEffect, it will have to be wraped in useCallback to make it referentially stable. And that works but it’s quite some boilerplate for something as simple as flipping a switch.

Also, a reducer restricts what can be done to the state 3. Suppose you’re working in a team on a gigantic app and pass a state setter for a toggle down to parts of the app that fall under your teammates responsibilty. They can now set the state to whatever, which is not what a toggle should be able to do! OK, this might be slightly farfetched but you get what I mean, limiting possibilites is good design✨ .

Real simple, barely a trick but here you go. Just like setting default values on props, you can set a default for the special children prop 4. Set it to a sane default string or element and render children where you want.

I often use it on small reusable (styled) UI components, like buttons and links, that contain the same text most of the time, but need to overriden every now and then. I’m talking about text like “Read more…”, or “Open”,


_10
export const DonateLink = ({ children = "Donate" }) => (
_10
<Link href="/donate" className="styledLink">
_10
{children}
_10
<HeartIcon />
_10
</Link>
_10
);
_10
_10
// In practice:
_10
<DonateLink /> // default text
_10
<DonateLink>Donate now!</DonateLink> // overridden text

Or using the little known defaultProps property on a component.


_3
DonateLink.defaultProps = {
_3
children: ’Donate’,
_3
}

I prefer assigning default values directly when destructuring props, that makes it more obvious.

JSX is just another way to React.createElement, and are just plain objects 5. We can declare element variables before the return for reuse.

Here’s an example, it’s a component I use on this blog, it’s a fleshed out <blockquote> as seen on its MDN page. You can pass the component a url props which will both be set as the url attribute on the blockquote element and as the href on the inside the cite element. If no url is given, no anchor element should be inside the <cite>.


_13
export function Quote({ children, url, source }) {
_13
_13
const citeEl = <cite>{source}</cite>;
_13
_13
return (
_13
<figure>
_13
<blockquote cite={url}>{children}</blockquote>
_13
<figcaption>
_13
{url ? <a href={url} rel="noopener">{citeEl}</a> : citeEl}
_13
</figcaption>
_13
</figure>
_13
);
_13
}

Another use case could be to conditionally wrap an element with a tooltip.


_9
function TableCell({ value, isOutdated }) {
_9
const tableCell = <td>{value}</td>;
_9
_9
return isOutdated ? (
_9
<Tooltip text="This value is not up to date">{tableCell}</Tooltip>
_9
) : (
_9
tableCell
_9
);
_9
}

Take it a step further with React.cloneElement to conditionally add a prop. I think this is only worth if the wrapped element takes a lot of props, so those don’t have to be repeated. Repeating the element is fine if it’s a DOM node or a component without props.


_15
function TableCell({ value, setCell, isOutdated }) {
_15
const tableCell = (
_15
<EditableCell onChange={setCell} long={long} list={list} ofProps={ofProps}>
_15
{value}
_15
</EditableCell>
_15
);
_15
_15
return isOutdated ? (
_15
<Tooltip text="The value is not up to date">
_15
{cloneElement(tableCell, { className: "outdated" })}
_15
</Tooltip>
_15
) : (
_15
tableCell
_15
);
_15
}

First off, putting things in state that don’t belong there is a bad idea, you’ll have a hard time when you do so. Remember that state is reserved for data that changes over time 6.

There’s one exception to this rule and it’s using useState to initialize a value once, and it sure is a neat trick.

Why would we want to initialize a value once? Well, values defined in React components are reevaluated every render, that can be problematic. Sometimes it’s important that a value is initialized strictly once, like an external library. Other times, an the object can be ’expensive’ to create, for instance a huge array or heavy class. If you need to initialize such a value per component, useState can help.

First, the state value is guaranteed to be stable. On top of that, useState can take an initializer function to ensure the initial state is . The value should never change, so we can just ignore the state setter by not destructuring it.


_7
function Image(props) {
_7
const [intersectionObserver] = useState(() => new IntersectionObserver());
_7
_7
return (
_7
// JSX
_7
)
_7
}

Voila, a lazily-created, stable value.

“Isn’t this what useMemo is for?” No, not really. useMemo should be seen as a performance optimization for expensive computations based on , we shouldn’t rely on it for one-time initializations 7. Or as the docs put it:

useMemo lets you memoize an expensive calculation if the dependencies are the same. However, it only serves as a hint, and doesn’t guarantee the computation won’t re-run.

React Hooks FAQ - How to create expensive objects lazily?

Write your code so that it still works without useMemo — and then add it to optimize performance.

React Hooks Reference - useMemo

You can achieve the same with useRef, below is the example from How to create expensive objects lazily? section Hooks FAQ.


_14
function Image(props) {
_14
const ref = useRef(null);
_14
_14
// ✅ IntersectionObserver is created lazily once
_14
function getObserver() {
_14
if (ref.current === null) {
_14
ref.current = new IntersectionObserver(props.onIntersect);
_14
}
_14
return ref.current;
_14
}
_14
_14
// When you need it, call getObserver()
_14
// ...
_14
}

But I think it’s not as nice as useState with an initializer. I’d love for useRef to have an initializer function too but it doesn’t.

See this this post post by Vladimir Klepov for more ways to make useRef lazy.

And those were all the tricks for this time!

  1. Place code as close to where it’s relevant as possible State Colocation will make your React app faster - Kent C. Dodds

  2. When an element type starts with a lowercase letter, it refers to a built-in component like <div> or <span> and results in a string ’div’ or ’span’ passed to React.createElement. Types that start with a capital letter like <Foo /> compile to React.createElement(Foo) and correspond to a component defined or imported in your JavaScript file. We recommend naming components with a capital letter. If you do have a component that starts with a lowercase letter, assign it to a capitalized variable before using it in JSX. JSX In Depth - User-Defined Components Must Be Capitalized

  3. Also passing dispatch down is a better idea than passing a setter because it restricts what child can do. Dan Abramov tweet

  4. When you nest content inside a JSX tag, the parent component will receive that content in a prop called children. React Docs: Passing JSX as children

  5. An element is a plain object describing a component instance or DOM node and its desired properties. It contains only information about the component type (for example, a Button), its properties (for example, its color), and any child elements inside it. An element is not an actual instance. Rather, it is a way to tell React what you want to see on the screen. React Docs: React Components, Elements, and Instances - Elements Describe the Tree

  6. State is reserved only for interactivity, that is, data that changes over time. Thinking in React

  7. You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance. React Hooks Reference: useMemo