PLAY
VIDEO
READ TIME: 5
Article

Functional vs. Class-Based React

Author

/
Matt Eagle

TOPICS:

/Web / CMS / Replatforming
/Integrations & API
/Software & Product

Typically in the Javascript world, there are more frameworks than there are projects which use those frameworks. Over time, certain frameworks rise up and become the current stars. It’s clear that React is completely dominating our industry and is now the de facto standard for reactive (pun intended) user interfaces.

But where did React come from and why is it so popular?

Luckily, through our close partnership with Facebook, we’ve been around to see the evolution of React from its early stages as an internal library at Facebook all the way to its widespread success today. As Facebook expanded its usage of Javascript across their applications and websites, they evaluated a number of existing open-source frameworks while determining the best route forward for how they structure their Javascript. The common factor they noticed was that frameworks, whether using the MVC (model, view, controller), MVVM (model, view, view model), or MVW (model, view, whatever) patterns, all tended to have the “model” aspect in common. They would use observer patterns to watch the models for changes and mutate the views accordingly. That’s where the difficult part comes into play. Which part of the view do you mutate? What dependencies can cause side effects and mismatched states? In a perfect world, we would be able to throw away the old view and replace it with a brand new view every single time. But that would be prohibitively expensive to do in a browser using Javascript, right? And that’s where React’s virtual DOM was born. By using a virtual representation of the DOM, we really can throw away the old view and create a new one every single time. And a simple comparison between the virtual DOM and the actual DOM shows us exactly what needs to be mutated.

When React was open sourced, it became clear very quickly that the engineers at Facebook who architected React were onto something. Having a library handle views this way can simplify our frontend code and allow us to produce much more declarative components. And thus it rose to the top as the most popular front-end framework.

But as with the first iteration of anything, it came with its quirks.

Class-Based Components

Building components using Javascript classes did a great job of modularizing components and embodying the object-oriented way of doing things. However, it did produce a bit of a learning curve and had some “gotchas” which made React a bit less desirable once an application grew in complexity.

Let’s start with lifecycle methods.

Class-based components have a number of methods, referred to as “lifecycle methods.” These would be inherited from the base React.Component class and would be the preferred way of executing logic and responding to events within the lifecycle of a component.

constructor()

For anyone familiar with one or two object-oriented languages, you may recognize this method. This is the first method that gets called when a class gets instantiated. 

componentDidMount()

Now this one is React specific. If you want to run any code once the component is...well, mounted, but not as early as the constructor, this is the method you would use.

render()

The most important of methods, this is where you return the html your component should produce. This method gets run a lot, even if it results in no actual changes to the DOM. This is why the virtual DOM is so important, and this is the key to the “throw away the old and recreate the new” methodology mentioned above.

componentDidUpdate()

This is where you can respond to updates in the component.

The point I’m making here is that the syntax is kind of verbose. And structuring your code cleanly can be difficult when you need to group everything together into a small number of lifecycle methods.

“But Matt,” you may be saying, “why can’t we just have the lifecycle methods call other methods as a means of splitting up the code and making it more readable?” You absolutely can take that approach! But with that comes the issue of context. In a class, you may need to access a property of that class, such as the state. Calling “this.state” is easy enough, but as you nest method calls, the context can change, and suddenly “this” is no longer referring to what you think it’s referring to. The typical approach for addressing the context issue was through binding. Bind the context (“this”) to your method, and it will have an idea of what “this” is referring to. But that can get messy, confusing, or downright ugly.

On the subject of context, there’s another common pitfall that many developers have faced when working with class-based components. There’s a concept called “prop-drilling”. This is when you have nested components that pass data through each other unnecessarily. Let’s say we have 3 components, each nested within the previous component. 

ComponentA → ComponentB → ComponentC

If ComponentC needs some data that ComponentA has, ComponentA needs to pass it to ComponentB, and ComponentB needs to pass it to ComponentC. ComponentB doesn’t need that data, so it shouldn’t care or even be aware of that data.

Libraries such as Redux can help solve this problem but tend to be overkill in small cases such as this.

Along Comes Functional Components

Now I don’t mean to disparage class-based components. After all, they were all React was, and React was still a fantastic framework even before functional components came along. But it’s important to note some struggles we had faced to be able to understand how functional components can solve them.

Here’s a comparison between a basic class-based component and a basic functional component.

Class-Based Component

class MyComponent extends React.Component { render() { return <div>This is a class-based component</div>; }}

Functional Component

function MyComponent() { return <div>This is a functional component</div>;}

Now with a component this simple, it’s hard to recognize a major difference between these approaches. So let’s look at a more complicated example from an actual project.

Class-Based

import styles from "../styles/Modules/Modal.module.scss";import anime from "animejs";class Modal extends Component { constructor(props) { super(props); this.setModalRef = this.setModalRef.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); this.modalOverlayRef = react.createRef(); } componentDidMount() { document.addEventListener("mousedown", this.handleClickOutside); document.addEventListener("touchstart", this.handleClickOutside); anime({ targets: this.modalOverlayRef.current, opacity: [0, 1], duration: 250, easing: "easeInQuad" }); } componentWillUnmount() { document.removeEventListener("mousedown", this.handleClickOutside); document.removeEventListener("touchstart", this.handleClickOutside); } setModalRef(node) { this.modalRef = node; } handleClickOutside(event) { const { onDismiss } = this.props; if (this.modalRef && !this.modalRef.contains(event.target)) { onDismiss(); } } render() { const { children } = this.props; return ( <div className={styles.overlay} ref={this.modalOverlayRef}> <div ref={this.setModalRef} className={styles.modal}> {children} </div> </div> ); }}export default Modal;

Functional

import styles from "../styles/Modules/Modal.module.scss";import anime from "animejs";import { useEffect, useRef } from "react";function Modal(props) { const modalOverlayRef = useRef(); const modalRef = useRef(); useEffect(() => { document.addEventListener("mousedown", handleClickOutside); document.addEventListener("touchstart", handleClickOutside); anime({ targets: modalOverlayRef.current, opacity: [0, 1], duration: 250, easing: "easeInQuad", }); return function cleanup() { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("touchstart", handleClickOutside); }; }, []); function handleClickOutside(event) { if (modalRef && !modalRef.current.contains(event.target)) { props.onDismiss(); } } return ( <div className={styles.overlay} ref={modalOverlayRef}> <div ref={modalRef} className={styles.modal}> {props.children} </div> </div> );}export default Modal;

Right away, you may notice that not only are there much fewer lines of code, it’s also a lot easier to read. In the class-based example shown above, you’ll see we had to do some binding to ensure that a few particular methods would have access to the context of the class. This wasn’t necessary to do in the functional version due to all the code in a functional component being within the same scope.

Hooks

Let’s talk about hooks. Hooks are a means of accomplishing the same sorts of things we would accomplish with lifecycle methods, but hooks actually enable additional functionality on top of that.

You’ll notice in the functional example presented above, we have a block of code wrapped in “useEffect()”. This is one area where we’re using a hook. Let’s break these down.

useEffect

This hook is where we react to things. And yes, once again that pun is very much intended. Let’s say we want to run some code when a certain prop changes, or when a certain part of the component’s state changes. With this hook, we just say what we want to react to and how we want to react to it. It takes 2 parameters: a function, and an array of things to watch for changes.

useContext

Remember that whole prop drilling situation? This can be easily solved with the Context API. You wrap a higher-level component with a context provider, and any children components, no matter how deeply nested, can leverage that context using this hook. Admittedly, Context was introduced in class-based components, but the functional/hook API is a lot cleaner.

useState

This hook is most likely the one you’ll use most often. No longer are you calling this.setState({ something: “new value” });. Now you’re able to handle state in a much more elegant way.

const [clicks, setClicks] = useState(0);return ( <> <div>Clicks: {clicks}</div> <button onClick={() => setClicks(clicks + 1)}>Click me</button> </>);

useReducer

This is an alternative to useState, and will be familiar to anyone who has worked with Redux. Essentially, it allows you to route state updates through functions where you can modify state in more complex ways. For example, if you want to increment a number using useState, you would do something like this:

const [clicks, setClicks] = useState(0);return ( <button onClick={() => setClicks(clicks + 1)}>Click me</button>);

With useReducer you can just have the button tell the state to increment, without the button caring about the previous state.

const initialState = {clicks: 0};function reducer(state, action) { switch (action.type) { case 'increment': return {clicks: state.clicks + 1}; default: throw new Error(); }}const [state, dispatch] = useReducer(reducer, initialState);return ( <button onClick={() => dispatch({type: 'increment'})}>Click me</button>);

At first, this looks a little bit more complicated. But I’ll break it down. The reducer can live in its own file where reducer logic can be isolated. With an example as simple as this, it can be hard to visualize a good use case for useReducer. But as an application becomes more complex, you may want to make the code more DRY (don’t repeat yourself) by throwing common logic into a reducer (especially as state logic begins to grow more complex than just a simple increment action). Then the state logic no longer needs to live within the components themselves.

useRef

This hook is a little more nuanced and flexible, but essentially it allows you to store a [mutable] value that can persist throughout the lifecycle of the component, regardless of re-renders.

The most common use for useRef is to refer to an element in the DOM. This way, you can add event listeners on elements, or read the values of input fields without any sort of onChange or onKeyUp logic.

useMemo

This is a performance shortcut. If you have an expensive operation that you don’t want to re-run every time a render happens, this hook can allow you to only recompute when certain dependencies change. Think of it as a mashup between useState and useEffect, but in a single hook.

Comparing Approaches

While class-based components and functional components differ in their approaches to solving problems, there is still parity in the types of problems you may want to solve. Let’s look at lifecycle methods. We mentioned lifecycle methods above. Now let’s look at how to implement the same functionality in a functional way.

componentDidMount

This one is pretty straightforward. In the dependency array for the useEffect hook, we just don’t pass through any dependencies.

Class Based

componentDidMount() { // do some setup work}

Functional

useEffect(() => { // do some setup work}, []);

componentDidUpdate

This one allows us to be much more declarative about what we want to respond to rather than blindly responding to all updates.

Class-Based

componentDidUpdate() { // do something when props or state updates}

Functional

useEffect(() => { // do something when a specific prop changes}, [someSpecificProp]);

componentWillUnmount

I’ll be honest, this one isn’t intuitive at first. The first time I had to use this, I wasn’t able to guess what the correct approach would be. But upon some research, I found that there’s actually a pretty clever way to do this. In your useEffect where you do your setup logic, you return a function for running your teardown logic. I find it pretty clever that the setup logic and teardown logic is able to all be in the same place.

Class-Based

componentWillUnmount() { // teardown logic}

Functional

useEffect(() => { // setup logic return () => { // teardown logic }}, []);

Summary

Through both the real-world example shown above and through the specific comparisons, we’ve covered our bases and shown the value of using functional components with React hooks. Functional components utilizing React hooks allow us to write cleaner, more declarative code; and we can also write code with fewer side effects and better performance. With React’s popularity, I hope the cleaner syntax of functional components lowers the barrier to entry. I look forward to seeing developers who want to learn more about frontend dive in, and I look forward to seeing people enter the exciting world of development for the first time. Hopefully, React’s popularity will be a part of that.

React is the current standard for frontend development, and functional components are the predominant paradigm, but just wait until we see what comes next...

How Brands Can Leverage AI to Enhance Customer Experience

Reframing the Web: Your Site Isn’t a Destination. It’s an Experience Platform.

Team Highlight: Cassie Tangney

Outlandish Optimism at PRPL | International Design Day 2025

Unlocking the Power of Content Organization: Insights for Museums and Beyond

Team Highlight: Jeff Katipunan

PRPL and Museums: A Partnership Built on Creativity and Impact

Team Highlight: Maria Szlosek

Team Highlight: Vic Cao

Team Highlight: Jenn Hunter

Driving Innovation in Design Management: Strategies for Fostering Creativity

Team Highlight: Anibal Cruz

Mastering Leadership with SLANT: A Principle for Effective Engagement By Bobby Jones

Breaking Boundaries With Augmented Reality

Navigating the Replatforming Process: A Comprehensive Guide for a Smooth Transition

Team Highlight: April Domingo

Team Highlight: Chris Sell

Agency Partnerships: What’s In It For You

Upgrading from Drupal 9 to Drupal 10: A Step-By-Step Guide

Team Highlight: Francesca Parker

Navigating the Replatforming Process: A Comprehensive Guide for a Smooth Transition

Purple, Rock, Scissors Wins Web3 Awards, Including Two Golds

Purple, Rock, Scissors Wins Six ADDY® Awards, including Best of Show

Transforming User Experience through Augmented Reality

Purple, Rock, Scissors Tops Orlando Business Journal's Book of Lists

Transforming the Patient Experience: Purple, Rock, Scissors' Collaborative Partnership With AdventHealth

State of the Art: How AI is Influencing Design

8 Ways to Increase Museum Visits with Digital Experiences

Functional vs. Class-Based React

10 Components Your Museum Website Needs to Succeed

Staff Augmentation: Utilizing Digital Creative Agencies for Your Business Needs

Technical Insights: Integrating Your Health App with Epic EHR/EMR Systems

Remote work? Hybrid ? We prefer flexible.

Optimization Primed: How AI is Making Businesses More Efficient

Fit to Print: How AI is Improving Business Writing

Best Museum Websites to Inspire Your Next Redesign

PRPL Summit Weekend

Cam's Tips for Working Remote

Google Update: Core Web Vitals

CX Essentials: What the Customer Experience Can Teach You About Being a Better Brand

PRPL Playlist: Creative as Folk

Google Update: Mobile-First-Indexing for the Whole Web, and What it Means for Your Website & Business

Student Design Society: Apply for Our Summer 2019 Internship

PRPL Playlist: Hits from the Crypt

PRPL Playlist: Back 2 School

PRPL Playlist: 1nn0vate

Scientific Storytelling at the Field Museum

What To Do When a Project Is on Hold

Hard Rock + PRPL

What Organization Means to a Creative Person

PRPL Playlist: Artificial Inchilligence

PRPL Playlist: BBQ Bangers

Vote PRPL in SXSW's PanelPicker!

A Comprehensive Beginner’s Guide to Automating with Ansible

Launched: Lux Capital

We've Moved to Medium!

Spotlight: Chris Reath

Content Before Design: The Fall of Lorem Ipsum

PRPL Playlist

3-Pointers: Project Manager Advice

Spotlight: Rad Kalaf

Launched: LMG

What the Internet's New TLS 1.2 Encryption Standards Mean for You. Yes, You.

Let’s Talk About SaaS, Baby.

NASA’s IDEAS Project Update: Phase 1

Thinking and Designing with Components

3-Pointers: Stellar Style Guides

Launched: Stetson University

Put on Those Beer Goggles!

Polymer Web Components Tutorial

Battle of the Jams

What’s My Title, Again? A Case for Generalists in the Web Industry

3-Pointers: Boost Tweet Engagement

Myers Briggs for Businesses

More iBeacons, Please!

Launched: 3Cinteractive

Spotlight: Katie Bartlett

Mo' Data, Less Problems: Finding Value in Big Data

What the Heck is an Agency Partner?

Briefs: Renee on Why Devs Are Cranky

How We Scrapped Time Sheets

5 Reasons Why the Office Phone is Dead

Launched: Adventist University of Health Sciences

Why Changing Careers is Like Changing Your Pants

How to Build an App Using Meteor JS

Spotlight: Adam Bullinger

Experimenting with Meteor Development

Webinar: A Field Guide to Interaction Design

Landed: NASA Partnership

Gifn: Rise of the GIF Photobooth

Briefs: Mike on Value-based Billing & Profit

iSummit Conference & Ticket Giveaway

Briefs: Adam & Renee on Magento

Chasing Perfect Weather with Mullen & CSX

Developing With Vagrant Flavors

Need Some Space? Book Ours.

Spotlight: Alex King

Designer Merch Collection

10 Reasons to Offer Flexible Work Hours

Google I/O Fanboy Duel

Close

TEAM MEMBER

ABOUT

Matt Eagle

ABOUT

Director of Innovation

As Director of Innovation at PRPL, Matt leads our technology-driven initiatives and oversees the development of cutting-edge experiential activations. With a deep background in software engineering, he brings hands-on expertise and strategic vision to every project. He has helped Fortune 500 brands launch impactful digital and physical experiences that push boundaries and drive engagement.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.