In this post, we’ll go over these ESLint rules and plugins, including as they apply to Hooks. Here are some quick links for you to jump around:
React Hooks rules (
This plugin only contains two rules, but they are critical to avoiding common pitfalls when writing function components with Hooks.
This rule enforces that components follow the Rules of Hooks when using Hooks. The rules are discussed in detail in the React documentation, but there are two rules that must be followed when using Hooks:
- Hooks should only be called from the top-level code of your component. What this really means is that the Hooks should not be called conditionally — they should instead be called on every render, in the same order, to avoid issues and subtle bugs
- Hooks should only be called from either a function component, or another Hook
- Custom Hooks often compose behavior together from inbuilt, or even other custom, Hooks
In the default configuration, violations of this rule will cause an error, causing the lint check to fail.
This rule enforces certain rules about the contents of the dependency array that is passed to Hooks, such as
useMemo. In general, any value referenced in the effect, callback, or memoized value calculation must be included in the dependency array. If this is not done properly, issues such as out-of-date state data or infinite rendering loops can result.
This rule is good at finding potential dependency-related bugs, but there are some limitations:
- Custom Hooks with dependency arrays will not be checked with this rule. It only applies to the inbuilt Hooks
- The rule can only properly check dependencies if it is a static array of values. If a reference to another array is used, or another array is spread into it, the rule will emit a warning that it cannot determine the dependencies
This rule has been somewhat controversial; there are several long issue threads on GitHub, but the React team has been good about soliciting and incorporating feedback. In the default configuration, violations of this rule are treated as warnings.
The details of this rule could take up an entire article on their own. For a deeper dive on this rule and how to properly use it, see the Understanding the React exhaustive-deps linting warning article, here on the LogRocket blog.
React rules (
This plugin contains a lot more rules (100 rules at time of writing) that are specific to the core of React. Most rules cover general React practices, and others cover issues related to JSX syntax. Let’s take a look at some of the more useful ones.
For accessibility reasons, most clickable elements in a component that aren’t simple links to another URL should be implemented as buttons. A common mistake is to omit the
type attribute from these buttons when they aren’t being used to submit a form.
type is specified, a button defaults to a type of
submit. This can cause issues for buttons that descend from a
form element. Clicking such a button inside of a form will cause a potentially unwanted form submission.
Action buttons that are not intended to submit a form should have a
type attribute of
This rule enforces that all buttons explicitly have a
type attribute — even ones that are intended as Submit buttons. By being explicit, unintentional submissions are avoided and the intent of the code is clear.
Requires that all React components have their props described in a
PropTypes declaration. These checks only throw errors in development mode but can help catch bugs arising from the wrong props being passed to a component.
If your project uses TypeScript, this rule is also satisfied by adding a type annotation to the component props that describes them.
These two approaches are covered in detail in Comparing TypeScript and PropTypes in React applications by Dillion Megida.
More great articles from LogRocket:
Depending on the component, some props may be required while others are optional. If an optional prop is not passed to a component, it will be
undefined. This may be expected but can introduce bugs if the value is not checked.
This rule requires that every optional prop is given a default value inside of a
defaultProps declaration for the component. This default value can be explicitly set to
undefined if that is what the component expects.
With function components, there are two different strategies that can be used to check default props:
This strategy expects the function component to have a
defaultProps object with the defaults.
const MyComponent = ( action ) => ... MyComponent.propTypes = Action: PropTypes.string; ; MyComponent.defaultProps = action: 'init' ;
const MyComponent = ( action = 'init' ) => ... MyComponent.propTypes = Action: PropTypes.string; ;
If you use the
defaultArguments strategy, there should not be a
defaultProps object. If there is, this rule will fail.
When rendering a list of items in React, we typically call
map on an array, and the mapping function returns a component. To keep track of each item in the list, React needs these components to have a
A common pitfall with rendering lists is using the array index as the key. This can cause unnecessary or even incorrect renders. The React documentation advises against this practice due to the issues it can cause (there is also a more detailed discussion about how keys are used). A key is expected to be a unique identifier for that item, within the list, that does not change, like the primary key value in a database row.
This rule ensures that the array index is not used as the key.
Consider this simple React component:
const Greeter = ( name ) => <div>Hello name!</div>;
React object is not referenced at all. However,
React.createElement in place of JSX elements. The above component might be transpiled to something like this:
const Greeter = ( name ) => React.createElement("div", null, "Hello ", name, "!");
The references to
React here are why
React must still be imported. This rule ensures that all files with JSX markup (not necessarily even a React component) have
React in scope (typically through an
Always importing React is necessary for proper transpilation, but when ESLint looks at the file, it’s still JSX, so it won’t see
React referenced anywhere. If the project is using the
no-unused-vars rule, this results in an error since
React is imported but not used anywhere.
This rule catches this situation and prevents
no-unused-vars from failing on the
For proper debugging output, all React components should have a display name. In many cases, this won’t require any extra code. If a component is a named function, the display name will be the name of the function. In the below examples, the display name of the component will be
const MyComponent = () => …
const MyComponent = function() return …;
export default function MyComponent() return …;
There are some cases where the automatic display name is lost. This is typically when the component declaration is wrapped by another function or higher order component, like in the two examples below:
const MyComponent = React.memo(() => … );
const MyComponent = React.forwardRef((props, ref) => … );
MyComponent name is bound to the new “outer” component returned by
forwardRef. The component itself now has no display name, which will cause this rule to fail.
When these cases arise, a display name can be manually specified via the
displayName property to satisfy the rule:
const MyComponent = React.memo(() => ... ); MyComponent.displayName="MyComponent";
React components accept a special prop called
children. The value of this prop will be whatever content is inside the opening and closing tags of the element. Consider this simple
const MyList = ( children ) => return <ul>children</ul>; ;
This will render the outer
ul element, and any children we put inside the element will be rendered inside of it.
<MyList> <li>item1</li> <li>item2</li> </MyList>
This is the preferred pattern with React components. It is possible, though not recommended, to pass children as an explicit children prop:
<MyList children=<li>item1</li><li>item2</li> />
The above usage will actually cause an error because JSX expressions, like the one passed as the explicit children prop, must have a single root element. This requires the children to be wrapped in a fragment:
<MyList children=<><li>item1</li><li>item2</li></> />
As shown in the first example, children are passed as child elements to the component directly, so the component is the root element of the expression. No fragment or other enclosing element is needed here.
This is mainly a stylistic choice/pattern, but it does prevent inadvertently passing both an explicit
children prop and child elements:
<MyList children=<><li>item1</li><li>item2</li></>> <li>item3</li> <li>item4</li> </MyList>
In this case, the child elements (
item4) would be displayed, but
item2 would not. This rule ensures that children are only passed in the idiomatic way, as child JSX elements.
dangerouslySetInnerHTML prop allows arbitrary markup to be set as the
innerHTML property of an element. This is generally not recommended, as it can expose your application to a cross-site scripting (XSS) attack. However, if you know you can trust the input and the use case requires it, this approach may become necessary.
The prop expects an object with an
__html property, whose value is a raw HTML string. This string will be set as the
Because this replaces any existing child content, it doesn’t make sense to use this in combination with a
children prop. In fact, React will throw an error if you attempt to do this. Unlike some errors that only appear in development mode (like
PropTypes validation errors), this error will crash your app.
This rule enforces the same rule. If
dangerouslySetInnerHTML is used with children, the lint rule will fail. It’s much better to catch these errors when linting, or at build time, rather than reported by users once the app is deployed!
Every time a React component is rendered, it comes at a performance cost. Oftentimes, certain patterns or practices can cause a component to unnecessarily re-render itself. There are many causes for this behavior, and this rule helps prevent one of them.
When any function is defined inside the component, it will be a new function object on every render. This means that whenever the component is re-rendered, the prop is considered changed. Even with
React.memo, the component will re-render.
If the child component has any
useEffect calls that take that function as a dependency, this can cause the effect to run again, creating the potential for an infinite loop that will likely freeze the browser.
With this rule enabled, any function that is passed as a prop will be flagged.
There are two ways this can be addressed. If the function does not depend on anything else inside the component, it can be moved outside of the component, where it is just a plain function that will always be the same memory reference. This ensures that the same function is passed to the prop each time.
For cases where the function does depend on the component in some way, the usual fix for this is to memoize it with the
useCallback Hook. Any properties referenced in the function will have to be included in the
useCallback dependency array; sometimes this requires multiple levels of memoization of values or functions.
This adds some complexity, but has the benefit of helping to reduce extra renders and prevent infinite loops.
The rules covered here are just a few of the ones provided by the
eslint-plugin-react plugin. Some rules can be opinionated or overzealous, but most also have configuration options to make them less strict.
There is also another very helpful ESLint plugin centered around JSX and accessibility practices:
eslint-plugin-jsx-a11y. The rules in this plugin check your JSX markup to make sure that good HTML accessibility practices are being followed.
These React ESLint plugins can be helpful to avoid common pitfalls, especially if you’re still new to React. You can even write your own rules and plugins to cover other situations!
Full visibility into production React apps
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.