Up until a few months ago class components were cool, but the arrival of React Hooks put an end to that. Why are class components not a great thing anymore? In this post we are going to enumerate the main pitfalls when working with class components.
In a class component the value of this inside a function depends upon how that function is invoked (when the event handler occurs and the handler is invoked, the this value fallsback to default binding and is set to to undefined, as class declarations and prototype methods run in strict mode).
More on this: https://medium.freecodecamp.org/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb
This means that event handlers that are triggered outside the class component it self will loose the this reference to the class were they belong.
If we try to run this sample, when clicking on the the Change Text button we will get an error:
Cannot read property 'setState' of undefined
export class MyHelloComponent extends React.Component {
constructor(props) {
super(props);
this.state = { myText: "hello" };
}
onChangeText() {
this.setState({ myText: "world" });
}
render() {
return (
<>
<h3>{this.state.myText}</h3>
<button onClick={this.onChangeText}>Change text</button>
</>
);
}
}
For every single event handler function, we have to remember to bind this to the class component (you can use explicit hard binding or experimental class method fat arrow).
export class MyHelloComponent extends React.Component {
constructor(props) {
super(props);
this.state = { myText: "hello" };
+ this.onChangeText = this.onChangeText.bind(this);
}
onChangeText() {
this.setState({ myText: "world" });
}
render() {
return (
<>
<h3>{this.state.myText}</h3>
<button onClick={this.onChangeText}>Change text</button>
</>
);
}
}
Live demo: https://codesandbox.io/s/71opm61rzq
This is a source of bugs, how many times have you forgetten to properly bind your event handler and bummer getting runtime error?
When defining state in a class component we have a single place to store everything, and on the other hand a single way to update the state.
What's the issue here? Is hard to separate concerns, it's hard to extract functionallity that could be reused.
In this case we have two unrelated topics living in the same class based state:
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { color: "teal", name: "John", lastname: "Doe" };
}
getFullname() {
return `${this.state.name} ${this.state.lastname}`;
}
render() {
return (
<div style={{ background: this.state.color }}>
<h3>{this.getFullname()}</h3>
</div>
)
}
}
Live demo: https://codesandbox.io/s/yp9v4yyr8x
Let's check how could we handle this using hooks, we could get two separate state instances for each concern:
import React from "react";
export const MyComponent = () => {
const [color, setColor] = React.useState("teal");
const [clientInfo, setClientInfo] = React.useState({name: 'John', lastname: 'Doe'});
const getFullname = () => {
return `${clientInfo.name} ${clientInfo.lastname}`;
}
return (
<div style={{ background: color }}>
<h3>{getFullname()}</h3>
</div>
);
};
Live demo: https://codesandbox.io/s/ppom543mj7
We could even go one step further and encapsulate the user info handling into a custom hook:
const useClientInfo = (name, lastname) => {
const [clientInfo, setClientInfo] = React.useState({
name,
lastname,
});
const getFullname = () => {
return `${clientInfo.name} ${clientInfo.lastname}`;
};
return {clientInfo, setClientInfo, getFullname}
}
export const MyComponent = () => {
const [color, setColor] = React.useState("teal");
const {getFullname} = useClientInfo('John', 'Doe');
return (
<div style={{ background: color }}>
<h3>{getFullname()}</h3>
</div>
);
};
Live demo: https://codesandbox.io/s/ly7wlq9mo9
When we use React class components, we got several events in the lifecycle (componentDidMount, ComponentDidUpdate, ComponentWillUnMount...),these events may handle related functionality, but we are forced to split that functionallity into separate methods. For instance:
Let's define a parent component:
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { visible: false };
}
render() {
return (
<>
{this.state.visible && <MyChildComponent />}
<button onClick={() => this.setState({ visible: !this.state.visible })}>
Toggle Child component visibility
</button>
</>
);
}
}
export class MyChildComponent extends React.Component {
constructor(props) {
super(props);
this.state = { name: "John", lastname: "Doe" };
}
componentDidMount() {
console.log("Hey Im mounting");
console.log(`${this.state.name} ${this.state.lastname}`);
}
componentDidUpdate() {
console.log("Just updating...");
console.log(`${this.state.name} ${this.state.lastname}`);
}
componentWillUnmount() {
console.log("bye bye, unmounting...");
}
render() {
return (
<div>
<h3>
{this.state.name} {this.state.lastname}
</h3>
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
/>
<input
value={this.state.lastname}
onChange={e => this.setState({ lastname: e.target.value })}
/>
</div>
);
}
}
Live Demo: https://codesandbox.io/s/oqyo3159jq
By using hooks we can group all this functionality in a single function:
const MyChildComponent = () => {
const [userInfo, setUserInfo] = React.useState({name: 'John', lastname: 'Doe'})
React.useEffect(() => {
console.log('called when the component is mounted and right after it gets updated');
return () => console.log('Clean up from the previous render before running effect next time ... ');
})
return (
<div>
<h3>
{userInfo.name} {userInfo.lastname}
</h3>
<input
value={userInfo.name}
onChange={e => setUserInfo({ ...userInfo, name: e.target.value })}
/>
<input
value={userInfo.lastname}
onChange={e => setUserInfo({ ...userInfo, lastname: e.target.value })}
/>
</div>
);
}
In this example the code defined under the useEffect is executed just when the component is mounted,and right after every render. On the other hand the clean up function is executed right after the component is mounted and right after each time the effect is executed.
Live Demo: https://codesandbox.io/s/5zllr3k09p
Uh? fine, but... what if I just want to execute a piece of code only when the component is just mounted and clean up when the component is unmounted We can play with React.UseEffect second parameter.
const MyChildComponent = () => {
const [userInfo, setUserInfo] = React.useState({name: 'John', lastname: 'Doe'})
React.useEffect(() => {
- console.log('called just when the component is mounted and when after it gets updated');
+ console.log('called just when the component is mounted');
- return () => console.log('Clean up from the previous render before running effect next time ... ');
+ return () => console.log('Clean up executed just when the component gets unmounted ... ');
- })
+ }, [])
return (
<div>
<h3>
{userInfo.name} {userInfo.lastname}
</h3>
<input
value={userInfo.name}
onChange={e => setUserInfo({ ...userInfo, name: e.target.value })}
/>
<input
value={userInfo.lastname}
onChange={e => setUserInfo({ ...userInfo, lastname: e.target.value })}
/>
</div>
);
}
Live Demo: https://codesandbox.io/s/q91qjql8r4
High Order Components are a great way to teach new tricks to components via composition, but:
Let's take a look at a minimal example: the following component will just greet the current user logged in:
import React from "react";
export const MyComponent = (props) => {
return (
<>
<h3>Hello: </h3>
</>
)
}
Now if we want to inject the user logged in we can create a HoC:
const withUserInfo = (ComponentToWrap) => (props) =>
<>
<ComponentToWrap {...props} user="John" />
</>
Let's make usage of this HoC in MyComponent
- export const MyComponent = (props) => {
+ const MyComponentInner = (props) => {
return (
<>
- <h3>Hello: </h3>
+ <h3>Hello: {props.user}</h3>
</>
)
}
+ export const MyComponent = withUserInfo(MyComponentInner)
Live demo: https://codesandbox.io/s/l7qznjjyw9
That's pretty powerful, but wouldn't it be easier just to say... I want to use this functionality? Let's find out how to implement this behavior using hooks:
import React from "react";
const useUserInfo = () => {
const [userInfo, setUserInfo] = React.useState('John');
return {userInfo, setUserInfo}
}
export const MyComponent = (props) => {
const {userInfo} = useUserInfo();
return (
<>
<h3>Hello: {userInfo}</h3>
</>
)
}
Live code demo: https://codesandbox.io/s/pwj6z446xq
Advantages:
When you start implementing a component, you probably want to keep it simple, and start implementing a functional component, let's get something easy, display a Hello message receiving from the parent component a userName property:
import React from "react";
export const MyComponent = () => {
return (
<>
<MyChildComponent userName="John"/>
</>
)
}
export const MyChildComponent = (props) => {
return (
<>
<h3>Hello: {props.userName}</h3>
</>
)
}
Live Demo: https://codesandbox.io/s/985wyr1olw
Now let's say that instead of getting this userName property from a parent component, we want to store it locally in our component state. A classic approach would involve refactoring the component to a class component base and totally revamping it (be careful not to mess it up with this.state, adding a constructor, a render method...), something like this:
export const MyComponent = () => {
return (
<>
- <MyChildComponent userName="John"/>
+ <MyChildComponent/>
</>
)
}
- export const MyChildComponent = (props) => {
+ export class MyChildComponent extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {userName: 'John'}
+ }
+
+ render() {
return (
<>
- <h3>Hello: {props.userName}</h3>
+ <h3>Hello: {this.state.userName}</h3>
</>
)
+ }
+ }
Live Demo: https://codesandbox.io/s/40l5k4q1o9
How could we make this refactor using hooks?
import React from "react";
export const MyComponent = () => {
return (
<>
- <MyChildComponent userName="John"/>
+ <MyChildComponent/>
</>
)
}
export const MyChildComponent = (props) => {
+ const [userName] = React.UseState('John');
return (
<>
- <h3>Hello: {props.userName}</h3>
+ <h3>Hello: {userName}</h3>
</>
)
}
Live Demo: https://codesandbox.io/s/jlkoxm6plw
Last but not least, in a larger project having to deal with a mix of function and class based components, it brings along several maintainability issues:
When new stuff gets into town, before jumping on the bandwagon, it's good practice to learn why we need to use all this cool aid and what problems it solves. I hope that this post helps you understand why hooks have generated so much hype in the community and have become a de facto standard. In next posts of these series we will start digging into hooks details, showing both their happy path plus edge cases based on real project usage, so stay tuned!
Hope you enjoyed this article, thanks for reading.
We are a team of Javascript experts. If you need coaching or consultancy services, don't hesitate to contact us.
C/ Pintor Martínez Cubells 5 Málaga (Spain)
info@lemoncode.net
+34 693 84 24 54
Copyright 2018 Basefactor. All Rights Reserved.