DEV Community

Cover image for React Components: Functional vs. Class πŸš€
Elnara
Elnara

Posted on

React Components: Functional vs. Class πŸš€

Let's make React components super easy to understand! We've got two main types: Functional Components and Class Components. Think of them as two ways to build your UI pieces.


1. Making a Basic Component πŸ—οΈ

Functional Component: Simple JavaScript Function

This is a normal JavaScript function that gives you back UI.

  • How to make:
    • Write an arrow function.
    • Give it a name (that's your component!).
    • It returns some JSX (your UI bits).
  • Export/Import: Works just like any other JavaScript file. Easy peasy!
import React, { useState, useEffect } from 'react';

const MyAwesomeFunctionComponent = () => {
    return (
        <>
            <h1>Hello from Functional Fun! πŸ‘‹</h1>
        </>
    );
};

export default MyAwesomeFunctionComponent;
Enter fullscreen mode Exit fullscreen mode

Class Component: Normal JavaScript Class

This is a normal JavaScript class that extends React.Component.

  • How to make:
    • Make a class that extends React.Component.
    • It must have a render() method inside that returns JSX.
  • Export/Import: Same as Functional Components.
import React from 'react';

class MyCoolClassComponent extends React.Component {
    render() {
        return (
            <>
                <h1>Hello from Classy Code! 🎩</h1>
            </>
        );
    }
}

export default MyCoolClassComponent;
Enter fullscreen mode Exit fullscreen mode

2. Sending Data with Props 🎁

Props are how you send information from a parent component down to its child.

Functional Component: Props as Arguments

Props come as an argument to your function. Grab them directly!

const FunctionalComponent = ({ name, location }) => { // Props are right here!
    return (
        <>
            <p>Name: {name}</p>
            <p>Location: {location}</p>
        </>
    );
};

export default FunctionalComponent;

// How you'd use it: <FunctionalComponent name="Alice" location="Wonderland" />
Enter fullscreen mode Exit fullscreen mode

Class Component: Props with this.props

For Class Components, props are in this.props. You'll need a constructor.

  • Constructor magic: The constructor gets props before render.
  • super(props) is key! ✨ You must write super(props). This ensures this.props is ready to use everywhere in your component.
import React from 'react';

class UserClass extends React.Component {
    constructor(props) {
        super(props); // ✨ IMPORTANT: Makes props available with 'this.props'
    }

    render() {
        const { name, location } = this.props; // Access props using 'this.props'
        return (
            <>
                <h2>Class Based User</h2>
                <p>Name: {name}</p>
                <p>Location: {location}</p>
            </>
        );
    }
}

export default UserClass;

// How you'd use it: <UserClass name="Bob" location="Builderland" />
Enter fullscreen mode Exit fullscreen mode

3. Handling Changing Data with State πŸ”„

State is like a component's personal memory. It tracks data that changes, updating the UI.

Functional Component: useState Hook

The useState "Hook" adds memory to functional components.

  • Making state: const [thing, setThing] = useState(initialValue);
    • thing is your current value.
    • setThing is the function to change it.
  • Using state: Just use thing.
  • Updating state: Call setThing(newValue);
import React, { useState } from 'react';

const FunctionalComponent = ({ name, location }) => {
    const [count, setCount] = useState(0); // 'count' is state, 'setCount' updates it

    return (
        <>
            <p>{name}</p>
            <p>Count: {count} πŸ”’</p>
            <button onClick={() => setCount(count + 1)}>Increment</button>
            <p>{location}</p>
        </>
    );
};

export default FunctionalComponent;
Enter fullscreen mode Exit fullscreen mode

Class Component: this.state & this.setState

In Class Components, state is a big object: this.state.

  • Making state: Put this.state = { ... }; in the constructor (when the class starts).
// Inside your Class Component's constructor
this.state = {
    count: 0, // Your state variable
    userName: "Guest", // Can have many!
};
Enter fullscreen mode Exit fullscreen mode
  • Using state: Access as this.state.count or destructure: const { count } = this.state;.
  • Updating state: NEVER change this.state.count = this.state.count + 1 directly! 🚫 Use this.setState().
    • this.setState() takes an object with new state values.
import React from 'react';

class UserClass extends React.Component {
    constructor(props) {
        super(props);
        // Creating state variables inside the constructor
        this.state = {
            count: 0,
        };
    }

    render() {
        const { name, location } = this.props;
        const { count } = this.state; // Grab 'count' from state

        return (
            <>
                <h2>Class Based Component</h2>
                <p>Name: {name}</p>
                <p>Location: {location}</p>
                <p>Count: {count} πŸ”’</p>
                <button
                    onClick={() => {
                        // Updating state variables using this.setState()
                        this.setState({
                            count: this.state.count + 1,
                        });
                    }}
                >
                    Increment
                </button>
            </>
        );
    }
}

export default UserClass;
Enter fullscreen mode Exit fullscreen mode

4. Talking to the Outside World: API Calls and Side Effects 🌐

Sometimes, components need to do things beyond rendering UI, like fetching internet data. These are "side effects".

Functional Component: useEffect Hook for API Calls

useEffect is your tool for side effects like API calls in functional components.

  • Why useEffect? Component shows up fast, then useEffect fetches data and updates the display.
  • When it runs for API calls:
    • Use [] as the second argument. This makes the effect run only once after the first render. Perfect for fetching initial data!
import React, { useState, useEffect } from 'react';

const FunctionalComponent = ({ name, location }) => {

    // State to hold GitHub user data
    const [githubUser, setGithubUser] = useState(null);

    // πŸ“ž GitHub User API Call (runs once on page load)
    useEffect(() => {
        const fetchGithubUser = async () => {


                // Fetch data for a specific GitHub user (e.g., 'octocat')
                const response = await fetch('https://api.github.com/users/octocat');
                const data = await response.json();
                setGithubUser(data); // Set fetched data to state

        };
        fetchGithubUser();
    }, []); // 🚨 Empty array means: Run ONLY once after the first render

    return (
        <>
            <p>{name}</p>
            <p>{location}</p>

            {/* Display GitHub user info if available */}
            {githubUser && (
                <div>
                    <h3>GitHub User Info:</h3>
                    <p>Username: **{githubUser.login}**</p>
                    <p>Name: {githubUser.name || 'N/A'}</p>
                    <p>Bio: {githubUser.bio || 'No bio provided.'}</p>
                    <p>Public Repos: {githubUser.public_repos}</p>
                    <p>Followers: {githubUser.followers}</p>
                </div>
            )}
        </>
    );
};

export default FunctionalComponent;
Enter fullscreen mode Exit fullscreen mode

Class Component: Life Cycle Methods

Class Components have special methods called Life Cycle Methods that run at different moments in their "life."


5. Class Component Life Cycle (The Component's Journey)

A component has three main phases:

  • Mounting: When it first appears on your screen. πŸš€
  • Updating: When its data (state or props) changes and it needs to refresh. πŸ”„
  • Unmounting: When it disappears from your screen. 🧹

LifeCycle

5.1. Mounting Life Cycle (When it appears πŸš€)

When a Class Component mounts:

  1. constructor(): First thing called! Sets up initial state.

  2. render(): Draws the component (might show empty data first).

  3. componentDidMount(): Called after the component (and its children) are fully visible.componentDidMount is the Best place for API calls. Fetch real data here; component is visible, update state to re-render.

Actual Flow:

  • Parent constructor
  • Parent render (Parent starts drawing)

    • First Child constructor
    • First Child render
    • Second Child constructor
    • Second Child render
    • React updates the actual web page (DOM) ✨ (This is the fast part where React figures out changes)
    • First Child componentDidMount (First child is ready)
    • Second Child componentDidMount (Second child is ready)
  • Parent componentDidMount (Parent is ready, after all its kids)

import React from 'react';

class UserClass extends React.Component {
    constructor(props) {
        super(props);

        console.log("UserClass: Constructor called.");
    }

    componentDidMount() {
        console.log("UserClass: componentDidMount called. data!");
        /

        // ⏰ Setting up an interval (will need cleanup!)
        this.timer = setInterval(() => {
            console.log('Hello from Class Component interval!');
        }, 1000);
    }

    render() {
        const { name, location } = this.props;

        console.log("UserClass: Render called.");

        return (
            <>
                <h2>Class Based Component (Life Cycle Demo)</h2>
                <p>Name: {name}</p>
                <p>Location: {location}</p>


            </>
        );
    }
}

export default UserClass;
Enter fullscreen mode Exit fullscreen mode

5.2. Updating Life Cycle (When it changes πŸ”„)

When component data (state or props) changes, it refreshes:

  1. render(): Called again with new props/state.

  2. componentDidUpdate(prevProps, prevState): Called right after update. You get old props/state. Handy for doing something only if specific data changed.

  3. componentDidUpdate(prevProps, prevState) : Calling this.setState in componentDidUpdate must be in an if statement to prevent endless loops.

// Inside your UserClass component...

componentDidUpdate(prevProps, prevState) {
    console.log("UserClass: componentDidUpdate called.");
    // Example: Fetch new data ONLY if 'location' prop changed
    if (this.props.location !== prevProps.location) {
        console.log("Location changed! Need new data...");
        // Call your API function here
    }

    // Example: Do something if 'count' state changed
    if (this.state.count !== prevState.count) {
        console.log("Count just changed to:", this.state.count);
    }
}
Enter fullscreen mode Exit fullscreen mode

5.3. Unmounting Life Cycle (When it disappears 🧹)

When a component is removed from the screen (e.g., navigating away):

  1. componentWillUnmount(): Called just before the component vanishes. Cleanup Time🧼 You must cleanup anything running in the background like a setInterval. Stop these processes here.
// Inside your UserClass component...

componentWillUnmount() {
    console.log("UserClass: componentWillUnmount called. Time to clean up! 🧹");
    clearInterval(this.timer); // Stop the timer we started in componentDidMount
}
Enter fullscreen mode Exit fullscreen mode

Cleanup: How it Works in Both Types πŸ€”

Handling background tasks (like timers or subscriptions) is crucial to prevent performance issues, especially in Single Page Applications (SPAs). Here's how cleanup is managed in both component types:

Functional Components (using useEffect's return)

  • If your useEffect sets up an ongoing task (like setInterval or an event listener), you return a function from useEffect.
  • This returned function automatically runs when the component unmounts (is removed from the screen) or just before the effect runs again (if its dependencies change).
  • Example: Stopping a timer

    useEffect(() => {
        const timer = setInterval(() => {
            console.log('Functional component interval running!');
        }, 1000);
    
        return () => { // This is the cleanup function!
            clearInterval(timer); // Stops the timer
            console.log('Functional component interval cleaned up! πŸ‘‹');
        };
    }, []); // Empty array means runs once on mount, cleans up on unmount
    

Class Components (using componentWillUnmount)

  • If you set up an ongoing task in componentDidMount (or other lifecycle methods), you must manually stop it in the componentWillUnmount() method.
  • This method is specifically designed to run just before the component is completely removed from the DOM.
  • Example: Stopping a timer

    class MyClassComponent extends React.Component {
        componentDidMount() {
            this.timer = setInterval(() => {
                console.log('Class component interval running!');
            }, 1000);
        }
    
        componentWillUnmount() { // This is the cleanup method!
            clearInterval(this.timer); // Stops the timer
            console.log('Class component interval cleaned up! 🧹');
        }
        // ... render method ...
    }
    

Why Cleanup is Super Important: Any background task (like a timer) started in a component will keep running in memory even after the component is gone if not stopped. This causes performance problems in Single Page Applications (SPAs). Always clean up!


Conclusion: Which One to Use? πŸ€”

While Class Components were the original way, Functional Components with Hooks (like useState and useEffect) are now the modern, recommended way in React!

They are:

  • Simpler: Less code, easier to read.
  • Cleaner: Logic for one feature stays together.
  • Modern: The way forward in React development.

But hey, knowing Class Components is still super useful for understanding old code or if you just prefer that style for certain things!

Top comments (0)