Neat Little React Tricks
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 1. 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:
_10import { LeftSidebar, RightSidebar } from "./sidebars";_10_10function Sidebar({ position, size, children }) {_10 const SidebarComponent = position === "left" ? LeftSidebar : RightSidebar;_10_10 return <SidebarComponent size={size}>{children}</SidebarComponent>;_10}
Another real world use case is conditionally replacing an old component with a newly refactored one:
_16// class-based or perhaps just wonky_16import { UserPermission as OldUserPermission } from "./old-user-permissions";_16// functional and clean_16import { UserPermission as NewUserPermission } from "./new-user-permissions";_16_16function 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.
_10const [isOpen, toggleOpen] = useReducer((prevIsOpen) => !prevIsOpen, false);_10_10// Flip the switch_10toggleOpen();
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 wrapped 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 2. 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 3.
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”,
_10export 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
JSX is just another way to React.createElement
, and are just plain objects 4.
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>
.
_13export 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.
_10function TableCell({ value, isOutdated }) {_10 const tableCell = <td>{value}</td>;_10_10 return isOutdated ? (_10 <Tooltip text="This value is not up to date">{tableCell}</Tooltip>_10 ) : (_10 tableCell_10 );_10}
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.
_15function 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 5.
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 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.
_10function Image(props) {_10 const [intersectionObserver] = useState(() => new IntersectionObserver());_10_10 return (_10 // JSX_10 )_10}
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 6.
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.
_14function 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.
And those were all the tricks for this time!
-
↩When an element type starts with a lowercase letter, it refers to a built-in component like
JSX In Depth - User-Defined Components Must Be Capitalized<div>
or<span>
and results in a string’div’
or’span’
passed toReact.createElement
. Types that start with a capital letter like<Foo />
compile toReact.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. -
↩Also passing dispatch down is a better idea than passing a setter because it restricts what child can do.
Dan Abramov tweet -
↩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 -
↩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 -
↩State is reserved only for interactivity, that is, data that changes over time.
Thinking in React -
↩You may rely on
React Hooks Reference: useMemouseMemo
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 withoutuseMemo
— and then add it to optimize performance.