-
Notifications
You must be signed in to change notification settings - Fork 10
Lesson 2.1
This lesson will teach you:
- Converting stateful class components to function components using hooks
The goal for this lesson is to get experience working with React Class components and how to get the same functionality from a class component and a functional component using hooks.
The React application that you will be using is a simple number guessing game. The game is currently written using a mix of class components for components that have state and stateless functional components.
When you are done the application should have all of the same functionality, but the class components should be refactored to be function components that use the useState hook to manage their stateful data.
Classes in JavaScript resemble classes in traditional object-oriented languages, such as Java or C, but with some fundamental differences.
The class syntax in JavaScript is just a new way to use function constructors and prototypal inheritance.
Components can be written both using a function and a JavaScript class, though functions are much simpler and requires less code than the alternative.
However, both methods are still widely used, especially with older code bases so it’s still important to have an understanding of how to write components using JavaScript classes.
import React from 'react';
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>Welcome!</h1>
);
}
};
export default MyComponent;
Note: You may have see the term “syntactic sugar” used to describe classes. Syntactic sugar refers to a simplified or abstracted way of writing something that makes it easier to write or to understand, but doesn't actually do anything that you couldn't previously do.
React.Component is the base class for every class component. It defines a number of methods, lifecycle methods, class properties to use in your class component.
The Constructor Function
If you include a constructor in your class, it will run one time when an instance of the class is created. The constructor function is where you will bind event handler functions to the instance of the class and set up the local state for the instance.
After the constructor and the call to the super function, the constructor has two purposes - iinitialize the component instance's state, and it bind event handler methods to the component instance.
Initializing Local State
Each instance of a React component maintains its own state, and the constructor is where you initialize this state.
If you're going to use props in the constructor, you need to pass it to the superclass's constructor when you call super.
import {Component} from 'react';
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {};
}
...
}
export default MyComponent;
Event handlers are functions that run in response to events.
Binding makes the this keyword work. By binding an event handler to the component instance, you make it possible to share the function with other components while maintaining its link to the state of the instance with which it's bound.
class MyComponent extends React.Component{
constructor( props ){
super( props );
this.message = "hello";
this.handleClick = this.handleClick.bind(this);
}
handleClick(event){
console.log(this.message); // 'this' is undefined
}
render(){
return (
<button type="button" onClick={this.handleClick}>
Click Me
</button>
);
}
}
export default MyComponent;
The ‘this’ keyword in JavaScript points to the object is calling the function. It is how you reference a class’s properties and methods.
However, the ‘this’ keyword’s scope is lost and will be undefined when it is passed as a callback function in the onClick event. The event handler method handleClick is not bound to the class MyComponent anymore.
To fix this issue, we can use the bind function to bind it to MyComponent so it will always run within the context of MyComponent.
An alternative would be to use an arrow function instead:
<button type="button" onClick={(e) => this.handleClick(e)}>
This works because using an arrow function automatically binds ‘this’ to the enclosing function.
Further reading: FreeCodeCamp - Why We Need to Bind Event Handlers in Class Components in React
The first step is to fork the source repository in GitHub and then clone your forked repository to your local development system. There are instructions for forking a repository in GitHub here: https://docs.github.com/en/get-started/quickstart/fork-a-repo
The repository to fork is: https://github.com/Code-the-Dream-School/react-guessing-game-vite
Once you have it cloned locally make sure to replace any instance of yarn start with yarn dev or npm run dev as Vite uses dev for starting the development server.
If you are using npm, just replace
yarnwithnpmrun and execute the same commands:
npm install
npm run dev
In the current version of the application there is a mix of class components and stateless function components. Take some time to look over the structure of the application so that you understand how it is constructed.
The class components in the current version are NumberGuessingGame and GuessControl. These are the components that you will be converting to be function components with hooks. The existing function components don't need any changes.
If you want to attempt this on your own without a step by step walkthrough first then leave the section below collapsed.
Click the arrow to expand this section and see step by step instructions to convert to function components with hooks.
- Open the
GuessControl.jsfile. - Rename the current
GuessControlclass toGuessControlOldif you want to keep it a reference while converting the code. - Create a new function component called
GuessControlthat will take anonGuessprop. - Copy the return value from the render function in the class component to be the return value in the new function component. Remove any references to
this.since those will be replaced with new references to local variables or props passed in to the function component. - Create a new state variable named
currentGuesswith settersetCurrentGuessand default value of an empty string. Set thevalueproperty for the input element to refer to this state value. (Make sure to importuseState) - Create a
handleInputChangefunction within the component that updates thecurrentGuessstate value when the user changes the value in the input. Set theonChangeproperty for the input element to refer to this function. - Create a
onSubmitGuessfunction that calls theonGuessprop with thecurrentGuessvalue converted to a number and also resets thecurrentGuessto an empty string when it is called. Set theonClickproperty on the button to refer to this function. - If you still have the old class version around as
GuessControlOld, delete it.
- Open the
NumberGuessingGame.jsfile. - Rename the current
NumberGuessingGameclass toNumberGuessingGameOldif you want to keep it a reference while converting the code. - Create a new function component called
NumberGuessingGame. - Copy the logic and return value from the render function in the class component to be in the new function component. Remove any references to
this.since those will be replaced with new references. - Create 3 state variables and their setters for
numberToGuess,numberOfGuesses, andlatestGuessand initialize them to the same values from the class component version. (Make sure to importuseState) - Create a
handleGuessfunction that will be passed in to theGuessControlcomponent as theonGuessprop. This function should take the guess as an argument and set thelatestGuessstate with the guess (converted to a number using the Number function) and increment thenumberOfGuessesstate. - Create a
handleResetfunction within the component that resets all 3 of the state properties the same way the handleReset function from the class component reset them. Pass this function to theGameOvercomponent as theonResetprop. - Update all references from the class component that referred to
this.<something>to refer to the correct variable or function for the new function component. - If you still have the old class version around as
NumberGuessingGameOld, delete it.