React Style Guides Are Usually Just Opinion with Enforcement
I spent about three years working through the chaos of inconsistent component patterns across multiple teams. The first style guide we wrote at that shop was 80 pages and nobody read past page 12. We burned through two engineers updating it every sprint because someone always deviated. The third version was fourteen pages with actual examples and a lint rule for every entry. That one stuck. A style guide is not a document you publish and hope people follow. It is a set of conventions backed by tooling that prevents violations before they reach code review. Without the enforcement layer, it is just a blog post that gets linked in Slack once and then forgotten.
Building a Style Guide For React Common Mistakes To Avoid
The most useful guides start by collecting the mistakes that actually slow down a team. I keep a running list in a public issue tracker where anyone can add an example. The format is simple: a broken snippet, why it is broken, and the corrected version. That list became the foundation of our style guide over six months of real bug reports and PR feedback. Here is how the process actually works. First, audit your last twenty pull requests. Look for patterns in what gets commented on. If three different people flagged the same anti-pattern, that belongs in the guide. If it happened once, it is an edge case and does not belong yet. Second, write the rule with a concrete before-and-after example. Third, add an ESLint rule or Prettier override that enforces it automatically. Fourth, put it in the PR template so reviewers reference the guide instead of writing the same comment repeatedly. The enforcement step is where most teams fail. A written rule without automation is a suggestion. React projects typically use eslint-plugin-react-hooks, eslint-config-airbnb, or a custom setup built around eslint-plugin-import and eslint-plugin-jsx-a11y. We also added custom rules for our component architecture, which took about two days of setup but saved roughly four hours per week in review time within the first month.
Component Structure Conventions
Component organization is the first place inconsistency shows up. I have seen the same component spread across four files with no clear reason for doing so. A simple convention removes that ambiguity. Each component should live in its own directory with an index.tsx that exports the public API. Types go into types.ts. Tests go into index.test.tsx. Styles, if needed, go into index.module.css or a styled-components file. Everything else stays inside the component file unless it is reused. This structure reduces file navigation time and makes tree shaking predictable. One specific mistake I ran into involved a data-fetching component that mixed its own API calls with presentation logic. The component was 340 lines and impossible to test in isolation. The fix was extracting a custom hook called useProjectData that handled the fetch, and leaving the component to accept props. That split reduced the component to about forty lines and made unit testing trivial. The hook itself had its own tests covering loading, error, and success states.
Get the Full Details

Prop Drilling and Context Misuse
React props are not a problem until you pass twenty of them through three layers of components. That is the signal to reach for a different pattern, and the style guide should make that decision explicit. The common mistake is treating context as a global store replacement. Context is designed for theme, locale, auth state, and similar cross-cutting concerns. It is not designed to hold every piece of application data. When you put a shopping cart or user preferences into context, you trigger unnecessary re-renders across the entire tree because context updates bubble to every consumer. I worked on a project where the context contained a large settings object. Every time a single nested field changed, the entire component tree re-rendered. The performance budget doubled. We replaced context with a Zustand store scoped to that data. The change cut re-renders in the affected sections by about seventy percent and made the state shape much easier to debug because the store had built-in middleware for logging and time-travel.
The style guide should define when to use context versus when to use a state management library. A practical rule I have used successfully is: if the data changes more than twice per second or if the consumers are fewer than three components, use a dedicated store. If the data is static or changes rarely and needs to be accessed by many components, context is acceptable.
Effect and Lifecycle Mistakes
useEffect is the most misused hook in React. The average developer treats it as componentDidMount, componentDidUpdate, and componentWillUnmount combined. It is none of those things. It runs after render whenever its dependency array changes, and cleanup runs before the next effect and on unmount. A common pattern I see constantly is missing dependencies in the array. ESLint has the exhaustive-deps rule for this, but teams often disable it. Disabling it without replacing it with something else is how memory leaks happen. Event listeners attached to window, setInterval calls, and subscriptions to external stores all leak if you do not clean them up. Another mistake is putting async logic directly in the effect callback. useEffect callbacks cannot be async because that returns a Promise, which React ignores. The correct pattern is to define an async function inside the effect and call it immediately. Here is what that looks like:

useEffect(() => { async function fetchData() { const result = await api.get('/data'); setData(result); } fetchData(); }, [dependency]); I ran into a case where a search input triggered an API call on every keystroke without debouncing. The server received about sixty requests in two seconds. We added a debounce hook with a 300 millisecond delay, reduced server load to one request per search session, and cut our API costs by roughly forty percent that month.
State Management Without a Map
When forms get complicated, developers often create a separate state variable for every input field. This creates a maintenance burden that grows linearly with the number of fields. A single form state object with typed keys is easier to manage and easier to validate. I worked on a multi-step checkout flow that had twenty-two individual useState calls. Adding validation required updating separate pieces of logic. We consolidated it into a single state object with a type definition and a reducer-style updater function. The form logic dropped from about three hundred lines to roughly one hundred and twenty. Validation became a single pass over the state object instead of independent checks.
List Rendering and Key Mistakes
Using array index as a key is a mistake that shows up in nearly every beginner codebase and surprisingly often in production code too. Index keys work fine for static lists that never reorder, delete, or insert items. Most lists are not static. When a list item is deleted and the remaining items shift, React uses the index to track which element is which. If the keyed item changes position, React may preserve the wrong component state, causing visual glitches or lost input. The fix is to use a stable unique identifier from the data itself. If the data does not have one, generate a UUID at the point where the data enters the system, not in the rendering layer. I encountered a bug where a chat message list used index keys. When a message was deleted, the input field in the next message unexpectedly retained the previous message's draft text. The fix was replacing the index key with message.id, which eliminated the state drift entirely. This kind of bug is extremely difficult to reproduce in testing because it depends on the exact sequence of renders and DOM mutations.

Conditional Rendering Anti-Patterns
Conditional rendering in React has several traps. Using boolean values directly in JSX is fine for simple cases, but mixing equality checks with logical operators creates subtle bugs. The expression {count && {count} items} will render the number zero when count is zero, because zero is falsy but still gets printed. A more reliable pattern is to use explicit comparison or a ternary operator. {count > 0 ? {count} items : null} handles the zero case correctly. This seems minor, but I have spent hours debugging why a badge component showed the number 0 instead of hiding when there were no notifications. Another conditional rendering mistake is importing heavy components conditionally without lazy loading. If a modal or a chart library is imported at the top level even though it only renders under certain conditions, it loads on every page visit. React.lazy and Suspense handle this, but the guide should specify when to apply it. A practical threshold is any component over five hundred lines or any library import that adds more than fifty kilobytes to the bundle.
Ref Management and DOM Access
Refs are useful for focusing inputs, measuring elements, and integrating with third-party libraries. They are not useful for triggering re-renders. Using a ref to store a value and then calling forceUpdate or setState to reflect that change is an anti-pattern that creates confusion about where the source of truth lives. I once debugged a component where the ref held the current form value, but the state held a stale copy. The two drifted apart whenever the component re-rendered for unrelated reasons. The form appeared to submit the wrong data because the API call read from state instead of the ref. The fix was removing the redundant state and reading from the ref at submission time. This also eliminated a whole class of race conditions that occurred during rapid input.
Performance Mistakes That Are Easy to Miss
React re-renders are not expensive by default. The expensive part is unnecessary re-renders of components that do not need to update. useMemo and useCallback are often applied prematurely, which adds complexity without measurable benefit. The rule I use is straightforward: optimize only after profiling. React DevTools Profiler shows exactly which components re-render and why. Without that data, adding memoization is guesswork. In one project, a developer added useMemo to every computed value and useCallback to every event handler. The bundle grew by twelve percent and the app felt slower because React spent more time managing memoization cache than doing actual rendering. Removing most of the memoization restored the original performance. A more reliable optimization is to lift state only when necessary. State localization means keeping state as close to the component that needs it as possible. When state lives too high in the tree, every update causes every sibling component to re-render even if they do not use that data. Splitting the state into smaller, local pieces usually improves performance more than any memoization technique.

Testing Conventions
A style guide should include testing conventions because inconsistent testing practices create fragile codebases. The most important rule is that tests should verify behavior, not implementation. Testing that a component calls a specific function with specific arguments is brittle. Testing that the component displays the correct output given a set of props is stable. We use Testing Library with React. The convention is to write tests that query by role, label, or test ID, never by DOM structure. This means adding aria-labels and semantic HTML from the start, which improves accessibility as a side effect. I have found that teams following this convention spend roughly half the time on test maintenance compared to teams that query by class name or data-testid on container divs. Snapshot testing is acceptable for static components but should not replace meaningful assertions. A snapshot that passes does not prove the component works correctly. It only proves the output has not changed, which is a weaker guarantee. The style guide should specify that snapshots supplement, not replace, behavioral tests.
File Organization Rules
File organization varies between teams, but the guide should enforce consistency even if the convention is arbitrary. A project where some components live in /components, others in /ui, and a few in /modules creates navigation overhead that compounds over time. The convention I recommend is feature-based folder structure. Each feature gets its own folder containing the component, its types, its tests, and its styles. Shared primitives go into a shared directory. This structure scales better than organizing by file type because related code stays together during refactors. When a feature is deprecated, the entire folder can be removed without hunting across multiple directories.
Documentation Standards
Components should have JSDoc comments for exported functions and types. This enables IDE auto-completion and makes the public API visible without opening the file. The comments should describe what the component does, what each prop means, and any required wrapper context like a ThemeProvider. I have seen teams skip this entirely, which creates a maintenance tax. Junior developers spend time reverse-engineering how a component should be used, and senior developers spend time answering the same questions repeatedly. Three minutes of JSDoc per component saves hours of confusion over a quarter. The style guide should also specify a standard story format for Storybook or similar documentation tools. Stories should cover the default state, each variant, and at least one edge case. Empty stories for components that are not yet complete are better than missing stories because they make the documentation scope visible.
What This Guide Cannot Fix
A style guide cannot compensate for poor architectural decisions. If the application is fundamentally over-fetching data, no amount of component convention will fix the performance. If the team does not have CI integration for linting and formatting, the guide will be ignored regardless of how well written it is. If the project lacks code review discipline, violations will accumulate until the codebase becomes unrecognizable. The guide works best when it is treated as a living document maintained by the team, not as a policy imposed from above. The most effective version I have used was updated once per sprint based on what reviewers actually flagged. The outdated version gathered dust on the wiki. Both contained good advice. Only one changed how people wrote code. If you are starting a new project, write the guide before you write the first component. If you are maintaining an existing project, start with the ten most repeated feedback items from your last month of PRs and build from there. A twelve-page guide with enforced rules is worth more than a hundred pages of recommendations.