Skip to content

[rule] add static-components rule #1665

@Rel1cx

Description

@Rel1cx

Problem Description

This rule validates that components are static, not recreated every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering.

Components defined inside other components are recreated on every render. React sees each as a brand new component type, unmounting the old one and mounting the new one, destroying all state and DOM nodes in the process.

This is a common pitfall for React developers, especially beginners, who might define components inside other components to access local variables, not realizing the performance and state loss implications.

Alternative Solutions

Rule Documentation Template

Rule Details

This rule enforces that React components are defined statically, outside of other components. When a component is defined inside another component, it gets recreated on every render. React treats each recreation as a brand new component type, causing the old component to unmount and the new one to mount, which destroys all state and DOM nodes.

This pattern is problematic because:

  • State is lost on every parent re-render
  • Performance is degraded due to unnecessary unmounting/remounting
  • It can cause subtle bugs that are hard to track down

If you find yourself wanting to define components inside other components to access local variables, that's a sign you should be passing props instead. This makes components more reusable and testable.

Common Violations

Invalid

// ❌ Bad: Component defined inside another component causes state reset on every render
function Parent() {
  const [count, setCount] = useState(0);
  
  // This component is recreated on every Parent render
  function Child() {
    return <div>Child content</div>;
  }
  
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <Child />
    </div>
  );
}
// ❌ Bad: Arrow function component defined inside render
function Parent() {
  const localData = fetchData();
  
  const ChildComponent = () => {
    return <div>{localData}</div>;
  };
  
  return <ChildComponent />;
}

Valid

// ✅ Good: Component defined outside, receives data via props
function Child({ data }) {
  return <div>{data}</div>;
}

function Parent() {
  const [count, setCount] = useState(0);
  const localData = fetchData();
  
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <Child data={localData} />
    </div>
  );
}
// ✅ Good: Using hooks to share logic instead of nested components
function useLocalData() {
  return fetchData();
}

function Child() {
  const data = useLocalData();
  return <div>{data}</div>;
}

function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <Child />
    </div>
  );
}

Troubleshooting

Why is this pattern problematic?

When you define a component inside another component, JavaScript creates a new function on every render. React uses the function reference to identify component types. Since the function reference changes on every render, React thinks it's a completely different component type and performs a full unmount/remount cycle, destroying all internal state and DOM.

What are the alternatives?

  1. Pass data via props: Move the child component outside the parent and pass the needed data as props.
  2. Use custom hooks: If you need to share logic, extract it into a custom hook instead of a nested component.
  3. Component composition: Structure your components so that data flows down through props rather than being captured in closures.

Resources

  • Rule Source: (to be added after implementation)

Further Reading

Evaluation Checklist

  • I have had problems with the pattern I want to forbid
  • I could not find a way to solve the problem by changing the API of the problematic code or introducing a new API
  • I have thought very hard about what the corner cases could be and what kinds of patterns this would forbid that are actually okay, and they are acceptable
  • I think the rule explains well enough how to solve the issue to make sure beginners are not blocked by it
  • I have discussed this rule with team members, and they all find it valuable

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions