DEV Community

Cover image for Master React: Say Goodbye to 7 Costly Mistakes Forever!
miroiu-dev
miroiu-dev

Posted on

Master React: Say Goodbye to 7 Costly Mistakes Forever!

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.

  1. Calling a function that takes no arguments in an event handler.
  2. Using useEffect to transform rendering data.
  3. Resetting the state of a component using useEffect.
  4. Not using key in lists.
  5. Modify the state value directly by assignment.
  6. Not using the previous state when calling setState.
  7. 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>
 );
}
Enter fullscreen mode Exit fullscreen mode

✅ 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>
 );
}
Enter fullscreen mode Exit fullscreen mode

🎈 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]);
  // ...
}
Enter fullscreen mode Exit fullscreen mode

✅ The right way

function Form() {
  const [firstName, setFirstName] = useState('Miroiu');
  const [lastName, setLastName] = useState('Dev');

  const fullName = firstName + ' ' + lastName;
  // ...
}
Enter fullscreen mode Exit fullscreen mode

🎈 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]);
  // ...
}
Enter fullscreen mode Exit fullscreen mode

✅ The right way

export default function Page({ formId }) {
  return (
    <Form
      formId={formId}
      key={formId}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

🎈 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}/>)}
  );
}
Enter fullscreen mode Exit fullscreen mode

✅ The right way

export default function UserList({ users }) {
  return (
    {users.map(user => <User key={user.id} {...user}/>)}
  );
}
Enter fullscreen mode Exit fullscreen mode

🎈 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} />
   );
}
Enter fullscreen mode Exit fullscreen mode

✅ The right way

export default function User() {
  const [name, setName] = useState(''); 

  const handleOnChange = (ev: ChangeEvent<HTMLInputElement>) => {
    setName(ev.target.value);
  }

  return (
    <input onChange = {handleOnChange} />
   );
}
Enter fullscreen mode Exit fullscreen mode

🎈 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);
  }

  //...
}
Enter fullscreen mode Exit fullscreen mode

✅ The right way

export default function UserList() {
  const [users, setUsers] = useState<User[]>([]);

  const addUser = (user: User) =>
    setUser(prevUsers => [...prevUsers, user]);
  }

  //...
}
Enter fullscreen mode Exit fullscreen mode

🎈 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);
   },[])
  //...
}
Enter fullscreen mode Exit fullscreen mode

✅ 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);
      }
   }, [])
  //...
}
Enter fullscreen mode Exit fullscreen mode

🎈 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

Master Data Fetching in React with This Easy Guide

Mastering JavaScript Classes: Your Shortcut to OOP Success!

Top comments (0)