React Anti-Patterns
Rough list of React anti—patterns with quotes I've compiled while reading docs, blog posts, and scrolling Twitter.
I think there are a lot of ways to write 'bad React' or 'React cut against the grain' that will still work, but will be needlessly complex, hard to read, more bug prone and just conceptually wrong. It might work but for the wrong reasons.
These are 'escape hatch' hooks, don’t overuse them.
useEffect
useRef
useMemo
useCallback
React state should be kept minimal, it’s a core principle of React for a reason. Introducing redundant states makes your app needlessly complex.
You’re making multiple sources of truth and now you have to keep those in sync. Nothing stays in sync unless it’s forced to.
With redundant states you’re creating places where bugs can creep in.
it’s easier to keep a single source of truth and derive values from that.
Keep the source data in state, derive what you need in render or even handlers.
Don’t duplicate/sync states and don’t put non-state values in useState
.
Derive, derive, derive!
Avoid state sprawl
Ask three questions about each piece of data:
- Is it passed in from a parent via props? If so, it isn’t state.
- Does it remain unchanged over time? If so, it isn’t state.
- Can you compute it based on any other state or props in your component? If so, it definitely isn’t state!
- Don’t use state when a regular variable works. State is only used to persist information between re-renders .
- Don’t add redundant state. If you can calculate something during render, you don’t need state for it.
If you can calculate some information from the component’s props or its existing state variables during rendering, you should not put that information into that component’s state.
There are two types of “model” data in React: props and state. The two are very different:
- Props are like arguments you pass to a function. They let a parent component pass data to a child component and customize its appearance. For example, a
Form
can pass acolor
prop to aButton
.- State is like a component’s memory. It lets a component keep track of some information and change it in response to interactions. For example, a
Button
might keep track ofisHovered
state.
Props and state are different, but they work together. A parent component will often keep some information in state (so that it can change it), and pass it down to child components as their props.
Next you’ll need to represent the visual states of your component in memory with
useState
. Simplicity is key: each piece of state is a “moving piece”, and you want as few “moving pieces” as possible. More complexity leads to more bugs!
The biggest problem with [duplicate states] is some of that state may fall out of sync with the true component state (
squares
). It could fall out of sync because we forget to update it for a complex sequence of interactions for example If you've been building React apps for a while, you know what I'm talking about. it’s no fun to have things fall out of sync. One thing that can help is to reduce duplication so that all relevant state updates happen in one place: We're no longer managing updating the squares state, so how do we keep these variables up to date? useEffect? Just call the state updaters when squares change right in the render method? I've seen people do all of these things... And none of them are great. The better way to do this is just to calculate it on the fly: (ie in render).
The key [...] is that for any piece of data, you need to pick a single component that owns it as the source of truth, and avoid duplicating it in other components.
Stuffing values that don’t need to be managed by React into state is more sneaky. It rarely results in visible bugs, but makes your components more complex and slows them down.
These examples [of redundant states] are all based on real-world code I’ve seen. And they all were in code that worked, the program executed as expected. But they’re still problematic. This type of derived state is error-prone; when new code is written to update the state, each new programmer touching the code has to understand the relationship, or the whole thing can get out of sync. When state is coming from props, like the error example above, you have to make sure that the state gets updated properly when the props change. And when the state gets out of sync, it is possible to end up in strange states where a programmer’s expectations are violated.
Just Derive State At Render Time. For many many things, including the 3 examples above, if you need information at render time that can be derived from other pieces of state, it’s totally fine to just calculate it at render time.
Storing derived information is unnecessary and harmful when calculating it at runtime adds minimal performance overhead or repetition.
useEffect
has the dubious honour of being the most misunderstood and misused hook!
Maybe this is not so much about
useState
after all, but more about a misconception withuseEffect
: It should be used to sync your state with something outside of React. UtilizinguseEffect
to sync two react states is rarely right. So I'd like to postulate the following:Whenever a state setter function is only used synchronously in an effect, get rid of the state!— TkDodo
[The default behavior of
useEffect
is not an infinite loop] unless yousetState
in it. the flaw is we failed to teach people thatsetState
in it is usually a bad idea.i went through 100+ examples at some point in the fb codebase, and about 30% of
useEffect
calls i’ve seen were unnecessary and/or wrong ways to approach a problem. this is partly an educational issue (we need to explain when not to use it) and partly the api being too generic.it doesn’t help that typically it’s one of the first things people are taught. (by us as well.) so people get the idea that it’s an api you’re supposed to use often.
useEffect
is the last api you’re supposed to use — it’s the last resort when absolutely nothing else works.one way to think about it is that
useEffect
is a way to synchronize React with some external system (network, subscription, DOM). if you don’t have any external system and just try to manage your data flow withuseEffect
, you’re gonna have all these problems.when you synchronize with an external system, you absolutely do want it to re-run when something changes. that’s what synchronization means. synchronizing an extra time is never a problem except perf (which dependencies let you optimize).
another way to think about
useEffect
is that the goal is to have a more concrete dedicated Hook for each common concrete use case so that you almost never have to actually writeuseEffect
yourself.you can think of
useEffect
as either a synchronization “input” (subscribe to something -> receive data -> set state). or as a synchronization “output” (something changes -> write it somewhere, like to DOM). You only get loops if you start mixing the two, like short circuiting.
In practice, you can make a lot of memoization unnecessary by following a few principles:
[...]
- Avoid unnecessary Effects that update state. Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over.
- Try to remove unnecessary dependencies from your Effects. For example, instead of memoization, it’s often simpler to move some object or a function inside an Effect or outside the component.
Remember that the function passed to
useMemo
runs during rendering. doesn’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong inuseEffect
, notuseMemo
.
useMemo()
is not for side effects. It’s for pure computation. Resetting state based on other state is here: Hooks FAQ – How do I implement getDerivedStateFromProps?
useCallback
is for referential equality of functions (callbacks) in React components, often called ‘making a function stable’ or ‘stabilizing’.
The principle that I go by for useCallback
is that it’s a last resort, only use it if we really can’t solve it another way.
See Royi Codes: Death by a thousand useCallbacks
See Zhenghao: Preemptive Memoization In React Is Probably Not Evil (Yet)
Shallow comparison isn't free.
A common mistake is to think functions shouldn’t be dependencies. [It] seems like it could work. And to be clear, this code does work. But the problem with simply omitting local functions is that it gets pretty hard to tell whether we’re handling all cases as the component grows!
If we forget to update the deps of any effects that call these functions (possibly, through other functions!), our effects will fail to synchronize changes from our props and state. This doesn’t sound great.
Luckily, there is an easy solution to this problem. If you only use some functions inside an effect, move them directly into that effect:
I want to emphasize that putting
useCallback
everywhere is pretty clunky. It’s a nice escape hatch and it’s useful when a function is both passed down and called from inside an effect in some children. Or if you’re trying to prevent breaking memoization of a child component. But Hooks lend themselves better to avoiding passing callbacks down altogether.
The recommended fix is to move that function inside of your effect. That makes it easy to see which props or state your effect uses, and to ensure they’re all declared:
If for some reason you can’t move a function inside an effect, there are a few more options:
You can try moving that function outside of your component*. In that case, the function is guaranteed to not reference any props or state, and also doesn’t need to be in the list of dependencies.
If the function you’re calling is a pure computation and is safe to call while rendering, you may call it outside of the effect instead, and make the effect depend on the returned value.
As a last resort, you can add a function to effect dependencies but**wrap its definition into the
useCallback
Hook. This ensures it doesn’t change on every render unless /its own/ dependencies also change:
- Is the function only used inside one effect? Yes? Move it in there.
- Are you sure that making that one function stable will prevent a component from rerendering? Are the other values in that components props changing often between renders?
- Does the useCallbacked function change with props that you’re also passing to a child component? I.e. are the useCallback deps also passed as props, fe example some state?
- Does the function really need to be inside React? Does it use any information from the render scope? Does the function use anything from the data flow (props or state)? Can you move it out? Maybe by adding parameters for the information it is reading from render scope?
- Does it make a big difference if the child rerenders? Is the child component (subtree) slow/expensive? Is the function defined high up in the component tree?
- Can you use a state updater function to avoid a dependency?
- Can you use a state updater module function instead of wrapping it in usecallback?
- Can you use a
useReducer
and pass thedispatch
function down? Can you use context module functions? It’s OK to useuseReducer
even if the state is small or medium-sized.
In the broader anti-pattern of optimizing too much, like too much useCallback
s and too many useMemo
s.
Just because it takes some work to figure out which ones to optimize, doesn’t mean we should turn it on everywhere. It will make the whole app slower. How is that possibly better? Shallow comparisons aren’t free. They’re
O(prop count)
. And they only buy something if it bails out. All comparisons where we end up re-rendering are wasted. Why would you expect always comparing to be faster? Considering many components always get different props.
See Zhenghao: Preemptive Memoization In React Is Probably Not Evil (Yet)
Placing state unnecessarily high up in tree can kill performance as a whole subtree needs to rerender. memo
might help but it’s better to move to state down if possible. Place state as low as possible.
You can even make a new component just to hold some state. See my post State Container Components for a practical example of this.
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.
Inline React components is when you’re declaring components inside of other components.
This is one undisputed anti-pattern. It will instantiate a new React component on each render, leading to very weird behavior and bad performace.
There's an ESLint rule for this in eslint-plugin-react that I recommend you turn on: react/no-unstable-nested-components
.
Components can render other components, but you must never nest their definitions: The snippet above is very slow and causes bugs. Instead, define every component at the top level: When a child component needs some data from a parent, pass it by props instead of nesting definitions.
Element type changes (reference equality,
current.elementType === element.type
):
-> Reconciliation sees it as a new component and replaces it and its children
-> Children lose their current state
No, [nesting components] is not supported (and has never been) and will reset state of the entire children tree every time the parent re-renders. we should add a lint rule or something. generally we haven’t been proactive warning about it because it’s assumed to be clear that something is wrong — the entire tree will be reset including all of its state so you’re bound to notice it. is that not the case in your experience? how do people miss this?
The point of components is that we let React decide when to call them.
You might be wondering: why don’t we just call components directly? Why write
<Form />
rather than Form()?React can do its job better if it “knows” about your components rather than if it only sees the React element tree after recursively calling them.
If we called
Comments()
as a function, it would execute immediately regardless of whether Page wants to render them or not
[...]
But if we pass a React element, we don’t execute Comments ourselves at all:
[...]
This lets React decide when and whether to call it. If ourPage
component ignores its children prop and renders<h1>Please log in</h1>
instead, React won’t even attempt to call theComments
function. What’s the point? This is good because it both lets us avoid unnecessary rendering work that would be thrown away, and makes the code less fragile. (We don’t care ifComments
throws or not when the user is logged out — it won’t be called.)
I recommend reading the rest of the section in that blog post.
Another good read on this is Don’t call a React function component.
While this will work ‘fine’, it’s counter to React’s idea/convention that every function that returns React elements to render should be a component. One that has a capitalised name, receives a single object parameter: props and is written in JSX. I’m not sure where this comes from, an aversion to making small component? It’s OK to extract it to a child component. If it’s enough to warrant a separate function, why not extract it to a component so we’re not cutting against the grain? Relying on closure makes it implicit what data the function has access to. In contrast, props make this explicit. That’s what props are for.
If your components get messy with too much nested conditional markup, consider extracting child components to clean things up
This is like a bastardised pseudo-component. It’s a function that returns a React element, but it doesn’t take props and can’t be rendered using JSX. It’s also usually defined /before/ the render method, so you end up reading some random child elements out of context before you know where they go in the parent.
I honestly can’t see any reason to use this pattern over a simple function component. A component can use hooks, can be memoised, is more idiomatic React—the framework gives you a specific way for modularising your render, so why not use it?
If you’re repeating yourself in the return, you can assign JSX to a variable up front (an element variable) and use that in multiple places, and even pass it on as props!
When you need to extract markup from a component or logic, don’t put it in a function living in the same component. A component is just a function. Defining it this way is nesting inside its parent. This means that it will have access to all the state and data of its parent. It makes the code more unreadable - what is this function doing in between all the components? Move it in its own component, name it and rely on props instead of a closure.
Too many props will make your component less readable and less understandable This is a sign of ill-composed components, which is another anti-pattern.
Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many components in the middle, or if many components in your app need the same information
Passing props is a great way to explicitly pipe data through your UI tree to the components that use it. But it can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and lifting state up that high can lead to a situation sometimes called “prop drilling
Context is very tempting to use! However, this also means it’s too easy to overuse it. Just because you need to pass some props several levels deep doesn’t mean you should put that information into context. Here’s a few alternatives you should consider before using context:
- Start by passing props . If your components are not trivial, it’s not unusual to pass a dozen props down through a dozen components. It may feel like a slog, but it makes it very clear which components use which data! The person maintaining your code will be glad you’ve made the data flow explicit with props.
- Extract components and pass JSX as children to them. If you pass some data through many layers of intermediate components that don’t use that data (and only pass it further down), this often means that you forgot to extract some components along the way.
What good is it to render the component when it cant do anything yet?
I don’t like living in ‘data limbo’, having to check and handle the value being undefined
or null
at every turn gets tedious real fast.
That's a lot of additional checks for nothing. Save yourself the pain and just hold off rendering until you have everything you need (or most of it).
I go by: when the data is not the ready, the component is not ready. Maybe I subconsiously got that from someone else and forgot.
I view TypeScript optional ?
for optional props, should not be used for signifying that data possible isn’t defined yet.
An accumulating hook is one that returns some data consumed from another custom hook. One hook should be the owner of some data, much like how one component should be the owner of some state in React.
Compare
_17function SubmitButton({ status, isNew }) {_17 const isCreating = status === "creating";_17 const isUpdating = status === "updating";_17_17 return (_17 <button_17 name="intent"_17 type="submit"_17 value={isNew ? "create" : "update"}_17 disabled={isCreating || isUpdating}_17_17 >_17 {isNew ? (isCreating ? "Creating..." : "Create") : null}_17 {isNew ? null : isUpdating ? "Updating..." : "Update"}_17 </button>_17 )_17}
with
_24function SubmitButton({ status, isNew }) {_24 const isCreating = status === "creating";_24 const isUpdating = status === "updating";_24_24 return isNew ? (_24 <button_24 name="intent"_24 type="submit"_24 value="create"_24 disabled={isCreating}_24 >_24 {isCreating ? "Create" : "Creating..."}_24 </button>_24 ) : (_24 <button_24 name="intent"_24 type="submit"_24 value="update"_24 disabled={isUpdating}_24 >_24 {isUpdating ? "Update" : "Updating..."}_24 </button>_24 );_24}
The first is shorter but harder to follow. In my opinion, it’s nicer to switch on isNew
and write two <button>
elements.
See also Good advice on JSX conditionals.