Top common mistakes in react and how to avoid them

1. Not Starting a Component’s Name with a Capital Letter 2. Not Creating Enough Components 3. Not Understanding the Virtual DOM 4. Not Understanding the Component Lifecycle 5. Not Using the “key” Attribute in Listing Component 6. Not Using the Optional Chaining Option 7. Modifying the State Directly 8. Using Duplicate State 9. Forgetting that setState() Is an Async Function 10. Underutilizing Redux or Using Redux so much 11. Not Using Multiple useEffect() 12. Passing Props Down as Strings Instead of Numbers 13. Using Anchor Tag for Navigation rather than Using the Link Component 14. Not Understanding Event Handling 15. Not Using the React Developers Tools and ESLint Plugin

Let's go over the most common mistakes you might be making in your React code right now, plus how to fix them.

If you want to create amazing React applications, it's essential to avoid many common errors along the way.

In this article, we'll not only cover how to fix your mistakes quickly, but also give you some awesome design patterns to make your code better and more reliable going forward.

Top common mistakes in react and how to avoid them

1. Don't pass state variables to setState in React

In the code below, we have a todo application that displays an array of todos (in TodoList).

We can add new todos in the

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

0 component, which updates the

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

1 array in App.

What's the problem with the props that we have passed to

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

0?

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos, todos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; const newTodos = todos.concat(todo); setTodos(newTodos); } return (
); }

We are adding the new todo to the

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

1 array and then setting state as we should. This will update the displayed todos in the TodoList component.

However, since the new state is based off of the previous state, we do not need to pass down the todos array.

Instead, we can access the previous todos state by writing a function within the setState function. Whatever we return from this function will be set as the new state.

In other words, we only need to pass down the

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

5 function to properly update state:

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

2. Make your React components single responsibility

In the application below, we're fetching a number of the users from an API within our app component, putting that user data in a state, and then displaying it within our user interface.

What is the problem with the

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

6 component?

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

In our component, we're doing multiple things.

We are not only doing remote data fetching from a server, but we are also managing state as well as displaying that state with JSX.

We are making our component do multiple things. Instead, your components should do only one thing and do that thing well.

This is one key design principle from the acronym SOLID, which lays out five rules for writing more reliable software.

The S in SOLID stands for the "single-responsibility principle", an essential one to use when writing React components.

We can divide our

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

6 component into separate components and hooks that each have their own responsibility. First, we will be extracting the remote data fetching to a custom React hook.

This hook, which we'll call useUserData, will take care of fetching the data and putting it in local state.

function useUserData() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((json) => {
        setUsers(json);
      });
  }, []);
  return users;
}

After that, we will call the hook within

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

6 to access our

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

9 array.

However, instead of displaying user data directly within our return statement in

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

6, we will create a separate

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

1 component which will contain all of the JSX necessary to display each element in that array, plus any related styles (if we have any).

function User({ user }) {
  const styles = {
    container: {
      margin: '0 auto',
      textAlign: 'center' 
    }
  };  
  return (
    
{user.name}
); } export default function App() { const users = useUserData(); return ( <>

Users

{users.map((user) => ( ))} ); }

After this refactoring, our components now have a clear, individual task to perform, which makes our app much easier to understand and extend.

3. Make your side effects single responsibility

In our

export default function App() {
  const [todos, setTodos] = React.useState([]);
  return (
    

Todo List

); } function AddTodo({ setTodos }) { function handleAddTodo(event) { event.preventDefault(); const text = event.target.elements.addTodo.value; const todo = { id: 4, text, done: false }; setTodos(prevTodos => prevTodos.concat(todo)); } return (
); }

6 component below, we are fetching both user and post data.

When the location – the URL – of our app changes, we fetch both the user and post data.

export default function App() {
  const location = useLocation();
  function getAuthUser() {
    // fetches authenticated user
  }
  function getPostData() {
    // fetches post data
  }
  React.useEffect(() => {
    getAuthUser();
    getPostData();
  }, [location.pathname]);
  return (
    
); }

We display a new post if the URL changes, but do we need to fetch that every time the location changes?

We don't.

In much of your React code, you may be tempted to stuff all of your side effects within a single use effect function. But doing so violates the single responsibility principle we just mentioned.

This can result in problems such as performing side effects when we don't need to. Remember to keep your side effects to a single responsibility as well.

In order to fix our code, all we need to do is call

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

3 within a separate use effect hook. This ensures it is not called whenever the location pathname changes, but only once when our app component mounts.

export default function App() {
  const location = useLocation();
  React.useEffect(() => {
    getAuthUser();
  }, []);
  React.useEffect(() => {
    getPostData();
  }, [location.pathname]);
  return (
    
); }

4. Use ternaries instead of

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

4 in JSX

Let's say we are displaying a list of posts in a dedicated component,

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

5.

It makes sense to check whether we have posts before we iterate over them.

Since our

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

6 list is an array, we can use the

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

7 property to check and see if it is a truthy value (greater than 0). If so, we can map over that array with our JSX.

We can express all this with the and

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

4 operator:

export default function PostList({ posts }) {
  return (
    
    {posts.length && posts.map((post) => )}
); }

However, you might be surprised with what we see, if we were to execute such code. If our array is empty, we don't see nothing – we see the number 0!

What? Why is this?!

This is a JavaScript-related problem, because the length of our array is 0. Because 0 is a falsy value, the

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

4 operator doesn't look at the right hand side of the expression. It just returns the left hand side – 0.

What is the best way to fix this and to prevent such errors in the future?

In many cases we shouldn't use the and operator, but instead use a ternary to explicitly define what will be displayed in the event that condition is not met.

If we were to write the following code with a ternary, we would include the value

function useUserData() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((json) => {
        setUsers(json);
      });
  }, []);
  return users;
}

0 in the else condition to ensure that nothing is displayed.

export default function PostList({ posts }) {
  return (
    
    {posts.length ? posts.map((post) => ) : null}
); }

By using ternaries instead of

export default function App() {
  const [users, setUsers] = React.useState([]);
  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);
  return (
    <>
      

Users

{users.map((user) => (
{user.name}
))} ); }

4, you can avoid many annoying bugs like this one.

Thanks for reading!

Become a Professional React Developer

React is hard. You shouldn't have to figure it out yourself.

I've put everything I know about React into a single course, to help you reach your goals in record time:

Introducing: The React Bootcamp

It’s the one course I wish I had when I started learning React.

Click below to try the React Bootcamp for yourself:

Top common mistakes in react and how to avoid them
Click to get started



Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Get started

How to work with React the right way to avoid some common pitfalls?

If you pass a new object or array to a child component, even if it has the same values, React will consider it as new props, and the component will re-render. To avoid this, you can use React. memo or shouldComponentUpdate to optimize the rendering process.

What are problems with React?

Problems and Solutions to Consider When Developing a React JS App.

Problem 1: State Management..

Problem 2: Component Communication..

Problem 3: Performance Optimization..

Problem 4: Routing..

Problem 5: Server-Side Rendering (SSR).

Problem 6: Styling..

Problem 7: Cross-Browser Compatibility..

Problem 8: Security..

What is the bad side of React?

Poor Documentation Since React is changing so fast, new tools and patterns are adding up every now and then, and it is becoming difficult for the community to maintain the documentation. This makes it difficult to work with for new developers who want to start with React.

How can I be better at React?

At a high level, React developers should be able to:.

Work with and write semantic HTML tags..

Work with and write CSS selectors..

Implement a CSS reset..

Understand the box model and how to reset to border-box..

Understand flexbox..

Work with and implement responsive web principles including the proper user of media queries..