We have updated the content of our program. To access the current Software Engineering curriculum visit curriculum.turing.edu.
Legacy Class Components in React
Prework
Understanding class components requires some background knowledge about classes in JavaScript. Please complete the following pre-work before the lesson.
Learning Goals
By the end of this lesson, you will be able to:
- explain the differences between class components and functional components
- create class components
- convert class based components to functional components
Vocabulary
this
- a keyword with a value that changes depending on the context in which it’s usedclass
- a special function which acts as as a template for creating objectsclass component
- a React component that inherits properties and methods from aComponent
class.
Warm Up
In small groups, answer the following questions. Make sure everyone has an opportunity to share and feel free to share screens.
- What do you know about JS classes?
- How do you create a class in JS?
- How do you create an object instance using a class?
Getting Started
We’re going to be using this ideabox repo today. Before going any further please do the following:
- Clone the repo to your machine
- Switch to the
refactor-classes-to-hooks
branch - Run
npm install
in the repo - Put a thumbs up in the participants panel of zoom once you’re done!
Background
React has been around since 2013 and that means it has gone through many iterations. Originally, if a developer wanted to utilize state and utilize a component’s lifecycle then they had to use a class component. More recently, React introduced Hooks. Hooks allow developers to use state and a component’s lifecycle in a functional component. This pattern is encouraged as common practice moving forward.
However, enterprise and company software moves at a much slower pace than the React ecosystem. This means that there are still many code bases out there that are using class components. You might find yourself in a job which uses class components! You may also find yourself tasked with the job of refactoring legacy class components into functional components.
Compare and Contrast
Both functional and class components share some common features:
- they both render JSX in some way
- they both can have props
- the both can have state
- they both have a way to call functions at different stages of a component’s life-cycle. (for example,
useEffect
)
The main differences are:
- things work differently under the hood
- the syntax is different!
React Class Components
What are class components anyway?
Class components, or class-based components, are just JavaScript classes that act as independent and reusable bits of code which allow you to render content to the DOM. A class component inherits methods and properties from a parent class which React provides, using the extend
keyword. We’ll take a look at more of these inherited properties and methods later. For now, let’s take a look at an example of a class component, which has a render
method and renders content to the DOM - similar to how a functional component renders content with the return
keyword.
import { Component } from 'react';
export default class App extends Component {
render() {
return (
<div>
Hello, World!
</div>
)
}
}
Tip
Render should only return JSX. Avoid doing anything else in this method - there should be no side-effects. This isn’t a good place to update state or run any operations.
State in Class Components
Instead of using hooks to define and update state, class components utilize class properties and a class method. To utilize the parent class’ properties and methods, we must call the super
function. This will give us access to a method called setState
, which we can use to update our state. After that, we can define our state using the this
keyword. Note: state must be an object, but you can have other data types inside of it.
You can access state in your render method by referencing this
.
import { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
name: 'Bill',
}
}
render() {
return {
<h1>Hello, {this.state.name}</h1>
}
}
}
Props in Class Components
Props probably look similar to what you’re used to in functional components. How we use them within our components must change a little bit, though.
You can pass props to a component.
<UserComponent email="bill@gmail.com" />
To access props in a class component, lean on your good friend this
:
import { Component } from 'react';
class UserComponent extends Component {
render() {
return <h1>Email: { this.props.email }</h1>;
}
}
Event Handlers
Event handlers may look a bit different in class based components, but only because we have to utilize this
to call methods from our class.
For example:
import { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
description: '',
title: ''
}
}
handleChange = event => {
this.setState({ [event.target.name ]: event.target.value });
}
render() {
return (
<form>
<input
type='text'
placeholder='Title'
name='title'
value={this.state.title}
onChange={event => this.handleChange(event)}
/>
<input
type='text'
placeholder='Description'
name='description'
value={this.state.description}
onChange={event => this.handleChange(event)}
/>
<button>SUBMIT</button>
</form>
)
}
For your consideration:
It’s common to define event handlers with arrow functions. Why do you think that is?
Your turn!
Use your new class component knowledge to begin refactoring the Form in the Ideabox repository into a functional component. Work with a partner to do the following:
- import the
useState
hook from the React library - refactor the class based component into a functional component of the same name
- remove the old constructor, and replace it with calls to the useState hook (make sure to name both your new piece of state, and the function that will update it)
- update your render method into a return statement to make use of your new state and setter methods
- NOTE: The current form makes use of a handleChange method. You don’t necessarily need this, but you can use it with some appropriate refactoring.
Here’s one way you could do it, don’t look until you’re done!
import { useState } from 'react';
import './Form.css';
function Form(props) {
const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const submitIdea = event => {
event.preventDefault();
const newIdea = {
id: Date.now(),
title,
description
}
props.addIdea(newIdea);
clearInputs();
}
const clearInputs = () => {
setTitle('')
setDescription('')
}
return (
<form>
<input
type='text'
placeholder='Title'
name='title'
value={title}
onChange={event => setTitle(event.target.value)}
/>
<input
type='text'
placeholder='Description'
name='description'
value={description}
onChange={event => setDescription(event.target.value)}
/>
<button onClick={submitIdea}>SUBMIT</button>
</form>
)
}
export default Form;
Lifecycle Methods
In functional components, you can call functions in response to the lifecycle of a component using the useEffect
hook.
Review the following questions before diving into Lifecycle methods for class components:
- How do you call a function when the component mounts using
useEffect
? - How do you call a function when state changes using
useEffect
?
Class components inherit several lifecycle methods that we can use. Two important methods are:
componentDidMount
- this method is invoked immediately after a component is mounted. React invokes this for us - we simply define the method in our class component. Note: the action of defining componentDidMount
is actually something fancy called method overwriting
, which allows a child class to make a specific implementation of the parent class’ method.
componentDidUpdate()
- this method is invoked after updating occurs. It is not called on the initial render. React invokes this for us, as well. This is a good place for network requests and updating state, but you’ll need to compare the current props to the previous props. Find more information here to learn about using a conditional to update state in componentDidUpdate
so that you don’t end up with an infinite loop.
Your turn!
With a partner, refactor the App component in our ideabox to be a functional component. Make sure you do the following:
- import
useState
anduseEffect
from the react library - convert the class based component into a functional component
- use the
useState
hook to recreate the parts of state that were previously in a constructor function - use the
useEffect
hook to recreate the functionality present in the lifecycle method - update the return statement using your new state values
If you’ve done everything right, the ideabox should still work exactly as it did before!
Here’s one way you could do it, don’t look until you’re done!
import { useState, useEffect } from 'react';
import Ideas from './Ideas';
import Form from './Form';
import './App.css';
function App() {
const [ideas, setIdeas] = useState([])
useEffect(() => {
document.title = `Ideabox (${ideas.length})`
})
const addIdea = (newIdea) => {
setIdeas([...ideas, newIdea]);
}
const deleteIdea = (id) => {
const filteredIdeas = ideas.filter(idea => idea.id !== id);
setIdeas(filteredIdeas);
}
return(
<main className='App'>
<h1>IdeaBox</h1>
<Form addIdea={addIdea} />
<Ideas ideas={ideas} deleteIdea={deleteIdea} />
</main>
)
}
export default App;
Checks for Understanding
- What’s the difference between class components and functional components in React?
- What is
componentDidMount
used for? - How do you work with state in a class component?
- Why is it useful to know about class components?