React is amazing, but it comes with its own set of quirks that can catch you off guard. Whether you're a beginner or an experienced developer, these surprising behaviors are a reminder of React's complexity and flexibility. Let’s dive in! 🚀
1. State Updates Are Asynchronous🕒
React batches state updates for better performance, which means changes to state aren’t immediate.
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // Still logs 0
};
Why? React doesn’t update the state until the end of the event loop. Use the functional form of setState to get the latest value:
setCount(prevCount => prevCount + 1);
2. Keys Aren't Just For Performance 🗝️
Keys help React identify elements during reconciliation. But using unstable keys, like array indices, can break your UI.
const items = ['React', 'JS', 'CSS'];
items.map((item, index) => <div key={index}>{item}</div>);
Problem: If the array changes (e.g., sorting), React might lose track of elements. Always use unique and stable keys, like IDs.
3. useEffect Runs Twice in Strict Mode 🌀
Strict Mode in development deliberately calls useEffect twice to detect bugs.
useEffect(() => {
console.log('Effect runs!');
}, []);
Gotcha: This happens only in development. In production, useEffect runs once as expected.
4. Props Are Read-Only 🚫
Props are immutable in React, and modifying them directly can cause errors.
const MyComponent = ({ name }) => {
name = 'New Name'; // ❌ Illegal
return <h1>{name}</h1>;
};
Solution: Use state if you need mutable data.
5. Re-renders Happen Even If Props Don't Change 🔁
React re-renders components even if props remain the same.
const Child = ({ value }) => {
console.log('Child renders');
return <div>{value}</div>;
};
Why? React doesn’t automatically optimize performance. Use React.memo to prevent unnecessary re-renders:
export default React.memo(Child);
6. Conditional Rendering Can Break Lists 💥
Rendering lists conditionally can lead to unpredictable outcomes.
{items.length && items.map(item => <div>{item}</div>)}
Gotcha: If items.length is 0, React renders 0 before the list. Use explicit checks:
{items.length > 0 && items.map(item => <div>{item}</div>)}
7. JSX Is Not HTML 🖋️
JSX looks like HTML but isn’t! For example, the class attribute is className, and self-closing tags must end with a /.
<div className="my-class" />;
8. Infinite Loops With useEffect 🔄
Forgetting to add dependencies in useEffect can create infinite loops.
useEffect(() => {
fetchData();
}, [data]); // Missing `data` can cause repeated calls
Solution: Use the dependency array carefully or useRef for stable references.
9. Fragments Don’t Accept Keys By Default 🔑
When using React.Fragment to wrap multiple elements, you might need to add a key, especially in lists.
<>
<div key="1">Item 1</div>
<div key="2">Item 2</div>
</>
Solution: Use instead if required.
10. Updating State Doesn’t Merge Objects ✂️
Unlike this.setState in class components, useState doesn’t merge object state automatically.
const [state, setState] = useState({ name: 'React', version: 18 });
setState({ version: 19 });
// Name is lost because the state isn’t merged!
Solution: Spread the previous state:
setState(prevState => ({ ...prevState, version: 19 }));
Conclusion: React Is Full of Surprises! 🎉
Understanding these quirks will make you a better React developer. What are some quirks you’ve encountered? Drop your answers in the comments below! Let’s discuss! 😎
Top comments (0)