An Animated Guide to Reacts Optional Parameters

Reacts API includes several optional and alternative parameters which provide additional behavior like optimization or other extras that occasionally can come in useful. Let’s look at those as animated diagrams.

Published · Last updated

Did you know that the React API includes several optional and alternative parameters? Hidden extras that provide ‘bonus behaviors’. Most are meant for optimization and can help you squeeze the most performance out of your React app.

While these are not the most essential parts of React to know, it’s good to have a solid understanding of all the tools at your disposal. So, let’s take a closer look at these hidden extras. We’ll go over them in order of commonness.

useState doesn’t really have optional parameters but you can optionally trigger special behavior by passing a function instead of a value to useState itself or its setter function. I call these alternative parameters, useState is the only part of React that has these.

React Docs

Components are contained pieces of the UI with logic for both initializing and updating. This is very convenient. One thing to note is that initialization logic is only for well, initialization. After that we don’t need to evaluate it anymore. Initial arguments for hooks, like for useState, are read only on mount and ignored on updates.

Animation of a regular useState initalisationconst[state,setState]=useState(MountinitialValue)

The initial arguments to hooks are still declared on every update though. Most of the time this is not a problem, only when declaring the initial argument is , it will be wasted effort on updates that might contribute to noticable jank.

To avoid recreating the initial state, React lets you pass an initializer function that creates the initial state. React will call it when the component mounts and not on updates . Maybe you’ve heard of this strategy as lazy evaluation.

Animation of a useState initialiser functionconst[state,setState]=useState(() =>getInitialValue())MountconstinitialValue=getInitialValue()

React Docs

Usually you update state by passing a new value to the state-setter function, like this:

Animation of a regular useState updateconst[state,setState]=useState(initialValue)Event or effectsetState(newValue)

Instead of a new value, React also lets you pass a function that calculates the next state based on the previous state; a state updater function.


// |-state updater function-|
setCount(prevCount => prevCount + 1)

A state updater function can be more convenient when updating state based on current state.

You no longer need to pass the state variable around to access it for an update. This helps you avoid adding to a dependency list when updating state based on previous state within a hook with a dependency list (like useEffect or useCallback)

I really like this description from A Complete Guide to useEffect:

When we want to update state based on the previous state, we can use the functional updater form of setState. [W]e only truly needed count to transform [state] and “send it back” to React. But React already knows the current [state]. All we needed to tell React is to increment the state — whatever it is right now.

That’s exactly what setCount(prevCount => prevCount + 1) does. You can think of it as “sending an instruction” to React about how the state should change.

The second benefit is that a state update functions enables queuing of updates. To briefly explain, queuing is applying multiple updates on top of each other from a single event. The React docs has a whole page on it that explains it better than I could, so I’ll just refer you to that: Queueing a Series of State Updates.

Here’s my animated diagram of it.

Animation of a useState state updater functionconst[state,setState]=useState(initialValue)Event or effect1.setState(pendingState=>transform(pendingState))constnextState=transform(pendingState)2.setState(pendingState=>transform(pendingState))3.setState(pendingState=>transform(pendingState))

The React docs calls it an “uncommon use case” and I have to agree. I haven’t needed to batch updates a whole lot, at most a couple of times.

Reusing state updater functions

By convention, state updater functions are writting inline as anonymous arrow functions. To reuse them, you can define state updater functions in module scope. It’s not common but I occasionally quite like it, see my post State Updater Module Functions for more on this pattern.

React Docs
Animation of normal useReducer initialisationconst[state,dispatch]=useReducer(reducer,MountinitialArg)

Like useState, useReducer supports an initializer function but the way it takes it is different. Rather than an alternative parameter type, it takes the initializer function as an optional parameter .

The initializer is called with the preceding parameter; initArg. Confusing? That’s why I like diagrams better for explaining this, even more so when they’re animated:

Animation of useReducer intialiser functionconst[state,dispatch]=useReducer(reducer,initialArg,init)MountconstinitialValue=init(initialArg)
Making a reducers parameters optional

In a way, a reducers parameters are optional. You’re not obliged to use the state or action parameters, a reducer only has to return new state.

Ignoring action or state parameter makes it an actionless or stateless reducer respectively. There are reasons to do so, I wrote about that and when that’s useful in Actionless and Stateless Reducers in React.

React Docs

memo is for optimizing rendering behavior, it lets you skip re-rendering a component when its props are unchanged . I’ve always thought of like this: memo turns your components props into a dependency list for that component. React checks the dependencies — i.e. props — every render and will only rerun the component when one has changed.

By default, React will compare each prop with Object.is , just like values in dependency lists. memo's optional parameter, arePropsEqual, lets you declare a custom comparison function.

Animation of memo with a custom comparison functionconstMemoizedComponent=memo(SomeComponent,arePropsEqual)constisEqual=arePropsEqual(oldProps,newProps)Rerendersome-file.jsxTrueSkipFalseRerenderRenderSkip/><MemoizedComponentsize={100}person={user}isSepia={false}
Click the True or False boxes to see the effect of memo.

But most likely you won’t use memo all that often and its optional parameter very rarely or perhaps never, but now you know about it at least.

When to use memo is a subject of its own. This bit in the docs is a good guideline:

If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful.

That whole section is worth a read. My personal, hand–wavy advice is You probably don’t need memo, or less often than you think. JavaScript engines are highly–optimized beasts and our intuitions on what’s slow are often wrong, at least mine is. So, a final word of caution from the docs on using a custom comparison function:

If you [use arePropsEqual], use the Performance panel in your browser developer tools to make sure that your comparison function is actually faster than re-rendering the component. You might be surprised.

React Docs

useDebugValue is a React Hook that lets you add a label to a custom hook in the React DevTools . It’s among the most rarely used hooks. Big chance you’ll never ever use this hook and even bigger chance you’ll never have to use its optional parameter. It might just be the most obscure part of the React API. Let’s look at it anyway.

Usually you just pass the string to display in the DevTools to useDebugValue:

Animation of normal useDebugValue usageuseCustomHook()useDebugValue(value)React DevToolsCustomHook:value

Now suppose you want to format some giant grid in state for useDebugValue.


const grid = [
{ a: 10, b: 4, c: 8, ... , j: 4},
{ a: 2, b: 8, c: 1, ... , z: 8},
// ... many more rows
]

It would be nice to view it without the keys being repeated for each row, as an array of strings like this:

['   ‖  A  |  B  |  C  |  ... |  J',
' 0  ‖  10 |  4  |  8  |  ... |  4',
' 1  ‖   2 |  8  |  1  |  ... |  8']
(Many more rows)

We’ll need a formatGrid function to format it like that.


useDebugValue(formatGrid(grid));

Great, but you, and maybe a few of your fellow devs are the only ones who will see the result of calling formatGrid. Yet it will run for everyone, even for those who don’t look at the React DevTools, and also for users who have no idea of what React even is (and why should they? The app just needs to work).

Once again we need lazy evaluation to defer calling it. That’s why useDebugValue accepts an optional format function.

The format function takes the preceding parameter; value, similar to how the init function of useReducer takes the preceding parameter. React won’t call the format function until the hook is inspected in the React DevTools, sparing some computation for our users.

View this CodeSandbox for live code of this example.


useDebugValue(grid, formatGrid);

Animation of useDebugValue with a formatter functionuseCustomHook())format,value(useDebugValueDevTools inspectconstformattedValue=format(value)React DevToolsCustomHook:formattedValue

React Docs

useSyncExternalStore is another rarely used hook. It is mostly meant for library authors, it does have some use cases in app code. useSyncExternalStore accepts an optional parameter to provide an initial value when the app is run outside of a browser environment, like on a server. In that case, this parameter is mandatory, an error will be thrown if it’s missing .

No animation for this one. I didn’t have good ideas and I just didn’t feel like making one ¯\_(ツ)_/¯. Maybe someday.

We can group React’s optional parameters into two camps: 1) optimization and 2) other additional behavior.

  • useState initializer
  • useReducer initializer
  • useDebugValue formatter

  • memo custom comparison function

  • useSyncExternalStores getServerSnapshot for SSR support
  • useState state updater queueing series of state updates in one event

Or summarized as a nifty circle pack diagram:

Circle pack diagram showing the grouping of the optional parameters in React

I didn’t discuss the effect dependency lists here (useEffect, useLayoutEffect, and I guess useImperativeHandle too), those are technically optional too. I don’t really see those as a ‘hidden extra’. More often then not, effects will have dependency lists so I left them out.

We’ve explored the depths of the React API. If you found this useful, share it on your social channels of choice. If you have questions, feel free to send me a tweet.

  1. React saves the initial state once and ignores it on the next renders. Although the result of createInitialTodos() is only used for the initial render, you’re still calling this function on every render. This can be wasteful if it’s creating large arrays or performing expensive calculations. To solve this, you may pass it as an initializer function to useState instead:
    React Docs: useState — Avoiding recreating the initial state
  2. It is an uncommon use case, but if you would like to update the same state variable multiple times before the next render, instead of passing the next state value like setNumber(number + 1), you can pass a function that calculates the next state based on the previous one in the queue, like setNumber(n => n + 1). It is a way to tell React to “do something with the state value” instead of just replacing it.
    React Docs: Queueing a Series of State Updates — Updating the same state multiple times before the next render
  3. React saves the initial state once and ignores it on the next renders. Although the result of createInitialState(username) is only used for the initial render, you’re still calling this function on every render. This can be wasteful if it’s creating large arrays or performing expensive calculations. To solve this, you may pass it as an initializer function to useReducer as the third argument instead:
    React Docs: useReducer — Avoiding recreating the initial state
  4. memo lets you skip re-rendering a component when its props are unchanged. Wrap a component in memo to get a memoized version of that component. This memoized version of your component will usually not be re-rendered when its parent component is re-rendered as long as its props have not changed. But React may still re-render it: memoization is a performance optimization, not a guarantee.
    React Docs: memo
  5. optional arePropsEqual: A function that accepts two arguments: the component’s previous props, and its new props. It should return true if the old and new props are equal: that is, if the component will render the same output and behave in the same way with the new props as with the old. Otherwise it should return false. Usually, you will not specify this function. By default, React will compare each prop with Object.is.
    React Docs: memo - arePropsEqual
  6. useDebugValue is a React Hook that lets you add a label to a custom Hook in React DevTools.
    React Docs: useDebugValue
  7. optional getServerSnapshot: A function that returns the initial snapshot of the data in the store. It will be used only during server rendering and during hydration of server-rendered content on the client. The server snapshot must be the same between the client and the server, and is usually serialized and passed from the server to the client. If you omit this argument, rendering the component on the server will throw an error.
    React Docs: useSyncExternalStore - getServerSnapshot