Functional Versus Class Components
This page contains more information and content for the YouTube Short on functional and class components.
React has two types of components: function components and class components.
Ever since the release of React 16.8, which was when hooks were introduced into React, function and class based components could achieve the same goals; they're just done in different ways.
Why is this important to know if I can just learn one type of component and stick with that? First reason, I got asked this question about 2 or 3 times during entry level front end or full stack developer interviews. Given that I've only done about 2 or 3 full stack or front end interviews, that's a pretty common question to be asked. Second reason, you can do whatever you like for personal projects, but when joining an existing team, or working on an existing project, there's going to be previous code that could be in a component type you don't understand. Even though we might prefer one type of component, it's important to be aware of the other type in case we ever encounter it in someone else's project; whether it's in work, another person's small project, etc.
Class Components
Let's start with class components because that was actually my initial preference when I started working with React. I'm not old, so I started working with React after hooks were introduced.
A class component is, well, a class. A class can have objects and methods. A class component treats state as an object; you modify state like modifying any object, and uses methods called lifecycle methods to run code at certain stages of the component being active. Let's take a look at this in code, and then we can explain further.
Generic Class Component Example
import React from "react"; class Welcome extends React.Component { // This is how state is initialized in a class component. // As you might notice, the syntax is quite verbose. constructor(props) { super(props); // Initial state this.state = { userName: "", }; } // Lifecycle method: Simulates fetching data when the component mounts. // In simpler terms, the following code will run when the component initializes. componentDidMount() { // This is just simulating calling an API. console.log("Fetching user data..."); setTimeout(() => { // Simulate fetching a user name. this.setState({ userName: "Inter" }); console.log("User data fetched!"); }, 2000); // Simulates a 2-second delay. } // Render method: Renders the component. // If you don't already know, this is where the HTML and JSX exist in the component. // Otherwise, everything above this is pretty much vanilla JavaScript. render() { return ( <div style={{ textAlign: "center", marginTop: "20px" }}> <h1>Welcome to the App</h1> {this.state.userName ? ( <p>Hello, {this.state.userName}!</p> ) : ( <p>Loading your name...</p> )} </div> ); } } export default Welcome;
Real Component Example
The below code snippet is from one of my personal projects, Bluebird Teaching.
import React from 'react' import Logo from './Logo' import Footer from './Footer' class Resources extends React.Component { // Same thing here; just initializing state. constructor() { super() this.state = { isLoading: true, focusInfo: [], } } // Now we're actually calling an API. // This API call will happen every time the component is mounted. componentDidMount() { this.setState({isLoading: false}) fetch("https://adminbluebirdteaching.pythonanywhere.com/focus_log_api/") .then(response => response.json()) .then(data => { this.setState({ focusInfo: data }) }) } // Here is our render method. render() { if (this.state.isLoading === false) { return ( <div> <Logo /> {console.log(this.state.focusInfo)} <div className="bodyDiv"> <h2>Resources</h2> </div> <div className="aboutBodyDiv"> {this.state.focusInfo.reverse().map(item => ( <div> <h2 className="h2-blue-headers">{item.title}</h2> <p className="p-body">{item.objective}</p> <p className="p-body">{item.categories}</p> <p className="p-body">{item.expected_completion_date}</p> <p className="p-body">{item.description}</p> <p className="p-body">{item.about_bird}</p> <p className="p-body">{item.contributing_nations}</p> <p className="p-body">{item.completed}</p> {item.completed ? ( <p className="p-body"><a href={item.url} target={"_blank"}>Completed</a></p> ) : ( <p className="p-body">In Progress</p> )} <hr></hr> </div> ))} </div> <Footer /> </div> ) } else { return ( <div> <Logo /> <div className="bodyDiv"> <h2>Resources</h2> </div> <div className="aboutBodyDiv"> <h2>Hello</h2> </div> <Footer /> </div> ) } } } export default Resources
Let's put everything together.
As we'll also see with function components, class components must have a return()
method. In a class component, return()
just exists inside render()
Below are all the other things we saw in the above code samples, but they are all optional. They are to be used as needed.
- The
constructor()
method initialize state. - Both examples used the
componentDidMount()
method to trigger code to run when the component is first mounted.
There are plenty of other lifecycle methods. You can visit this link to read about all the other ones, and what they're responsible for.
Function Components
To start off, you probably noticed that class components are pretty wordy; lots of syntax to memorize. That's probably why a lot of people, including myself, switch over to using function components whenever possible.
Lifecycle methods are very useful because there is really where most of our functional code lies. We need to communicate with databases, backend, other services, etc. Those are all done pretty much with lifecycle methods. So how do we achieve the same thing with function components? That is done with hooks.
Ever since the release of React 16.8, which was when hooks were introduced into React, function and class based components could achieve the same goals; they're just done in different ways.
Let's again look at some code samples before we go further into detail.
Generic Function Component Example
import React, { useState, useEffect } from "react"; function Welcome() { // useState initializes the state. // We're essentially using a React predefined method/function to initialize state. const [userName, setUserName] = useState(""); // useEffect replaces lifecycle methods in functional components. // The following code runs after the component is mounted. useEffect(() => { console.log("Fetching user data..."); const timer = setTimeout(() => { // Simulate fetching a user name setUserName("Inter"); console.log("User data fetched!"); }, 2000); // Cleanup function: If the component unmounts before the timeout ends, // This ensures the timeout is cleared. return () => clearTimeout(timer); }, []); // Empty dependency array means this runs only once, after the component mounts. // This return is equivalent to the render method in class components. // No render method. return ( <div style={{ textAlign: "center", marginTop: "20px" }}> <h1>Welcome to the App</h1> {userName ? <p>Hello, {userName}!</p> : <p>Loading your name...</p>} </div> ); } export default Welcome;
Real Code Example
The below code snippet is actually from this project!
import React, { useState, useEffect } from "react"; import { auth } from "../../firebase.js" import { useNavigate } from "react-router-dom" import "../../css/DocumentationHomeComponent.css" function DocumentationHomeComponent() { // Initializing some state that I need. const [loading, setLoading] = useState(true); const [uid, setUid] = useState("") const navigate = useNavigate(); const [hasSpaces, setHasSpaces] = useState(false) // A hook to trigger code to fetch user information when the component is mounted. useEffect(() => { const unsubscribe = auth.onAuthStateChanged((user) => { if (user) { setUid(user.uid) } else { setUid(null) } }) return () => { unsubscribe() } }, []) // Another hook to trigger code to change CSS styling depending on the theme the user has selected. useEffect(() => { const theme = localStorage.getItem('theme') || 'Default'; const root = document.documentElement; if (theme === 'Dark Theme') { root.style.setProperty('--bg-main-container', '#121d2d') root.style.setProperty('--font-color', "#5B92E5") } }, []); // Yet another hook to trigger code to call the fetchPageDetails function. // This hook is actually different because it will only trigger if uid value is no null, undefined, for falsy value. // This hook will also trigger again if the uid value is to change due to the dependency array at the end. useEffect(() => { if (uid) { fetchPageDetails() } }, [uid]) // This is the function that is called by the hook directly above this comment. // Just a function to call an API. const fetchPageDetails = async () => { try { if (uid.length !== 0) { const response = await fetch("https://bluebirddocumentationadmin.pythonanywhere.com/page-details/author/" + uid + "/", { method: "GET", headers: { "Content-Type": "application/json" } }); if (response.status === 200) { console.log("Successfully fetched data from page details"); setHasSpaces(true) setLoading(false) } else if (response.status === 404) { console.log("No page detail data found for this author.") setHasSpaces(false) setLoading(false) } else { console.log("Failed to fetch page detail data."); } } } catch (error) { console.error("Error getting data from page detail table:", error); } } const handleCreateClick = () => { navigate("/create") } // These two conditionals are just to conditionally render based off certain conditions. // Not really important for understanding the whole class versus function component stuff. if (loading) { return ( <div className="DocumentationHomeComponent__loading-animation-div"> <i className="fas fa-spinner fa-spin DocumentationHomeComponent__loading-animation-icon"></i> </div> ) } if (loading === false && hasSpaces === false) { return ( <div> <p className="large"> Looks like you haven't created any documentation yet! <strong>Click the button below to get started</strong>. </p> <button onClick={handleCreateClick} className="submit-button">Create</button> </div> ) } // Here is the return with my HTML. return( <div> <p className="large"> <strong>To view/modify existing documentation</strong>, click on any of the spaces/pages on the left sidebar. </p> <p className="large" style={{ paddingTop: "50px" }}> <strong>To create new documentation</strong>, click the green "Create" button on the bottom of the left sidebar. </p> </div> ) } export default DocumentationHomeComponent
Let's put everything together.
A lot less lines of code to achieve the same thing. Honestly my comments are taking up a good chunk of the lines. Again, the only required portion is the return()
method for our HTML and JSX. Below are all the optional parts that we saw in the above examples.
- There's no verbose
constructor()
method; instead, we useuseState()
to initialize state. - As the second code snippet shows, we can use hooks to trigger code at different conditions. In the example shown above, one of the hooks triggers when a certain state value is truthy, or if that state value has changed.
Hopefully this is enough to get you to understand the gist of function and class components, and to know enough to give a good answer to a very likely interview question! Please feel free to leave a comment below with any questions, comments, or concerns.
Part of: YouTube Channel