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:
- When rendering the list, do not use the key
- Modify the state value directly by assignment
- Bind the state value directly to the value property of the input
- Use state directly after executing setState
- Infinite loop when using useState + useEffect
- Forgetting to clean up side effects in useEffect
- Incorrect use of boolean operators
- The component parameter type is not defined
- Passing Strings as Values to Components
- There is no component name that starts with a capital letter
- 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>);
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>);
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
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";
};
At this point, the editor will prompt a warning ⚠️:
Do not mutate state directly. Use setState().
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");
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} />;
}
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} />;
}
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
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!
});
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>;
}
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>;
}
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);
});
-
The second parameter is an empty array: The side effect function of
useEffect
is only triggered on mount and unmount.
useEffect(() => {
setCount(count + 1);
}, []);
- **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]);
- **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]);
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]);
Documentation
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>;
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>;
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>
);
};
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 }) => {
//...
};
- Without using TypeScript,
props
types can be defined usingpropTypes
;
const UserInfo = (props) => {
return (
<div>
{props.name} : {props.age}
</div>
);
};
UserInfo.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
};
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>
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>
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 {}
Solutions
Just change the first letter to uppercase:
class MyComponent extends React.component {}
Documentation
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>
);
}
}
When clicking the update
button, the console will report an error:
Cannot read properties of undefined (reading 'setState')
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);
}
- Use arrow functions
update = () => {
this.setState({ name: "Hello Chris1993!" });
};
- 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>
- 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>
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.
Top comments (0)