Welcome, fellow React developers.
You thought React was easy until you opened a file and saw:
useEffect(() => {
setState(state + 1);
}, [state]);
You realize it's re-rendering forever. Your browser crashes. Your laptop fans scream. You scream.
This article is a tour of the 9 deadly sins of React — complete with real pain, bad decisions, and some trauma bonding.
1. 🧟♂️ Thou Shalt Not Use Class Components (Unless You're Archaeologist)
Back in 2017, class components were React’s hot new thing.
Today, they’re the rotary phones of development.
I once joined a project where the main component looked like this:
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {},
metrics: [],
loading: true,
};
}
async componentDidMount() {
const data = await fetchDashboardData();
this.setState({ ...data, loading: false });
}
shouldComponentUpdate(nextProps, nextState) {
return JSON.stringify(this.state) !== JSON.stringify(nextState);
}
// and 1000 more lines...
}
By the time I finished reading it, my eyes needed pagination.
☠️ Why it hurts:
-
this
binding is a nightmare - Logic reuse? Good luck!
- Too many lifecycle methods
✅ Use function
components + hooks
2. 💥 The “12 useState
s in One File” Crime Scene
Here’s something I actually saw in production:
const [step, setStep] = useState(0);
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [formTouched, setFormTouched] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [serverError, setServerError] = useState(null);
const [redirect, setRedirect] = useState(false);
This component had:
- Business logic
- UI logic
- Form validation
- API handling
- Toasts
- Animations
...all jammed into one monstrous 500-line file.
☠️ Why it hurts:
- Impossible to read or debug
- No separation of concerns
✅ Use useReducer
or custom hooks like useLoginForm()
3. 🤯 Using useEffect
for Everything Because It “Just Works”
useEffect(() => {
const data = expensiveCompute(props.items);
setState(data);
}, [props.items]);
Why not use useMemo
, you ask?
Because this dev believed useEffect
is React’s if-else
for life.
☠️ Why it hurts:
- Side effects happen on re-render → trigger state updates → re-render again
✅ Use useMemo
for computed values and useEffect
for async stuff
4. 🔁 Hook Dependency Hell (a.k.a. “The Loop of Doom”)
const [count, setCount] = useState(0);
useEffect(() => {
setCount(prev => prev + 1);
}, [count]); // INFINITE LOOP TIME 🚨
Bonus points if the dev added a console log and then:
console.log("count:", count); // console never stops
☠️ Why it hurts:
- You crash the tab
- You question your career choices
✅ Use guards or useRef
to break circular updates
5. 🌍 Using Global State Like It’s 1995
Every UI interaction hits context. Close a dropdown? Whole page re-renders.
const { isModalOpen, setIsModalOpen } = useContext(GlobalContext);
☠️ Why it hurts:
- Global context = global performance hit
- Trivial things become untestable
✅ Keep state local unless it truly must be shared
6. ⚶️ Redux Isn’t Dead — But You’re Using It Wrong
Still doing this?
dispatch({ type: 'SET_AUTH', payload: true });
And you have:
/actions/user.js
/reducers/userReducer.js
/types/userActionTypes.js
That’s classic Redux. It's like writing essays for text messages.
☠️ Why it hurts:
- Boilerplate ☑
- Fragile action chains ☑
- Slow onboarding ☑
✅ Use:
- Redux Toolkit (RTK)
- RTK Query for async state
- Zustand, Jotai, React Query as simpler alternatives
const authSlice = createSlice({
name: 'auth',
initialState: { loggedIn: false },
reducers: {
login: (state) => { state.loggedIn = true },
logout: (state) => { state.loggedIn = false },
},
});
💡 Redux isn’t dead. But your 2017 version of Redux should be.
7. 🏠 Real Project Structure, Not Folder Soup
Bad:
src/
App.jsx
helpers.js
FinalFix.jsx
testComp3.jsx
utils2.js
Opening that repo feels like entering a garage with no shelves.
☠️ Why it hurts:
- You can't find anything
- No domain boundaries
✅ Do this instead:
/src
/shared
/ui # Reusable UI (buttons, inputs)
/hooks # Custom hooks
/lib # Utilities
/types # Global types/interfaces
/features
/auth
/components
/hooks
/auth.slice.ts
/dashboard
/components
/hooks
/pages
/dashboard
/login
/entities
/user
/product
Think "group by feature", not by file type.
8. 🔤 Naming Components Like a Drunken Goblin
Real names I’ve seen:
ThingWrapper.jsx
HandlerStuff.js
FinalFinalForm2.jsx
useMagicState.js
YoloModal.jsx
NoIdea.jsx
☠️ Why it hurts:
- Impossible to understand
- Refactoring becomes a horror movie
✅ Use clear, purpose-driven names:
- Components:
UserCard
,ProductList
,LoginForm
- Hooks:
useUser
,useFormValidation
- Utils:
getDateRange
,debounce
😂 If your filename has "Final" and a number in it, stop. Reboot your brain.
9. 🧼 God Components That Do Everything
One file:
- Fetches data
- Handles auth
- Renders modals
- Shows toasts
- Writes to Firestore
- Animates a wizard hat spinning
☠️ Why it hurts:
- Not testable
- Not reusable
- Not readable
✅ Break it down:
useUserData
UserForm
ToastManager
UserModal
Split UI, logic, and data.
⚠️ Bonus: Don’t Be Stubborn — Be Smart
React evolves. So should you.
✅ Read the docs
✅ Learn new APIs
✅ Avoid outdated patterns
Your future self will thank you.
🧠 Wrap-Up: React Is Simple — Until It Isn’t
React's flexibility is a gift and a curse.
🔁 TL;DR
- No more class components
- Don’t abuse
useState
-
useEffect
isn't your calculator - Respect hook deps
- Global state ≠ default
- Use Redux Toolkit or better
- Structure your app
- Name things clearly
- Break up god components
What React sins have you seen? Share your horror stories and memes 👇
Top comments (0)