I've recently undergone analyzing and improving a React application's performance and have noticed some pretty serious code smells that affect both code quality and performance.
Here are some code smells that I watch out for in a React application:
A massive component (500+ LOC) - I hesitate to write a specific number of LOC because there are always exceptions but 500 begins to become TOO large. Components should be small and practice Individual Responsibility.
Business logic and UI are intertwined - components should only contain the UI and not have complex business rules intertwined in the component. I suggest abstracting away business rules either into a helper function or into a custom hook.
No tests for business logic - each set of business logic rules should be tested to document the expected outcome. Tests add confidence to the software that other programmers can use to be certain that they didn't break something pre-existing. It is extremely helpful to have tests when you need to refactor to get immediate feedback through the testing framework.
Massive amounts of props are being passed - if there are a lot of props being passed through the component I check two things:
Can we compose this component better with composition (see smell 5)
If we're prop drilling certain props, like a user object or some international translation function that doesn't change that often, let's put it into Context.
Lack of use of composition - If a codebase or a section of code isn't taking advantage of composition it points to a lack of understanding of React. Using composition results in fewer unnecessary rerenders and less prop drilling.
Two fantastic resources if you're unfamiliar with composition: an article by Kent C. Dodds https://epicreact.dev/one-react-mistake-thats-slowing-you-down/ and a video by Michael Jackson (no not that one) https://www.youtube.com/watch?v=3XaXKiXtNjw&t=592s
Incorrect usage of hooks - the most commonly misused I've seen are useEffect - which should not be used to adjust state or props when a certain value changes, or replicate what componentDidMount/ other lifecycle methods did. Instead use it to sync an external system to React (network, DOM, etc.) These days with how awesome React-Query/SWR are you don't even need it.
Also, stop putting useCallbacks everywhere - premature optimization is the root of all evil.