Neat Little React Tricks

Some small React tricks you may or may not have seen before.

Published · Last updated

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

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

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


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

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


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

Yeah you could conditionally render the element with a ternary expression, or an early return if that’s your jam, 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.


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

A nice benefit is that the dispatch function toggleOpen is stable (referentially the same function each render), whereas a callback for setting state is not. Calling a setter-wrapping callback in a useEffect requires wrapping it in useCallback to make it referentially stable. And, sure, 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 . 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✨ .

In my opinion, using a dispatch or a state updater function is much nicer than using a callback to setState.

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 . 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”,


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

JSX is just another way to React.createElement, and React elements are just plain objects . 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 anchor tag inside the cite element. If no url is given, no anchor element should be inside the <cite>.


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

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


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

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 .

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

Why would we want to do this? 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 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 accepts an initializer function to ensure the initial state is created just once. React calls the initializer only on mount of the component. The value should never change, so we can just ignore the state setter by not destructuring it.


function Image(props) {
const [intersectionObserver] = useState(() => new IntersectionObserver());
...
}

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 props and state, we shouldn’t rely on it for one-time initializations . 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.

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

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


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

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.

The useEffect callback should return nothing at all or if needed, a cleanup function . No other returns are allowed. This is why the callback can’t be async, because async functions implicitly return a Promise.

It’s also why the function body should be wrapped in curly brackets for arrow functions. If those are left off, the expression implicitly becomes the return value . With curly brackets, Prettier will dilligently move the function body to a new line.

But sometimes I prefer a small fire-and-forget useEffect to be on a single line. How can we do that?


// Curly brackets body, so multiple lines
useEffect(() => {
fetch("/hit-counter");
}, []);

Here, removing the curly brackets would result in the callback returning a fetch promise , which is not allowed.


// The callback returns a promise, not allowed!
useEffect(() => fetch("/hit-counter"), []);

We can make the useEffect single-line by adding a void operator, it evaluates the expression and then returns undefined , that’s the trick.


// One line and no returned value, all good
useEffect(() => void fetch("/hit-counter"), []);

Now Prettier is satisfied and so am I.

And those were all the tricks for this time!

  1. 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

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

  3. 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

  4. 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

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

  6. 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

  7. Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, “connect” needs “disconnect,” “subscribe” needs “unsubscribe,” and “fetch” needs either “cancel” or “ignore”.React Beta Docs: How to write an Effect

  8. Arrow functions can have either a concise body or the usual block body. In a concise body, only an expression is specified, which becomes the implicit return value. In a block body, you must use an explicit return statement.MDN: Arrow Functions

  9. The global fetch() method starts the process of fetching a resource from the network, returning a promise which is fulfilled once the response is available.MDN: fetch

  10. The void operator evaluates the given expression and then returns undefined.MDN: Void