Simple function components are great for simple needs, but sometimes we need more. React supports creating components through the JavaScript class syntax as well. Here’s the Button component (in Example 1) written with the class syntax:
// Example 9 - Creating components using JavaScript classes
// https://jscomplete.com/repl?j=ryjk0iKHb
class Button extends React.Component {
render() {
return <button>{this.props.label}</button>;
}
}
// Use it (same syntax)
ReactDOM.render(<Button label="Save" />, mountNode);
The class syntax is simple. Define a class that extends React.Component (another top-level React API thing that you need to learn). The class defines a single instance function render(), and that render function returns the virtual DOM element. Every time we use the Button class-based component above (for example, by doing <Button ... />), React will instantiate an object from this class-based component and use that object to render a DOM element in the DOM tree.
This is the reason why we used this.props.label inside the JSX in the rendered output above. Because every element rendered through a class component gets a special instance property called props that holds all values passed to that element when it was created.
Since we have an instance associated with a single use of the component, we can customize that instance as we wish. We can, for example, customize it after it gets constructed by using the regular JavaScript constructor function:
// Example 10 - Customizing a component instance
// https://jscomplete.com/repl?j=rko7RsKS-
class Button extends React.Component {
constructor(props) {
super(props);
this.id = Date.now();
}
render() {
return <button id={this.id}>{this.props.label}</button>;
}
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);
We can also define class functions and use them anywhere we wish, including inside the returned JSX output:
// Example 11 — Using class properties
// https://jscomplete.com/repl?j=H1YDCoFSb
class Button extends React.Component {
clickCounter = 0;
handleClick = () => {
console.log(`Clicked: ${++this.clickCounter}`);
};
render() {
return (
<button id={this.id} onClick={this.handleClick}>
{this.props.label}
</button>
);
}
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);
Note a few things about Example 11 above:
- The
handleClickfunction is written using the new proposed class-field syntax in JavaScript. This is still at stage-2, but for many reasons it’s the best option to access the component mounted instance (thanks to arrow functions). But, you need to use a compiler like Babel configured to understand stage-2 (or the class-field syntax) to get the code above to work. The jsComplete REPL has that pre-configured. - We’ve also defined the
clickCounterinstance variables using the same class-field syntax. This allows us to skip using a class constructor call altogether. - When we specified the
handleClickfunction as the value of the specialonClickReact attribute, we did not call it. We passed in the reference to thehandleClickfunction. Calling the function on that level is one of the most common mistakes when working with React.
// Wrong:
onClick={this.handleClick()}
// Right:
onClick={this.handleClick}