Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Accelerate quality software

The nine best recommendations in the new React docs

Learn the top React code style recommendations from the latest React docs to write cleaner, more maintainable code. Perfect for improving team discussions.
Josh Justice
|
October 15, 2023
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Usually when a new software development team forms there is some negotiating how exactly the team wants to write their code. When the maintainers of a programming language or major framework are opinionated on these things, this can help provide a default starting point for these discussions.

React is known for not being very opinionated in some ways, but the latest React docs actually make quite a few recommendations about how you write your React code! Since these docs were first released in beta form, other agents and I have been referring to them more and more as a basis for conversations about code style we have on our teams.

In this post I'll highlight the recommendations from the React docs that come up most frequently in my team discussions about React code style.

For each I'll share a brief summary and usually a code snippet. Much more information and rationale can be found by following the included link to the relevant portion of the React docs. When there is an ESLint rule that can enforce the recommendation, I've linked to that rule as well—thank you to Lorenzo Sciandra for the idea!

Some of these recommendations might feel like code style opinions with no real consequences. However, as the React docs explain, in each case straying from the recommendation comes with costs or risks. You're free to make your own decision, but keep in mind that the React core team has thought through these things a lot more than you or I have. This doesn’t mean they’re always right, but it's a good idea to at least hear them out.

  1. When choosing a key for an element in a loop, use an identifier that will always be the same for the same entry—not an array index
  2. When defining a component, define it at the top level of the file/module, not nested inside another component or function
  3. When deciding what to store in state, store the minimal representation that can be used to compute what you need
  4. When considering whether to cache with useMemo, useCallback, or React.memo, defer caching until there is an observed performance problem
  5. When extracting shared code into a function, only name it as a hook if it calls other hooks
  6. When you need to adjust state in response to a prop change, set the state directly in the component function (during rendering), not in an effect
  7. When you need to fetch data, prefer using a library over useEffect
  8. When you need to take an action in response to an event occurring, write the code in an event handler, not in a useEffect
  9. When a useEffect dependency is causing rerenders you don't want (including infinite loops), don't just remove the dependency from the array: remove the dependency from the effect function too

1. When choosing a key for an element in a loop, use an identifier that will always be the same for the same entry—not an array index

React uses the key to keep track of list elements across renders. If an element is added, deleted, or reordered then index keys will mislead React, which can lead to bugs.

  • React docs on keys
  • ESLint rule: react/no-array-index-key
// 🛑 WRONG
return (
  <ul>
    {items.map((item, index) => (
      <li key={index}>…</li>
  ))}
  </ul>
);

// 🟢 RIGHT, assuming item.id is a stable unique identifier
return (
  <ul>
    {items.map((item, index) => (
      <li key={item.id}>…</li>
  ))}
  </ul>
);

2. When defining a component, define it at the top level of the file/module, not nested inside another component or function

Sometimes it can seem convenient to define a component inside another component. But this will result in the component being treated as different on every render, leading to slow performance.

  • React docs on component nesting
  • ESLint rule: react/no-unstable-nested-components
// 🛑 WRONG
function ParentComponent() {
  // ...
  function ChildComponent() {…}

  return <div><ChildComponent /></div>;
}

// 🟢 RIGHT
function ChildComponent() {…}

function ParentComponent() {
  return <div><ChildComponent /></div>;
}

3. When deciding what to store in state, store the minimal representation that can be used to compute what you need

This makes state easy to update without introducing bugs, because it precludes different state items falling out of date with one another or otherwise becoming inconsistent.

React docs on structuring state‍

// 🛑 WRONG
const [allItems, setAllItems] = useState([]);
const [urgentItems, setUrgentItems] = useState([]);

function handleSomeEvent(newItems) {
  setAllItems(newItems);
  setUrgentItems(newItems.filter(item => item.priority === 'urgent'));
}

// 🟢 RIGHT
const [allItems, setAllItems] = useState([]);
const urgentItems = allItems.filter(item => item.priority === 'urgent');

function handleSomeEvent(newItems) {
  setAllItems(newItems);
}

4. When considering whether to cache with useMemo, useCallback, or React.memo, defer caching until there is an observed performance problem.

Although there is not a major downside to always memoizing, the minor downside is that it makes the code less readable.

Read more in the React docs on useMemo, React docs on useCallback, or React docs on React.memo‍

// 🛑 WRONG
const [allItems, setAllItems] = useState([]);
const urgentItems = useMemo(() => (
  allItems.filter(item => item.status === 'urgent'
), [allItems]);

// 🟢 RIGHT (until an observed performance problem)
const [allItems, setAllItems] = useState([]);
const urgentItems = allItems.filter(item => item.priority === 'urgent');

5. When extracting shared code into a function, only name it as a hook if it calls other hooks

If your function calls other hooks, it needs to be a hook itself, so that React can enforce the restrictions on hooks that allow them to work. If your function does not call other hooks, then there’s no reason to opt in to those restrictions. Your function will be more versatile as a non-hook because it can be called from anywhere, including within conditionals.

React docs on the "use" prefix

// 🛑 WRONG
function useDateColumnConfig() { // will be subject to hooks restrictions
  return {
    dataType: 'date',
    formatter: prettyFormatDate,
    editorComponent: DateEditor,
  };
}

// 🟢 RIGHT
function getDateColumnConfig() { // can be called anywhere
  return {
    dataType: 'date',
    formatter: prettyFormatDate,
    editorComponent: DateEditor,
  };
}

function useNameColumnConfig() { // has to be a hook since it calls a hook: useTranslation
  const { t } = useTranslation();
  return {
    dataType: 'string',
    title: t('columns.name'),
  };
}

6. When you need to adjust state in response to a prop change, set the state directly in the component function (during rendering), not in an effect

If you're planning to adjust state in response to a prop change, it's a good idea to first confirm you really need to. It's preferred if you can instead derive the data during rendering (see recommendation 3, above) or use a key to reset all of the state.

If you do need to adjust part of the state, it's helpful to consider a key point the React docs make about effects: they “are an escape hatch from the React paradigm. They let you ‘step outside’ of React and synchronize your components with some external system…” That complexity is not needed when you just need to do a quick state update in response to a prop change.

React docs on state changes from props

Thanks to the React docs for the example code this (slightly simplified) snippet is based on!‍

// 🛑 WRONG
function List({ items }) {
  const [selection, setSelection] = useState(null);

  useEffect(() => {
    setSelection(null);
  }, [items]);
  //...
}

// 🟢 RIGHT
function List({ items }) {
  const [prevItems, setPrevItems] = useState(items);
  const [selection, setSelection] = useState(null);

  if (items !== prevItems) {
    setPrevItems(items);
    setSelection(null);
  }
  //...
}

7. When you need to fetch data, prefer using a library over useEffect

Subtle bugs can occur with useEffect data fetching unless you write a lot of boilerplate to handle them. The React docs provide a number of suggestions for good data fetching libraries.

React docs on data fetching

// 🛑 WRONG
const [items, setItems] = useState();
useEffect(() => {
  api.loadItems().then(newItems => setItems(newItems));
}, []);

// 🟢 RIGHT (one library option)
import {useQuery} from '@tanstack/react-query';

const { data: items } = useQuery(['items'], () => api.loadItems());

8. When you need to take an action in response to an event occurring, write the code in an event handler, not in a useEffect

This ensures the code will run only once per event occurrence.

Read the React docs on effects vs. events, or watch a conference talk on effects vs. events.

const [savedData, setSavedData] = useState(null);
const [validationErrors, setValidationErrors] = useState(null);

// 🛑 WRONG
useEffect(() => {
  if (savedData) {
    setValidationErrors(null);
  }
}, [savedData]);

function saveData() {
  const response = await api.save(data);
  setSavedData(response.data);
}

// 🟢 RIGHT
async function saveData() {
  const response = await api.save(data);
  setSavedData(response.data);
  setValidationErrors(null);
}

9. When a useEffect dependency is causing rerenders you don't want (including infinite loops), don't just remove the dependency from the array: remove the dependency from the effect function too

It can be hard to understand why making this change is worth the effort; to do so, I recommend reading the pages that the React docs devote to useEffect. In brief, using dependencies that you don't list in the dependency array probably means that the effect is being used for something other than what effects are intended for: synchronizing. This is likely to lead to difficult-to-diagnose bugs sooner or later.

  • React docs on removing effect dependencies
  • ESLint rule: react-hooks/exhaustive-deps

Putting it into practice

I hope these points from the React docs help you learn a new technique, understand a technique more deeply, or explain a technique to others.

Do you follow these recommendations on your React projects? Are there other recommendations from the React docs that come up frequently for you?

Related Insights

🔗
The looming demise of the 10x developer
🔗
Adding deep links to a React Native app: A guide
🔗
Finding the right React component in the MUI design system
🔗
A guide to building a custom ESLint rule for React Testing Library
🔗
React Context for dependency injection not state management

Explore our insights

See all insights
Leadership
Leadership
Leadership
Why we coach the system, not just the team

Slow delivery isn’t usually about your people—it’s about your system. Shifting focus to incremental improvements in the system helps change not just processes but behaviors for lasting change.

by
Doc Norton
Developers
Developers
Developers
Developer QA checklist for feature releases

Quality Assurance is a mindset integrated throughout development to catch issues early, build user trust, and reduce maintenance costs. These recommended procedures for dev teams without dedicated QA roles establish collective responsibility for ensuring feature stability, functionality, and usability before release.

by
Lee Quarella
Developers
Developers
Developers
From engineer to consultant: The powerful shift from inward to outward focus

What transforms a skilled software engineer into an exceptional consultant? Approach new codebases with respect rather than judgment, embrace constraints as creative boundaries, and prioritize client needs over personal preferences.

by
Dave Mosher
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
No items found.
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.