Ten Common Mistakes To Avoid When Using React

React is one of the most popular client-side JavaScript libraries these days and its popularity is likely to increase in the future. It’s also very hard to find good articles discussing React JS drawbacks because everyone seems to love it. Not knowing common react mistakes can lead to some bad practices. Are you making any of these mistakes in your React app?

8 mins read
September 16, 2022
React is a great JavaScript library for creating user interfaces. You can easily design stunning applications using React. However, React may be misused to the point where you wind up causing more issues than you can manage to fix.

If you don’t remember some crucial “gotchas,” you risk writing spaghetti code, getting trapped on an untraceable error, or worse—having to rewrite most of your application. So let’s jump right into the ten typical errors React users should avoid.

Missing Props Validations

React Components use props as a method of passing read-only data from parent to child. The functioning of a component frequently depends on the props that are provided. Therefore, to ensure that the props work as intended, it is crucial to validate them before processing.

PropTypes and defaultprops are the two properties that make up a React component. The propTypes property specifies the kind of props that it will accept. If the parent does not provide the requested props, the default value for those props is described by the defaultProps property.

import PropTypes from 'prop-types';

const UserDetails = props => (
  <>
     <div>Name: {props.name}</div>
     <div>Email: {props.email}</div>
     <div>Age: {props.age}</div>
  </>
);

UserDetails.propTypes = {
  // age must be number or string value
  age: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  // email must be string value
  email: PropTypes.string,
  // name must be passed from parent and contains string value
  name: PropTypes.string.required,
};

UserDetails.defaultProps = {
  age: '--', // age will contain this value if not passed by parent
  email: '--', // email will contain this value if not passed by parent
};

Set The Wrong Value To The Initial State

Data or information about the component is stored using a built-in React object called the state. A component may vary over time, and each time it does, a new rendering of the component is produced. A component accepts an initial state when it loads in the application and subsequently performs changes to the scenarios. Setting a proper state at initialization is crucial to stopping re-rendering.

// Bad
export const UserDetails = () => {
   const [user, setUser] = useState();
   // Here, rendering will break because user is not an object
   // and can't access the property name of user
   return <div>Name: {user.name}<div>;
};
// Good
export const UserDetails = () => {
   const [user, setUser] = useState({ name: '' });
   return <div>Name: {user.name}<div>; 
};

Don’t Update State Directly

React gave us the function setState in the class components to modify the state object. On occasion, such as in this state, we immediately update the state object. name = “Test” will stop the render function from running again. So if we want to update the state, we should utilize the setState function.

The useState gives us the state value in the first index and a function to set the state value in the second index, both of which are identical in terms of their functional components.

let [name, setName] = useState(‘’)

We should not update the name directly like `name = ‘Test’`. We must use the `setName` function to set the state value like setName(‘Test’). It will re-render the value in DOM.

React gives us the function setState in the class components to modify the state object. On occasion, such as in this state, we immediately update the state object. state.name = will not execute the render function again with updated state values.

The useState hook gives us the state value in the first (0) index and a function to update the state value in the second (1) index, both of which are identical in terms of their functional components.

For example, we should not update the value of the state directly. We must use this.setState in the class component or setName (the state setter in hook) to update the state.

export class UpdateStateExample extends React.Component {
    state = {
        name: '',
    };

    onChange = evt => {
        // Bad - It will not re-render the input with updated value
	this.state.name = evt.target.value;

	// Good - It will re-render the input with updated value
	this.setState({ name: evt.target.value });
    };
    
    render() {
        return (
	    <input
  	        type="text"
  	        placeholder="Type name here..."
  	        value={this.state.name}
  	        onChange={onChange}
	    />
        );
    }
}

export function UpdateStateExampleV2() {
    let [name, setName] = useState('');

    const onChange = evt => {
       // Bad - It will not re-render the input with updated value
       name = evt.target.value;

      // Good - It will re-render the input with updated value
      setName(evt.target.value);
    };

    return (
	<input
  	    type="text"
  	    placeholder="Type name here..."
  	    value={name}
  	    onChange={onChange}
	/>
    );
}

Asynchronous setState

We frequently encountered this problem when we first began developing in React. Asynchronous setState is used. After some time, it updates the state value. After altering the state value, we are unable to access it. To access the changed state, we must give a callback function to setState as the second parameter in class components.

// Case 1
this.setState({ name: 'Ravi' });
console.info(this.state.name); // It will print empty string

// Case 2
this.setState({ name: 'Ravi Heer' }, () => {
   // It will print Ravi Heer
   console.info(this.state.name);
});

In functional components, we are also facing the same issue. To access the updated state for the next execution, we can bind this state with the useEFfect hook and execute the logic there.

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

    const onChange = evt => {
       setName(evt.target.value);
        // the updated value of name is not accessible here
        // We have to use useEffect and bind with name dependency
    };

    useEffect(() => {
        // Whenever the value of name will be changed
        // this code will be executed with updated value of name
        // You can execute your logic with updated value of name here
       console.log(name);
   }, [name]);

   return <input type="text" name="name" onChange={onChange} />;
}

Stale State

In React, Functional Components, It’s too frustrating to deal with a stale state at first.

Let’s consider we have a count variable in state and we have to increment it repeatedly at a specific count.

As code executes, we expect the final value of the count to be 3 but it is not in this case. While we are updating the count using setCount(count + 1), it is not using the updated current value of the count, but the initial value. It is called a stale state in React.

As we discussed earlier, the state setter in the useState hook is asynchronous. This indicates that between each repetition of the loop, the value of the count is not actually getting updated as expected. Therefore, each time, we’re incrementing a previous value (zero), not the current value.

React also offers another method for updating the state to address the problem of a stale state. During the call to setCount, we have the option of supplying a callback function rather than the count value. With the help of this method, we can get the state’s latest modified value and then return it. With each cycle execution, it is consequently correctly increased, resulting in an ultimate count of 3.

const StateStateExample = () => {
  const [count, setCount] = useState(0);

  const onClickIncrementCount = () => {
    for (let i = 0; i < 3; i++) {
      // It will use the count value 0 in every iteration       
      // and final value of count will be 1       
      setCount(count + 1); // Stale state issue   
      
      // It will use the count value 0 in first iteration       
      // then will use the updated value of count in next iterations      
      setCount(count => count + 1); // Stale state issue resolved
    }
  };
 
  return (
    <>
      <b>Count: {count}</b>
      <button onClick={onClickIncrementCount}>Increment Count</button>
    </>
  );
};

Use Unique IDs In The Map

We are using the map function to show a list of items with a single block of code. If we are not possessing the key prop to the wrapper, it will show a warning message.

To skip this message we should pass a unique ID as a key prop to the elements. This key is used as an identifier of this element. This unique key prop is used for performance optimization and updates in a loop.

We can pass keys in multiple ways like:

  • Array Index
  • Use any ID type key from the list which will be unique
  • Use any library or custom function to get a unique ID

// Good if need no updates on change
const UsersNameV1 = ({ users }) => (
  <ul>
    {users.map((user, index) => (
      <li key={index}>{user.name}</li>
    ))}
  </ul>
);
 
// Good - Each element is getting bind with id
const UsersNameV2 = ({ users }) => (
  <ul>
    {users.map(user => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);
 
// Good - Each element is getting bind with unique id
const UsersNameV3 = ({ users }) => (
  <ul>
    {users.map(user => {
      const uniqueId = getUniqueId(); // Any library or custom function to generate unique ID
 
      return <li key={uniqueId}>{user.name}</li>;
    })}
  </ul>
);

Beneficial Fragments

In React, when we have to return multiple sibling elements as a group, we wrap those elements in a single tag. We mostly use the div tag as a wrapper. But React provides us with Fragments for the same purpose.

We can use <> Code</> or <React.Fragment>Code</React.Fragment> for the same purpose. The benefit of this over div tag is that it will not create an element in DOM but div creates an element

export const Test = () => {
    return (
        // If div is just used to wrap and return multiple elements
        // Then it is bad practice
        <div>
            <div>Element 1</div>
            <div>Element 2</div>
        </div>
    );
};

// Good
export const FragmentsExample01 = () => {
    return (
        <>
           <div>Element 1</div>
           <div>Element 2</div>
        </>
    );
};

export const FragmentsExample02 = () => {
    return (
          <React.Fragment>
             <div>Element 1</div>
             <div>Element 2</div>
          </React.Fragment>
    );
};

Anonymous Action Handlers

React’s action handling feature is quite intriguing. OnClick, OnChange, OnFocus, among many other routinely handled actions. Additionally, we are creating parent-child connections and handling custom actions via props. A distinct handler function may occasionally be given in place of an anonymous function when passing an action handler. When we send a reference to an unnamed function, it always passes a new reference on each render; but, when we pass a reference to a defined function, it always maintains the same reference.

export const Test = () => {
  return (
    <input
      type="text"
      name="test"
      onChange={event => {
        /* Handle Change */
      }}
    />
  );
};

// Good
export const Test = () => {
  const onChangeInput = event => {
    // Handle Change
  };
 
  return <input type="text" name="test" onChange={onChangeInput} />;
};

Using Redux/Flux Everywhere

Using props, Redux/Flux is a very useful technique/library that enables access to or setting of values kept in a single location known as the store in numerous components at any level of the hierarchy. Any value that is utilized in-store can be changed using actions from any component, and it will be updated everywhere it is used.

Redux has a complicated code-level structure in addition to its benefits. It does a variety of tasks in the background. In programming, redux should be used only when it is necessary. The performance of an application will suffer if redux is used throughout all of its parts.

Some Reasons:

  • Few states must be shared across components and a small codebase.
  • Your app only consists of UI changes.
  • Only their offspring are receiving high-level component props.
  • Your application doesn’t need a lot of frequently refreshed data.
  • It does not need an authentication mechanism, such as determining whether a user is logged in or authenticated.

Forget To Clean Up Side Effects

If the component unmounts or the side-outcome effect is no longer needed, don’t forget to clean it up. For instance, if you’ve started a countdown, be sure to terminate it when the component unmounts.

What happens if your component’s state is supposed to be updated after a certain amount of time, but the component unmounts during that time?

The counter in the example below is increased every five seconds. The mounting of the component initiates the incrementation cycle. However, after unmounting, there is no code to halt that increment cycle.

In the second code example below, we returned a function (in useEffect) which is executed when the component will unmount and it will stop the increment cycle (Clear the interval).

We should clean up side effects when a Component unmounts

  • To avoid memory leaks
  • To optimize our applications for a good user experience
  • To prevent unexpected errors in our applications

// Bad
export function SideEffectsExample() {
  const [counter, setCounter] = useState(0);
 
  useEffect(() => {
    setInterval(() => {
      setCounter(counter => counter + 1);
    }, 5000);
  }, []);

  return <div>Count: {counter}</div>;
}

// Good
export function SideEffectsExample() {
  const [counter, setCounter] = useState(0);
 
  useEffect(() => {
    const intervalId = setInterval(() => {
      setCounter(counter => counter + 1);
    }, 5000);
 
    // we need this cleanup function... (on Unmount)
    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return <div>Count: {counter}</div>;
}

Conclusion

By steering clear of the common react mistakes mentioned in this article and staying informed about best practices, you can elevate your React development skills and create remarkable user experiences.

Remember, React is a powerful tool, but it’s only as good as the developer wielding it. Keep learning, stay curious, and strive for excellence in your React projects.

Frequently Asked Questions

Q: What is the most common react mistake?
A: Neglecting proper component structuring is one of the most common mistakes in React development.

React

    Related Articles