common react mistakes
user profile avatar
Tech Tales Team

Published on • 🕑5 min read

Common Mistakes that Beginners Often Make in React

2likes1

Blog views242

Listen to this blog

React is a popular javascript framework that is the most loved library for building user interfaces. Despite being one of the front-end developer's favorites, React introduces pitfalls that beginners often fall into. These blogs are related to state and effects and other functionalities that make React easier to work with. Here are five common mistakes beginners often make in React, along with their solutions.

1. Mutating state

React's state is an essential concept that allows components to manage and update their data. However, directly modifying the state is a common mistake that can lead to unexpected behavior and difficult-to-debug issues.

An example of a mutating state is through the use of destructive array methods such as push to add an item to a list.

const [items, setItems] = useState([1, 2, 3]);
  // ❌ Mutates an existing object
const addItem = (item) => {
  items.push(item); // Mutating state directly
  setItems(items);
};
// âś… Creates a new object
const addItem = (item) => {
  setItems([...items, item]); // Creating a new array
};

React relies on a state variable's identity to tell when the state has changed. When we push an item into an array, we aren't changing that array's identity, and so React can't tell that the value has changed.

Instead of modifying an existing array, I'm creating a new one from scratch. It includes all of the same items (courtesy of the ... spread syntax), as well as the newly-entered item.

The distinction here is between editing an existing item, versus creating a new one. When we pass a value to a state-setter function like setCount, it needs to be a new entity. The same thing is true for objects.

2. Accessing state after changing it

State in React is asynchronous. This means that when we update the state we are not re-assigning a variable but rather scheduling a state update. Mostly common, developers will be frustrated if they console.log the new value and it is not there.

For example:

const [count, setCount] = useState(0);
const increment = () => {
  setCount(count + 1);
  console.log(count); // This will log the old count, not the new one
};

This can lead to confusion when debugging because the console might not show the expected value. It can take a while for us to fully wrap our heads around this idea, but here's something that might help it click: we can't reassign the count variable, because it's a constant!
To work with the updated state, you can use the functional form of setState, which provides the latest state:

const increment = () => {
  setCount((prevCount) => {
    console.log(prevCount + 1); // Correctly logs the new count
    return prevCount + 1;
  });
};

3. Using Functions as useEffect Dependencies 

useEffect is one of the most abused hooks and developers often use it recklessly even in areas where it is not needed. One common mistake with useEffect is failing to provide a dependency array, which leads to endless renders. 

One point to note here is not to confuse re-renders with refresh. Since React uses a Virtual DOM, re-renders happen in the back-stage and thus developers might fail to notice.

The following is a react useEffect example: 

function App(){ 
    const [data, setData] = useState(null); 
    const fetchData = () => { 
         // some code 
    } 
    useEffect(() => { 
    fetchData(); //used inside useEffect 
     }, [fetchData]) 
} 

It is not recommended to define a function outside and call it inside an effect. In the above case, the passed dependency is a function, and a function is an object, so fetchData is called on every render.

React compares the fetchData from the previous render and the current render, but the two aren't the same, so the call is triggered. 

4. Props Drilling

Props drilling in React occurs when developers pass the same props to every component one level down from its parent to the required component at the end. Thus, components become closely connected with each other and can’t be used without adding a particular prop. The fewer unnecessary interconnections between components in your code, the better.

For example, if we need to pass a user name to a deeply nested component but the name is defined in the first parent component:

const Grandparent = () => {
  const user = { name: 'Alice' };
  return <Parent user={user} />;
};
const Parent = ({ user }) => {
  return <Child user={user} />;
};
const Child = ({ user }) => {
  return <div>{user.name}</div>;
};

In this example, the user prop is passed down from Grandparent to Child through the Parent component, even though Parent doesn't need it. This kind of pattern can quickly lead to a tangled mess of props.

To avoid props drilling, consider using React Context or state management libraries like Redux, Zustand, or Recoil. Context allows you to share data across multiple components without passing props manually through every level.

// Create a context
const UserContext = createContext();
const Grandparent = () => {
  const user = { name: 'Alice' };
  return (
    <UserContext.Provider value={user}>
      <Parent />
    </UserContext.Provider>
  );
};
const Parent = () => {
  return <Child />;
};
const Child = () => {
  const user = useContext(UserContext);
  return <div>{user.name}</div>;
};

5. Changing from Uncontrolled to Controlled Inputs

Uncontrolled components rely on the DOM to manage their state, while controlled components rely on React. Beginners sometimes switch from uncontrolled to controlled components without a clear need, adding complexity without gaining any tangible benefits.

One major reason why developers switch to controlled inputs is the need for validations. However, we can validate inputs without the need for additional javascript simply using browser inbuilt functions. See more here a-better-way-to-validate-html-forms-without-usestate.

Another reason (I am often guilty of this) is when we need to implement search functionality that filters our data to return the results. However, the most efficient solution to search is to store the value in search parameters. In this way, the value will not be lost on refresh.

Developers might implement the search functionality like this:

function SearchForm() {
  const [query, setQuery] = useState(null);
  function handleSearch (e){
    e.preventDefault()
    window.location.href=`/my-site/search?q=${query}`
  }
  return (
    <form onSubmit={handleSearch}>
      <search>
        <input
          type="search"
          value={search}
          onChange={() => setQuery(e.target.value)}></input>
      </search>
    </form>
  );
}

This example receives the search input and saves it in the state. When the search form is submitted, we redirect the user to the search page with the search-params.

One simple solution is:

function SearchForm() {
  return (
    <form action="/search/query">
      <search>
        <input type="search" name="query"></input>
      </search>
    </form>
  );
}

This will work the same and the form will be submitted with the query params that can be used to filter the data. Another advantage of this setup is that if we are using NextJS, this form will work as a server component and thus benefit from faster rendering.

Conclusion

There are many mistakes that we make in React that we are often unaware of. By understanding and avoiding these common mistakes, you can write more robust and efficient React code. Remember to practice good coding practices and explore the full range of React hooks to build high-quality user interfaces.

Like what you see? Share with a Friend

2 Comments

1 Likes

Comments (2)

sort comments

Before you comment please read our community guidelines


Please Login or Register to comment

user profile avatar

Tech Wizard

online

Published on

Generally, "prop drilling" is fine. It looks a bit ugly, but it's easy to understand and easy to change. I think we can use prop drilling with some caution especially in server components since using context will turn our entire component tree to a client component.

user profile avatar

Tech Tales Team

online

Published on

Let me know which mistake you often make with React