Rules for React component lifecycle methods, state management, and component update patterns. These rules ensure proper usage of lifecycle methods and prevent common state-related issues.
Rules for safe and proper state management in React components.
const stateRules = {
/** Prevent usage of setState in componentDidMount */
'react/no-did-mount-set-state': ESLintRule;
/** Prevent usage of setState in componentDidUpdate */
'react/no-did-update-set-state': ESLintRule;
/** Prevent usage of setState in componentWillUpdate */
'react/no-will-update-set-state': ESLintRule;
/** Prevent using this.state within a this.setState */
'react/no-access-state-in-setstate': ESLintRule;
/** Enforce that setState is used properly */
'react/state-in-constructor': ESLintRule;
};Usage Examples:
// ✓ Good - Async operations without setState in componentDidMount
class MyComponent extends React.Component {
componentDidMount() {
this.fetchData(); // Async operation that will setState elsewhere
}
async fetchData() {
try {
const data = await api.getData();
this.setState({ data }); // setState in separate method is OK
} catch (error) {
this.setState({ error });
}
}
}
// ✗ Bad - setState directly in componentDidMount
class MyComponent extends React.Component {
componentDidMount() {
this.setState({ mounted: true }); // Can cause unnecessary re-render
}
}
// ✓ Good - Conditional updates in componentDidUpdate
class MyComponent extends React.Component {
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUserData();
}
}
}
// ✗ Bad - setState in componentDidUpdate without conditions
class MyComponent extends React.Component {
componentDidUpdate(prevProps) {
this.setState({ updated: true }); // Can cause infinite loop
}
}
// ✓ Good - Using functional setState to avoid state dependency
this.setState(prevState => ({
count: prevState.count + 1
}));
// ✗ Bad - Reading current state in setState
this.setState({
count: this.state.count + 1 // Race condition potential
});Rules for proper usage of React lifecycle methods and avoiding deprecated patterns.
const lifecycleRules = {
/** Prevent usage of componentWillMount, componentWillReceiveProps, componentWillUpdate */
'react/no-deprecated': ESLintRule;
/** Prevent usage of UNSAFE_ lifecycle methods */
'react/no-unsafe': ESLintRule;
/** Enforce ES5 or ES6 class for returning value in render function */
'react/require-render-return': ESLintRule;
/** Prevent usage of shouldComponentUpdate when extending React.PureComponent */
'react/no-redundant-should-component-update': ESLintRule;
/** Prevent usage of arrow functions in lifecycle methods */
'react/no-arrow-function-lifecycle': ESLintRule;
};Usage Examples:
// ✓ Good - Using modern lifecycle methods
class MyComponent extends React.Component {
static getDerivedStateFromProps(props, state) {
if (props.value !== state.previousValue) {
return {
derivedValue: props.value * 2,
previousValue: props.value
};
}
return null;
}
componentDidMount() {
this.setupComponent();
}
componentDidUpdate(prevProps, prevState) {
if (prevState.data !== this.state.data) {
this.processData();
}
}
render() {
return <div>{this.state.derivedValue}</div>;
}
}
// ✗ Bad - Using deprecated lifecycle methods
class MyComponent extends React.Component {
componentWillMount() {
// Deprecated and unsafe
this.setupComponent();
}
componentWillReceiveProps(nextProps) {
// Deprecated and unsafe
this.setState({ value: nextProps.value });
}
componentWillUpdate(nextProps, nextState) {
// Deprecated and unsafe
console.log('About to update');
}
}
// ✓ Good - Proper render method with return
class MyComponent extends React.Component {
render() {
return <div>Content</div>;
}
}
// ✗ Bad - Render method without return
class MyComponent extends React.Component {
render() {
console.log('rendering');
// Missing return statement
}
}
// ✓ Good - Regular methods, not arrow functions for lifecycle
class MyComponent extends React.Component {
componentDidMount() {
this.initializeComponent();
}
// Arrow function for event handlers is OK
handleClick = () => {
this.setState({ clicked: true });
}
}
// ✗ Bad - Arrow function for lifecycle methods
class MyComponent extends React.Component {
componentDidMount = () => {
this.initializeComponent();
}
}Rules for optimizing component performance and avoiding redundant updates.
const performanceRules = {
/** Prevent usage of shouldComponentUpdate when extending React.PureComponent */
'react/no-redundant-should-component-update': ESLintRule;
/** Enforce usage of optimization like PureComponent or React.memo */
'react/require-optimization': ESLintRule;
/** Prevent unused state */
'react/no-unused-state': ESLintRule;
/** Prevent unused class component methods */
'react/no-unused-class-component-methods': ESLintRule;
};Usage Examples:
// ✓ Good - PureComponent without shouldComponentUpdate
class MyComponent extends React.PureComponent {
render() {
return <div>{this.props.data}</div>;
}
}
// ✗ Bad - PureComponent with shouldComponentUpdate (redundant)
class MyComponent extends React.PureComponent {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.data !== this.props.data;
}
render() {
return <div>{this.props.data}</div>;
}
}
// ✓ Good - Component with shouldComponentUpdate
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.data !== this.props.data;
}
render() {
return <div>{this.props.data}</div>;
}
}
// ✓ Good - All state properties used
class MyComponent extends React.Component {
state = {
count: 0,
name: ''
};
render() {
return (
<div>
<span>{this.state.count}</span>
<span>{this.state.name}</span>
</div>
);
}
}
// ✗ Bad - Unused state property
class MyComponent extends React.Component {
state = {
count: 0,
unused: 'never referenced' // This state is never used
};
render() {
return <div>{this.state.count}</div>;
}
}Rules for proper state initialization patterns.
const initializationRules = {
/** Enforce state initialization in constructor vs class property */
'react/state-in-constructor': ESLintRule;
/** Enforce consistent placement of static properties */
'react/static-property-placement': ESLintRule;
};Usage Examples:
// ✓ Good - State in constructor (when rule enforces constructor)
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
loading: false
};
}
}
// ✓ Good - State as class property (when rule allows class properties)
class MyComponent extends React.Component {
state = {
count: 0,
loading: false
};
}
// Configuration determines which pattern is enforced:
{
"rules": {
"react/state-in-constructor": ["error", "always"], // Enforce constructor
// or
"react/state-in-constructor": ["error", "never"] // Enforce class property
}
}
// ✓ Good - Static properties at top (when configured)
class MyComponent extends React.Component {
static propTypes = {
name: PropTypes.string
};
static defaultProps = {
name: 'Anonymous'
};
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return <div>{this.props.name}</div>;
}
}Rules for lifecycle-equivalent patterns in functional components.
const functionalLifecycleRules = {
/** Enforce destructuring and symmetric naming of useState hook value and setter */
'react/hook-use-state': ESLintRule;
};Usage Examples:
// ✓ Good - Proper useState destructuring with symmetric naming
const MyComponent = () => {
const [count, setCount] = useState(0);
const [isVisible, setIsVisible] = useState(false);
const [userData, setUserData] = useState(null);
useEffect(() => {
// Effect logic
}, [count]);
return (
<div>
<span>Count: {count}</span>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
// ✗ Bad - Non-symmetric naming for useState
const MyComponent = () => {
const [count, updateCount] = useState(0); // Should be setCount
const [isVisible, changeVisibility] = useState(false); // Should be setIsVisible
return (
<div>
<span>Count: {count}</span>
<button onClick={() => updateCount(count + 1)}>Increment</button>
</div>
);
};Rules related to error handling in component lifecycle.
const errorHandlingRules = {
/** Prevent usage of isMounted */
'react/no-is-mounted': ESLintRule;
/** Prevent usage of the return value of React.render */
'react/no-render-return-value': ESLintRule;
};Usage Examples:
// ✓ Good - Error boundary with proper lifecycle methods
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong.</div>;
}
return this.props.children;
}
}
// ✓ Good - Avoiding isMounted anti-pattern
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { data: null };
this._isMounted = false;
}
componentDidMount() {
this._isMounted = true;
this.fetchData();
}
componentWillUnmount() {
this._isMounted = false;
}
async fetchData() {
const data = await api.getData();
if (this._isMounted) {
this.setState({ data });
}
}
}
// ✗ Bad - Using isMounted (deprecated and unreliable)
class MyComponent extends React.Component {
async fetchData() {
const data = await api.getData();
if (this.isMounted()) {
this.setState({ data });
}
}
}Lifecycle rules often support configuration for different coding styles:
{
"rules": {
"react/no-did-mount-set-state": ["error", "disallow-in-func"],
"react/no-did-update-set-state": ["error", "disallow-in-func"],
"react/state-in-constructor": ["error", "always"],
"react/static-property-placement": ["error", "property assignment"],
"react/hook-use-state": ["error", {
"allow": ["count", "setCount"] // Allow specific patterns
}],
"react/require-optimization": ["error", {
"allowDecorators": ["observer"] // Allow certain HOCs to skip optimization
}]
}
}