DEV Community

Cover image for 🔥Top 11 Mistakes to Avoid When Using React In 2022
Chris1993
Chris1993

Posted on

🔥Top 11 Mistakes to Avoid When Using React In 2022

As React becomes more and more popular, more and more React developers have encountered various problems in the development process.

In this article, based on my actual work experience, I will summarize some common mistakes in 11 React development to help you avoid some mistakes.


If you are just starting to use React, it is recommended that you take a good look at this article. If you have already used React to develop projects, it is also recommended that you check and fill in the gaps.

After reading this article, you will learn how to avoid these 11 React mistakes:

  1. When rendering the list, do not use the key
  2. Modify the state value directly by assignment
  3. Bind the state value directly to the value property of the input
  4. Use state directly after executing setState
  5. Infinite loop when using useState + useEffect
  6. Forgetting to clean up side effects in useEffect
  7. Incorrect use of boolean operators
  8. The component parameter type is not defined
  9. Passing Strings as Values to Components
  10. There is no component name that starts with a capital letter
  11. Incorrect event binding for element

1. When rendering the list, do not use the key

Problem

When we first learned React, we would render a list according to the method described in the documentation, for example:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li>{number}</li>);
Enter fullscreen mode Exit fullscreen mode

After rendering, the console will prompt a warning ⚠️ a key should be provided for list items.

Solutions

You just need to follow the prompts and add the key attribute to each item:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number, index) => <li key={index}>{number}</li>);
Enter fullscreen mode Exit fullscreen mode

key helps React identify which elements have changed, such as been added or removed. So we need to set a unique key value for each element in the array.

Documentation

React - Basic List Component


2. Modify the state value directly by assignment

Problem

In React, state cannot be directly assigned and modified, otherwise it will cause problems that are difficult to fix. Example:

updateState = () => {
  this.state.name = "Chris1993";
};
Enter fullscreen mode Exit fullscreen mode

At this point, the editor will prompt a warning ⚠️:

Do not mutate state directly. Use setState().
Enter fullscreen mode Exit fullscreen mode

Solutions

Class components can be modified with the setState() method, and function components can be modified with useState():

// ClassComponent:use setState()
this.setState({ name: "Chris1993" });

// FunctionConponent:use useState()
const [name, setName] = useState("");
setName("Chris1993");
Enter fullscreen mode Exit fullscreen mode

Documentation

React - State and Lifecycle
React - Using the State Hook


3. Bind the state value directly to the value property of the input

Problem

When we directly bind the value of state as a parameter to the value property of the input tag, we will find that no matter what we enter in the input box, the content of the input box will not change.

export default function App() {
  const [count, setCount] = useState(0);
  return <input type="text" value={count} />;
}
Enter fullscreen mode Exit fullscreen mode

This is because we use the state variable with state as the default value to assign to the value of <input>, and the state in the functional component can only be modified by the set method returned by useState . So the solution is also very simple, just use the set method when modifying.

Solutions

Just bind an onChange event to <input>, and modify it by calling setCount:

export default function App() {
  const [count, setCount] = useState(0);
  const change = (val) => setCount(val.value);
  return <input type="text" value={count} onChange={change} />;
}
Enter fullscreen mode Exit fullscreen mode

4. Use state directly after executing setState

Problem

When we modify the data through setState() and get the new data immediately, there will be a situation where the data is still the old data:

// init state data
this.state = { name: "Chris1993" };

// update state data
this.setState({ name: "Hello Chris1993!" });
console.log(this.state.name); // output: Chris1993
Enter fullscreen mode Exit fullscreen mode

We might think that the this.state.name entered at this point should be Hello Chris1993!, but it turns out to be Chris1993.

This is because setState() is asynchronous. When setState() is executed, the real update operation will be placed in the asynchronous queue for execution, and the code to be executed next (ie console.log this line) is executed synchronously, so the state printed out is not the latest value.

Solutions

Just encapsulate the subsequent operation to be performed as a function as the second parameter of setState(), this callback function will be executed after the update is completed.

this.setState({ name: "Hello Chris1993!" }, () => {
  console.log(this.state.name); // output: Hello Chris1993!
});
Enter fullscreen mode Exit fullscreen mode

The correct content is now output.


5. Infinite loop when using useState + useEffect

Problem

When we directly call the set*() method returned by useState() in useEffect(), and do not set the second parameter of useEffect(), we will find an infinite loop:

export default function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount(count + 1);
  });
  return <div className="App">{count}</div>;
}
Enter fullscreen mode Exit fullscreen mode

At this time, you can see that the data on the page has been increasing, and useEffect() has been called infinitely, entering an infinite loop state.

Solutions

This is a common problem of using useEffect() incorrectly. useEffect() can be regarded as a combination of the three lifecycle functions componentDidMount, componentDidUpdate and componentWillUnmount in class components.
useEffect(effect, deps) takes 2 arguments:

  • effect side effect function;
  • deps array of dependencies.

When the deps array changes, the side effect function effect is executed.
To modify the method, you only need to pass [] in the second parameter of useEffect():

export default function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    setCount(count + 1);
  }, []);
  return <div className="App">{count}</div>;
}
Enter fullscreen mode Exit fullscreen mode

To summarize the 4 cases where useEffect is used:

  • Do not set the second parameter: When any state is updated, the side effect function of useEffect will be triggered.
useEffect(() => {
  setCount(count + 1);
});
Enter fullscreen mode Exit fullscreen mode
  • The second parameter is an empty array: The side effect function of useEffect is only triggered on mount and unmount.
useEffect(() => {
  setCount(count + 1);
}, []);
Enter fullscreen mode Exit fullscreen mode
  • **The second parameter is a single-valued array: **The side-effect function of useEffect will be triggered only when the value changes.
useEffect(() => {
  setCount(count + 1);
}, [name]);
Enter fullscreen mode Exit fullscreen mode
  • **The second parameter is a multi-valued array: **The side effect function of useEffect will only be triggered when the passed value changes.
useEffect(() => {
  setCount(count + 1);
}, [name, age]);
Enter fullscreen mode Exit fullscreen mode

6. Forgetting to clean up side effects in useEffect

Problem

In class components, we use the componentWillUnmount() lifecycle method to clean up some side effects, such as timers, event listeners, etc.

Solutions

A return function can be set for the side effect function of useEffect(), which is similar to the role of the componentWillUnmount() lifecycle method:

useEffect(() => {
  // Other Code
  return () => clearInterval(id);
}, [name, age]);
Enter fullscreen mode Exit fullscreen mode

Documentation

React - Example Using Hooks


7. Incorrect use of boolean operators

Problem

In JSX/TSX syntax, we often use boolean values to control rendered elements, and in many cases we use the && operator to handle this logic:

const count = 0;
const Comp = () => count && <h1>Chris1993</h1>;
Enter fullscreen mode Exit fullscreen mode

We thought that the page displayed empty content at this time, but it actually displayed the content of 0 on it.

Solutions

The reason is because the falsy expression causes elements after && to be skipped, but returns the value of the falsy expression. So we try to write the judgment condition as complete as possible, without relying on the true and false of JavaScript's boolean value to compare:

const count = 0;
const Comp = () => count > 0 && <h1>Chris1993</h1>;
Enter fullscreen mode Exit fullscreen mode

The page will display empty content.

Documentation

React - Inline If with Logical && Operator


8. The component parameter type is not defined

Problem

It is common for team development. If the components developed by each person do not have well-defined parameter types, it is easy for cooperating colleagues to not know how to use the components, which is very troublesome, such as:

const UserInfo = (props) => {
  return (
    <div>
      {props.name} : {props.age}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Solutions

Solutions are

  • Using TypeScript, define component props types;
// ClassComponent
interface AppProps {
  value: string;
}
interface AppState {
  count: number;
}

class App extends React.Component<AppProps, AppStore> {
  // ...
}

// FunctionComponent
interface AppProps {
  value?: string;
}
const App: React.FC<AppProps> = ({ value = "", children }) => {
  //...
};
Enter fullscreen mode Exit fullscreen mode
  • Without using TypeScript, props types can be defined using propTypes;
const UserInfo = (props) => {
  return (
    <div>
      {props.name} : {props.age}
    </div>
  );
};

UserInfo.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
};
Enter fullscreen mode Exit fullscreen mode

9. Passing Strings as Values to Components

Problem

Since React also has a template syntax, which is very similar to HTML, it often happens that numbers are passed directly to components as props, resulting in an unexpected value judgment:

<MyComp count="99"></MyComp>
Enter fullscreen mode Exit fullscreen mode

Passing props.count === 99 in the MyComp component will return false.

Solutions

The correct way should be to use curly brackets to pass parameters:

<MyComp count={99}></MyComp>
Enter fullscreen mode Exit fullscreen mode

10. There is no component name that starts with a capital letter

Problem

Developers just starting out often forget to start with a capital letter for their component names. Components starting with a lowercase letter in JSX/TSX are compiled into HTML elements, such as <div /> for HTML tags.

class myComponent extends React.component {}
Enter fullscreen mode Exit fullscreen mode

Solutions

Just change the first letter to uppercase:

class MyComponent extends React.component {}
Enter fullscreen mode Exit fullscreen mode

Documentation

React - Rendering a Component


11. Incorrect event binding for element

Problem

import { Component } from "react";

export default class HelloComponent extends Component {
  constructor() {
    super();
    this.state = {
      name: "Chris1993",
    };
  }

  update() {
    this.setState({ name: "Hello Chris1993!" });
  }
  render() {
    return (
      <div>
        <button onClick={this.update}>update</button>
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

When clicking the update button, the console will report an error:

Cannot read properties of undefined (reading 'setState')
Enter fullscreen mode Exit fullscreen mode

Solutions

This is because this points to the problem and there are several solutions:

  • bind in the constructor
constructor() {
  super();
  this.state = {
    name: "Chris1993"
  };
  this.update = this.update.bind(this);
}
Enter fullscreen mode Exit fullscreen mode
  • Use arrow functions
update = () => {
  this.setState({ name: "Hello Chris1993!" });
};
Enter fullscreen mode Exit fullscreen mode
  • Bind in the render function (not recommended, create a new function every time the component renders, affecting performance)
<button onClick={this.update.bind(this)}>update</button>
Enter fullscreen mode Exit fullscreen mode
  • Use arrow functions in the render function (not recommended, create a new function every time the component renders, affecting performance)
<button onClick={() => this.update()}>update</button>
Enter fullscreen mode Exit fullscreen mode

Documentation

React - How do I pass an event handler (like onClick) to a component?


If you think this article is good, please like, comment and follow, your support is the biggest motivation for me to share.

https://medium.com/@Chris1993

Top comments (1)

Collapse
 
loucyx profile image
Lou Cyx

I see a few issues and missing info in the list:

1. When rendering the list, do not use the key

That's true (tho in dev mode you'll get a warning if you forget about it), but your solution isn't great either. If the array is static, is ok to use the index because it will never change, but if the list you're rendering is dynamic (like coming from a state that could change), then you need to avoid using index as a key because that will produce rendering issues:

// Avoid this:
users.map(({ name }, index) => <li key={index}>{name}</li>);

// Have something like this instead
users.map(({ id, name }) => <li key={id}>{name}</li>);
Enter fullscreen mode Exit fullscreen mode

2. Modify the state value directly by assignment

Is true that you should avoid mutation, but you can use mutation before setting state if that makes your code easier to read:

const [state, setState] = useState({ values: [1, 2, 3], otherValue: "foo" });

// Instead of doing this:
setState({ ...state, values: [...values, 4] });

// You can do this:
state.values.push(4);
setState(state);
Enter fullscreen mode Exit fullscreen mode

I don't recommend doing this, but folks have to be aware that is possible. You can also use excellent tools such as immer and have the "best of both worlds":

import { produce } from "immer";

// ...

setState(
    produce(state, draft => {
        draft.values.push(4);
    }),
);
Enter fullscreen mode Exit fullscreen mode

You write code as if you were doing a mutation, but immer uses proxies to generate an immutable copy of the original object with the new state on it ^_^

3. Bind the state value directly to the value property of the input

This one, similarly to the first one, you get an error on dev from React. Not much to add other than saying that you had a typo on the example, there's no value property in the event of input, that's actually in the currentTarget property:

export const App = () => {
    const [count, setCount] = useState(0);
    const change = ({ currentTarget }) => setCount(currentTarget.value);

    return <input type="text" value={count} onChange={change} />;
};
Enter fullscreen mode Exit fullscreen mode

Also for this particular use case, you could have a custom hook that would make interacting with input states way easier:

const useInput = initialValue => {
    const [value, setValue] = useState(initialValue);
    const onChange = useCallback(
        ({ currentTarget }) => setValue(currentTarget.value),
        [],
    );

    return { value, onChange };
};

// And then in your component:
export const App = () => {
    const inputProps = useInput(0);

    return <input type="text" {...inputProps} />;
};
Enter fullscreen mode Exit fullscreen mode

4. Use state directly after executing setState

This has 2 issues:

  1. You're using this.setState, clearly an old class component, we should be moving away from those in 2022.
  2. The correct solution for this is not to use the callback, but actually to use an intermediary constant:
// With hooks
const newName = "lukeshiru";
console.log(`Updating name to ${name}`);
setName(newName);

// With the vintage class approach
const name = "lukeshiru";
console.log(`Updating name to ${name}`);
this.setState({ name });
Enter fullscreen mode Exit fullscreen mode

5. Infinite loop when using useState + useEffect

Another one that you'll get an error in dev mode from React. I recommend taking a look a the beta React docs about effects and how to use them correctly:

I'll skip over 6 because it has almost the same response as 5.

7. Incorrect use of boolean operators

The solution is to NOT USE short-circuit. It's a really bad practice in JS and it should be avoided. Instead use a conditional ternary:

// You can...
count ? <h1>Count is: {count}</h1> : undefined;

// Or...
count > 0 ? <h1>Count is: {count}</h1> : undefined;

// Or to avoid layout shits, you could...
<h1>{count > 0 ? `Count is: ${count}` : undefined}</h1>;
Enter fullscreen mode Exit fullscreen mode

One other approach I've seen is to have a super small util function when, and then use that for optional renders:

const when = (value, render) => (value ? render() : undefined);

// And then same examples as above:
when(count, () => <h1>Count is: {count}</h1>);
when(count > 0, () => <h1>Count is: {count}</h1>);
<h1>{when(count, () => `Count is: ${count}`)}</h1>;
Enter fullscreen mode Exit fullscreen mode

Either way, the important thing here is to avoid short-circuiting which is really, really bad.

8. The component parameter type is not defined

I love TS, and I wouldn't say this is a "common React mistake", it's just a TypeScript mistake. Also your "vanilla JS" solution is far from ideal because you're adding "runtime type checks". We can have TS like types in vanilla JS, by just using good JSDocs. Using your 2 examples as a base:

/**
 * @typedef AppProps
 * @property {string} [value]
 */

/** @param {import("react").PropsWithChildren<AppProps>} props */
const App = ({ value = "", children }) => {
    //...
};

/**
 * @typedef UserInfoProps
 * @property {string} name
 * @property {number} age
 */

/** @param {UserInfoProps} props */
const UserInfo = props => {
    return (
        <div>
            {props.name} : {props.age}
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

9. Passing Strings as Values to Components

I think this particular tip of yours is not that clear. Is completely valid to pass string as values to components, the problem comes from that component expecting other types instead. This can be solved by using solutions as the above mentioned (JSDocs or TypeScript typing), so if the consumer of your component accidentally passes the wrong type, they get a warning at dev time.

10. There is no component name that starts with a capital letter

Again this should be catched automatically by your editor or React at dev time. One thing we could add is that your components can definitely be properties of an object that starts with lower case:

<componentName /> // This will fail
<objectWithComponents.ComponentName /> // This will work just fine
Enter fullscreen mode Exit fullscreen mode

11. Incorrect event binding for element

The actual real world solution to this problem is simply to not use class components. Function components don't rely on inconsistent and flaky stuff like this, so they will work as expected without any extra work (such as .bind(this)). So the actual tip here is: Avoid class. I have articles written about why you don't need class and why using this is bad, if you're interested.


Overall the list is kinda ok, as I mentioned at the beginning I just wanted to add some missing information and correct some issues that needed to be addressed so if this is seen by someone with less experience, they have a more complete picture.

Cheers!