React is a very powerful library for creating UI. With this power, you are responsible for crafting readable and bug-free code, which means that you have to keep up with best practices and patterns. The easiest way to become a better developer is to learn from your mistakes, or even other people's.
In this article, based on my actual work experience, you'll learn how to avoid 7 common mistakes that many developers make.
- Calling a function that takes no arguments in an event handler.
- Using
useEffect
to transform rendering data. - Resetting the state of a component using
useEffect
. - Not using
key
in lists. - Modify the state value directly by assignment.
- Not using the previous state when calling setState.
- Forgetting to clean up side effects in
useEffect
.
1. Calling a function that takes no arguments in an event handler
❌ The wrong way
export function App() {
const [clapped, setClapped] = useState(false);
const clap = () => setClapped(true);
return (
<div>
<p>Support this author: {clapped ? 'yes' : 'no'}</p>
<button onClick={(ev: MouseEvent) => clap()}>Clap</button>
</div>
);
}
✅ The right way
export function App() {
const [clapped, setClapped] = useState(false);
const clap = () => setClapped(true);
return (
<div>
<p>Support this author: {clapped ? 'yes' : 'no'}</p>
<button onClick={clap}>Clap</button>
</div>
);
}
🎈 Explanation
We can simply provide the function, without calling it. This works because React will take that function and call it internally with the event object as an argument. Our function takes no argument, so the event object will be discarded.
2. Using useEffect to transform rendering data
❌ The wrong way
function Form() {
const [firstName, setFirstName] = useState('Miroiu');
const [lastName, setLastName] = useState('Dev');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}
✅ The right way
function Form() {
const [firstName, setFirstName] = useState('Miroiu');
const [lastName, setLastName] = useState('Dev');
const fullName = firstName + ' ' + lastName;
// ...
}
🎈 Explanation
Using unnecessary effects can lead to bugs or even infinite loops in your application. You can create new variables based on the state or props of your component that will change on render, without using an effect.
3. Resetting the state of a component using useEffect
❌ The wrong way
export default function Form({ formId }) {
const [name, setName] = useState('');
useEffect(() => {
setName('');
}, [formId]);
// ...
}
✅ The right way
export default function Page({ formId }) {
return (
<Form
formId={formId}
key={formId}
/>
);
}
🎈 Explanation
Keys identify and track items. This is important for React's virtual DOM reconciliation process, which efficiently updates the user interface by minimizing unnecessary re-renders and improving performance.
When we formId changes, our key changes as well. React sees this and thinks the Form component is a new component, so it mounts it again and resets the state.
4. Not using key in lists
❌ The wrong way
export default function UserList({ users }) {
return (
{users.map(user => <User {...user}/>)}
);
}
✅ The right way
export default function UserList({ users }) {
return (
{users.map(user => <User key={user.id} {...user}/>)}
);
}
🎈 Explanation
This wouldn't be a problem if users never changes, React will resort to using the element's index as a default key
. If we perform operations such as add or remove, then we really need to provide a key
. React will know which elements have been added, removed, or changed when the list is re-rendered. This results in better performance, as React can minimize the number of DOM manipulations needed. Using the key
prop will prevent many bugs.
5. Modify the state value directly by assignment
❌ The wrong way
export default function User() {
const [name, setName] = useState('');
const handleOnChange = (ev: ChangeEvent<HTMLInputElement>) => {
name = ev.target.value
}
return (
<input onChange = {handleOnChange} />
);
}
✅ The right way
export default function User() {
const [name, setName] = useState('');
const handleOnChange = (ev: ChangeEvent<HTMLInputElement>) => {
setName(ev.target.value);
}
return (
<input onChange = {handleOnChange} />
);
}
🎈 Explanation
Updating the state directly will not trigger a re-render. React may not recognize the change when the state is modified directly, and it can result in bugs and issues. To ensure the proper behavior and reactivity of your React components, you should always use the provided state update mechanisms.
6. Not using the previous state when calling setState
❌ The wrong way
export default function UserList() {
const [users, setUsers] = useState<User[]>([]);
const addUser = (user: User) =>
users.push(user);
setUser(users);
}
//...
}
✅ The right way
export default function UserList() {
const [users, setUsers] = useState<User[]>([]);
const addUser = (user: User) =>
setUser(prevUsers => [...prevUsers, user]);
}
//...
}
🎈 Explanation
Using the correct method with the function that takes the prevUsers
as an argument ensures that you're always working with the latest state, preventing race conditions and issues that may arise from concurrent state updates. This is particularly important in situations where multiple state updates may occur in rapid succession or in response to user interactions.
7. Forgetting to clean up side effects in useEffect
❌ The wrong way
export default function UserList() {
const [users, setUsers] = useState<User[]>([]);
const onAddUser = (user: User) => {
setUsers(prevUsers => [...prevUsers, user]);
}
useEffect(() => {
socket.on('add-user', onAddUser);
},[])
//...
}
✅ The right way
export default function UserList() {
const [users, setUsers] = useState<User[]>([]);
const onAddUser = (user: User) => {
setUsers(prevUsers => [...prevUsers, user]);
}
useEffect(() => {
socket.on('add-user', onAddUser);
return () => {
socket.off('add-user', onAddUser);
}
}, [])
//...
}
🎈 Explanation
Forgetting to unsubscribe can lead to unexpected bugs and memory leakage. In this example when the socket acts on the add-user event, because we did not unsubscribe, onAddUser will be called twice. Also every time we left the page, and came back, another subscription to the add-user event will be added, which results in memory leakage.
It is always a best practice to unsubscribe when possible.
Thank you for reading!
If you enjoyed this article, please clap a few times or share it with your awesome friends.
If you like this content, feel free to follow me on medium.
More posts like this:
Supercharge Your React Components with This Powerful Pattern
Top comments (0)