Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.
React Conventions
Component and hook conventions for React apps: composition over inheritance, honest dependency arrays, state colocation, derived state, controlled forms, key discipline, and measured memoization.
Mby @markdownersPublished August 21, 2026 · ~4 min read
0 downloads · Used by 0 stacks
Prefer composing small components over building inheritance-like abstractions, and treat every piece of state as something that must justify why it exists where it lives — most React bugs come from state that's stale, duplicated, or living one level too high or too low.
Component composition
- Build UI by composing small, focused components (children/render props/slots) rather than parameterizing one large component with booleans and conditionals — a component with five boolean props hiding six different layouts is harder to reason about than five small components.
- Never reach for a class hierarchy or a shared base component to share behavior — extract a hook or a wrapper component instead; React's composition model doesn't need inheritance.
- Keep components pure with respect to props/state during render — no mutating props, no side effects in the render body itself; side effects belong in event handlers or effects.
Hooks: honest dependency arrays
- Include every value the effect/callback/memo actually reads in its dependency array — never suppress the linter rule to silence a warning; a missing dependency is a stale-closure bug waiting to happen, not a lint annoyance.
- If a dependency array must exclude a value on purpose, restructure the code (move the value inside the effect, use a ref for a value you intentionally don't want to react to) rather than lying to the array.
- Prefer the functional form of a state setter (
setCount(c => c + 1)) when the new value depends on the previous one, so the update doesn't need to be a dependency at all.
Custom hooks for shared logic
- Extract shared stateful logic (data fetching, subscriptions, form state) into a custom hook the moment it's needed in a second component — don't wait for a third duplication, and don't copy-paste effect logic between components.
- Name custom hooks for what they provide (
useDebouncedValue,useMediaQuery), not for the component that first needed them, so the hook reads as reusable infrastructure.
State colocation and derived state
- Put state in the lowest component that needs it; lift it only as far as the nearest common ancestor of the components that actually use it — state living in a top-level provider "just in case" causes unnecessary re-renders across the tree.
- Never store a value in state if it can be computed from existing props/state during render (a filtered list, a formatted string, a total) — compute it inline or with
useMemoif the computation is expensive; storing it invites the two copies to drift out of sync. - Reset state by changing a component's
key(remounting) instead of writing an effect that manually resets fields when a prop like an item ID changes.
Controlled forms
- Prefer controlled inputs (value + onChange bound to state) for any field the app validates, formats, or submits programmatically; use uncontrolled inputs with refs only for simple, single-shot cases (a file input, a form library already handling state).
- Validate on submit and on blur, not on every keystroke, unless the UI explicitly needs live feedback — keystroke-level validation that also blocks typing is a common source of frustrating input bugs.
List rendering and key discipline
- Use a stable, unique identifier from the data (a database ID) as the
keyfor list items — never the array index, except for a genuinely static list that never reorders, filters, or has items inserted/removed. - An index key on a dynamic list causes state to attach to the wrong item after a reorder or deletion (wrong input focus, wrong text mid-edit) — treat this as a correctness bug, not a lint nitpick.
Memoization only when measured
- Do not wrap every component in
memoor every value inuseMemo/useCallbackby default — memoization has its own cost (comparison, memory) and adds noise; reach for it after profiling shows a specific re-render is expensive and frequent. - When memoizing a callback passed to a memoized child, memoize the callback too (
useCallback) — otherwise a new function identity on every render defeats the child'smemoentirely. - Prefer moving state down (colocation) or splitting a component to naturally reduce re-render scope before reaching for a memoization API to paper over unnecessary re-renders.
Badge
Link back to this module from your own README.
[](https://markdowners.com/m/markdowners/react-conventions)Discussions about this module
No discussions about this module yet.
Start a discussion
Comments (0)
Sign in to comment. Sign in
No comments yet. Be the first to add one.