Isaac Godwin Udofia
2 Nov 2022
•
6 min read
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
);
}
}
export default App;
But it doesn’t. React throws up an error with a message that says “Adjacent JSX elements must be wrapped in an enclosing tag“. This is because you cannot return multiple elements from a React component without wrapping them in something.
Failed to compile error message
Before React 16, the common way to solve the problem was to enclose the adjacent JSX elements inside a wrapper tag, usually a div.
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;
And it totally works. But many people do not like this solution because it adds extra markup to the output of the component which is undesirable in many cases.
So the React developers made it possible to return an array of elements in React 16, which enabled developers to skip the wrapper tag:
import React, { Component } from "react";
class App extends Component {
render() {
return [
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
</header>,
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
];
}
}
export default App;
React 16.2 introduced another way to solve this problem: using Fragments. Fragments let you group a list of children without adding extra nodes to the DOM.
Here’s an example of how that works:
import React, { Component } from "react";
class App extends Component {
render() {
return (
<React.Fragment>
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</React.Fragment>
);
}
}
export default App;
If you check out the rendered component using your browser’s dev tools, you will see that there no extra node was inserted into the DOM.
Console view using Fragments
This is my preferred way of rendering multiple elements from a React component because, according to Dan Abramov, it’s faster and uses less memory than container divs. It also makes it easier to work with CSS layout techniques such as Grid and Flexbox where having extra markup can have an impact on the layout.
In JSX, rendering a component that begins with a lowercase letter compiles down to React.createElement('component'), the equivalent of an HTML element. For example, let’s say you have a button component that returns a simple button element, this will not work as expected.
import React, { Component } from "react";
import ReactDOM from "react-dom";
class button extends Component {
render() {
return <button className="App-button">Yo!</button>;
}
}
ReactDOM.render(<button />, document.getElementById("root"));
Instead of rendering your component, React ignores it and renders a vanilla
If your component name is not a valid HTML tag, such as
Console error when name not capitalized
This problem is easily avoided by beginning all component names with a capital letter. Here’s an example that provides the correct output:
import React, { Component } from "react";
import ReactDOM from "react-dom";
class Button extends Component {
render() {
return <button className="App-button">Yo!</button>;
}
}
ReactDOM.render(<Button />, document.getElementById("root"));
Name correctly capitalized
import React, { Component } from "react";
class App extends Component {
sayName(name) {
return name;
}
render() {
return `Hello ${this.sayName("Ayo")}`;
}
}
export default App;
This above code will work just fine because React makes sure this refers to the instance of the class.
However, when you reference a class method from an event handler, such as onClick, the class methods lose their this bindings so you cannot this.state, this.props or this.setState() unless you re-bind them.
For example, the code sample below will not work as expected. When you try to update the form, you get an error with a message that says “this is undefined”.
class App extends Component {
constructor(props) {
super(props);
this.state = {
age: ''
};
}
updateAge(event) {
this.setState({
age: event.target.value
});
}
render() {
return (
<form>
<input onChange={this.updateAge} value={this.state.age} />
</form>
)
}
}
Type error
There are quite few approaches you can take to solve this problem. One way is to bind wrap the methods in an arrow function like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
age: ""
};
}
updateAge(event) {
this.setState({
age: event.target.value
});
}
render() {
return (
<form>
<input
onChange={event => this.updateAge(event)}
value={this.state.age}
/>
</form>
);
}
}
Or you can just bind them in the constructor function as shown below:
class App extends Component {
constructor(props) {
super(props);
this.state = {
age: ""
};
this.updateAge = this.updateAge.bind(this);
}
updateAge(event) {
this.setState({
age: event.target.value
});
}
render() {
return (
<form>
<input onChange={this.updateAge} value={this.state.age} />
</form>
);
}
}
A more elegant solution is to utilize the class property syntax and just use an arrow function for the methods. At the time of writing, the class property syntax is not yet part of the official ECMAScript spec yet (stage-3) so you’ll need to utilize Babel’s class properties transform plugin in your project.
If you’re using create-react-app to bootstrap your projects, it already has the class properties transform plugin enabled, so the following should work just fine.
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
age: ""
};
}
updateAge = event => {
this.setState({
age: event.target.value
});
};
render() {
return (
<form>
<input onChange={this.updateAge} value={this.state.age} />
</form>
);
}
}
export default App;
The React docs describes this concept in greater detail:
Think of setState() as a request rather than an immediate command to update the component. For better-perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Here’s an example that illustrates this:
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
checked: false
};
}
updateCheckbox = event => {
this.setState({
checked: !this.state.checked
});
console.log(this.state);
};
render() {
return (
<form>
<label>
Checkbox:
<input
onChange={this.updateCheckbox}
type="checkbox"
value={this.state.checked}
/>
</label>
</form>
);
}
}
export default App;
Accessing state
As you can see from the gif above, logging the state object immediately after the call to setState() returns the previous state instead of the updated state. This is because, by the time the console.log() statement is invoked, setState() hasn’t done its job yet.
If you need to access the state object immediately after updating the state, you can pass in a callback function to setState. The callback is guaranteed to fire after the update has been applied so you’ll always get the latest state object.
this.setState(
{
checked: !this.state.checked
},
() => {
console.log(this.state);
}
);
You can also use the componentDidUpdate lifecycle hook which is also certain to be invoked immediately after updating occurs.
If you need to set the state based on the previous state and props, you can use the following pattern:
this.setState((previousState, currentProps) => {
return { ...previousState, name: currentProps.name };
});
Here, setState receives the previousState and currentProps as arguments so you can use them to construct the next state. The return value of this function is merged with the existing state to form the new state.
JSX, on the other hand, requires that all elements must be closed, either with the self-closing format or with a corresponding closing tag. This also applies to React components:
Not closing element tags
Isaac Godwin Udofia
See other articles by Isaac
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!