Introduction to Functional Components in React

Anton Ioffe - August 31st 2023 - 23 minutes read

Understanding Functional Components

Functional components, often referred to as stateless components, are a basic unit in React's component hierarchy. They are a simpler and more lightweight method of constructing components compared to class components. But what exactly are these functional components, how do they compare to class components, and where do they fit in the overall structure of React?

To put it in its simplest terms, a functional component is just a JavaScript function. This function returns a React element that renders some HTML to the DOM. The function takes in props as an argument and returns a React element. The functional nature of these components is where its name stems from.

Here's a basic example of a functional component in React:

// This is a functional component
const Greeting = (props) => {
    return <h1>Hello, {props.name}</h1>;
}

In this example, Greeting is a functional component that takes in props as an argument and returns a React element as an output. This React element then renders an H1 tag containing the text "Hello" followed by a prop name.

Unlike class components, functional components do not have lifecycle methods because they do not have state. Instead, functional components purely take in props and return JSX. They are presentational in nature and often are involved in rendering UI.

It's important to note that not all functions are functional components. For them to be considered as a functional component, the function should adhere to the following criteria:

  • The function should return a React element.
  • It has to take in props as an argument (this may be implicitly done through destructuring in the function signature).

Here's an example of creating a functional component without a return statement, which is possible due to the implicit return functionality of arrow functions:

// This is a functional component without a return statement
const Greeting = (props) => <h1>Hello, {props.name}</h1>;

The purpose of functional components is to provide a simpler and more concise way to create components that are primarily presentational. They offer an easier syntax and a more straightforward approach to component rendering. Functional components also lend themselves quite well to predictable and easy-to-follow patterns in code, making them beneficial from a maintainability and understandability perspective.

To sum up, functional components are a fundamental aspect of React's component hierarchy, offering a leaner, more functional approach to constructing components. They cater to the needs of simpler, stateless components by providing a straightforward syntax and a predictable pattern of behavior. The usage and understanding of functional components in React is fundamental to grasp React's approach to building applications, and mastering them is key to becoming an effective React developer.

Writing and Importing Functional Components

Defining Functional Components in JavaScript

In React applications, functional components are JavaScript functions. They return a React element and accept a "props" object as a parameter. Here's a straightforward example:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

In this block, Welcome is a functional component. Specifically, it's a JavaScript function that returns JSX.

Defining Functional Components in TypeScript

Just like in Javascript, TypeScript uses a similar syntax when defining functional components, with an added step of explicitly specifying the type of the props:

interface WelcomeProps {
  name: string;
}

function Welcome(props: WelcomeProps) {
  return <h1>Hello, {props.name}</h1>;
}

Here, Welcome is expected to receive a name property of type string.

Importing Functional Components in a React Application

After defining the functional components, it is necessary to export them for usage in other parts of your application. Functional components can be exported and then imported in another file using the import statement. Following is an example with JavaScript:

export function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

import { Welcome } from './Welcome.js';

The export and import statements in TypeScript follow similar syntax:

export function Welcome(props: WelcomeProps) {
  return <h1>Hello, {props.name}</h1>;
}

import { Welcome } from './Welcome.tsx';

Common Mistakes in Syntax

Programming involves some syntax traps that a programmer can easily fall into, especially when exporting and importing components. One such common mistake is mixing up the export default keyword with the export keyword and the usage of curly braces ({}). If export default is used when exporting, there would be no need to use curly braces when importing. Conversely, if the default keyword is not used, then it is necessary to include {} when importing.

Frequently made syntax error during writing a functional component:

export Welcome(props) { 
  return <h1>Hello, {props.name}</h1>; 
}

This example is missing the function keyword which results in a syntax error. The correct version should be export function Welcome(props).

Keyword Considerations

When writing functional components, the JSX is being returned from your function, and when importing the components, the import...from syntax is used. Several keywords, such as export, import, and default, play important roles in correctly defining and importing components. Remembering the correct usage of {} and default can help avoid potential syntax errors.

Advantages & Disadvantages of Functional Components

Functional components in React or functional React components have been gaining popularity since the introduction of React Hooks in React version 16.8.0. They are essentially JavaScript functions that accept props as an argument and return a React element. While functional components are pure functions, not tied to class instances, they pose a unique set of advantages and disadvantages. Let's take a look.

Advantages of Functional Components:

  1. Simplicity: Functional components are simpler and easier to understand, especially for beginners. There's no need for this keyword, and props are passed as a simple argument to a function. For example:
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
  1. Incorporation of React Hooks: Functional components can use React Hooks, a powerful feature introduced in React 16.8. This allows functional components to have a state and other lifecycle features that were previously only available to class components.

  2. Performance: Functional components tend to be slighter faster and smaller than equivalent class components. Since they are just plain JavaScript functions, they require less code, which means less bytes to load and a faster parsing time.

Disadvantages of Functional Components:

  1. Lifecycle Methods: Until the introduction of Hooks, functional components lacked lifecycle methods. Thus, doing complex operations that required lifecycle methods were tough to do with functional components.

  2. Verbose Syntax with Hooks: Code can become more verbose when using Hooks for state and lifecycle methods. A lot of logic comes into play while managing state in functional components which can make the code harder to manage.

  3. Overhead of Hooks: Each Hook in functional component renders, which adds a slight overhead.

It's important to weigh the benefits and drawbacks when it comes to choosing between functional and class components in React. Will the simplicity and ease of use of functional components outweigh the more verbose syntax when dealing with state and lifecycle methods? Is the minimal performance gain worth the slight overhead of Hooks?

Furthermore, performance comparison between a functional component and a class component often comes down to the specific use case. In general, the performance difference is negligible and should not be a deciding factor. Precise performance optimization varies depending on the structure of the application and the exact code in question.

In conclusion, before deciding to utilize one or the other, it's essential to define the terms and do in-depth analysis, as we did above. Consider all the aspects of the project, including the skill level of the team, size and complexity of the project, before deciding on the specific type of component to use. A balanced approach and good understanding of both paradigms can lead to effective use of React components in any application.

Here's your challenge: Create a functional component and a class component that performs the same task, then compare their performance using the console.time and console.timeEnd methods. Is there a noticeable performance difference, given your specific use case?

State and Props in Functional Components

If you have worked with React before, you must have dealt with props and state. Today, we're going to explore how you can work with state and props in functional components using React hooks, namely useState and useEffect.

Props in Functional Components

Props are short for properties. They are how we pass data from parent components down to child components. In functional components, props are accessed via the function parameter. Here’s an example:

function Greetings(props){
  return <h1>Hello, {props.name}</h1>;
}

In the above functional component, props is an object that has a property name. We could use destructuring to access props.name directly as follows:

function Greetings({ name }){
  return <h1>Hello, {name}</h1>;
}

Remember, props are read-only. You should never try to modify them. Modifying props could lead to she unpredictable app behavior.

State in Functional Components

While props allow data to flow down the parent component to the child components, state is what allows components to maintain and update their data. Before React hooks were introduced, you could use state only with class components - all that changed with useState.

import { useState } from 'react';

function Counter() {
 const [count, setCount] = useState(0);

 return (
   <div>
     <p>You clicked {count} times</p>
     <button onClick={() => setCount(count + 1)}>
       Increment
     </button>
   </div>
 );
}

Here, useState is a hook that allows you to add React state to functional components. It accepts the initial state as an argument and returns an array of two entries. The first one is the state value, and the second one is the function to update the state (in this case, count and setCount ).

You might come across scenarios where you'd want to use props in the useState hook like so:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count is: {count}
    </button>
  );
}

Here, we are initializing the count state with the initialCount prop. This is fine; however, there's a tiny catch. useState only assigns the initial value once, in the first render.

React’s useEffect Hook

Effects are where you perform side effects in functional components. Side effects could be data fetching, setting up a subscription, or manually changing the DOM. In class components, side effects are done in lifecycle methods such as componentDidMount, componentDidUpdate, or componentWillUnmount.

To do this in a function component, useEffect is your hook of choice. Unlike the lifecycle methods in class components, useEffect runs after every render, including the first render.

Here’s an example of a useEffect:

import { useState, useEffect } from 'react';

function User({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch('/' + userId);
      const rspUser = await response.json();
      setUser(rspUser);
    };

    fetchUser();
  }, [userId]);  // Only re-run the effect if userId changes

  return (
    <div>
      {user == null ? 'Loading...' : user.name}
    </div>
  );
}

In the example above, useEffect is running an effect that fetches user’s data whenever the userId prop changes.

Using state and props in functional components might seem a little bit different from what you were used to with class components. However, once you get your hands dirty, you'll find that it's a direct, more intuitive way to handle data passing and state maintenance in React. Don't shy away from using functional components in your next React project!

Passing Data and Functions in Functional Components

In the sphere of web development, functional components in JavaScript and TypeScript pivotally impact the flow of data and capabilities across an app. Grasping how data and functions get transferred into these components can significantly transform the overall development process. Essentially, the conveyance of data and functions is steered by the use of properties, or props.

To explore this principle further, let's plunge into the fundamental ideas and practical scenes.

Data passing between two functional components

Props facilitate communication between components. Think of props as the door that allows parents components to give data to child components. Written as attributes in components, props interpret the read-only nature of their values to cement that data bequeathed from a parent will not undergo changes inside a child component.

Here's an example below:

// ParentComponent defines greeting and passes it to ChildComponent through props.
function ParentComponent() {
    const greeting = 'Hello, child!';
    return <ChildComponent propMessage={greeting} />
}

In this instance, 'greeting' is handed over to the ChildComponent as a 'prop'. To utilize this 'prop' in the child component, it proceeds in the following manner:

// ChildComponent receives and uses the prop.
function ChildComponent(props) {
    return <h1>{props.propMessage}</h1>
}

Here, props embodies an object receiving the data from ParentComponent. The material 'greeting' can be accessed as a property of 'props', generating a header that reads "Hello, child!".

Passing functions between components

Following the same vein as data, functions too, can be moved from one component to another through props. This method is notably practical when a child component needs to relay back to its parent.

Check out this example:

// ParentComponent defines a function and passes it to ChildComponent as prop.
function ParentComponent() {
    const sayHi = () => {
        console.log('Hello, child!');
    }
    return <ChildComponent greetPropFunc={sayHi} />
}

In this case, the function gets passed on to ChildComponent as a 'prop'. Inside the child, the function, 'sayHi', is invoked as follows:

// ChildComponent using the received function prop.
function ChildComponent(props) {
    return <button onClick={props.greetPropFunc}>Greet parent</button>
}

In this scenario, 'props.greetPropFunc' relates back to the 'sayHi' function in ParentComponent. By clicking the button, "Hello, child!" will now be displayed on the console.

Common mistakes

Often, programmers inadvertently modify props directly, which in JavaScript and TypeScript, does not change the original data and can bring about glitches in the application.

// Incorrect usage of props leading to bugs.
function ChildComponent(props) {
    props.propMessage = 'Altered Text';
    return <h1>{props.propMessage}</h1>
}

In the above example, although the value of props.propMessage gets altered within ChildComponent, the original value of 'propMessage' remains unchanged in ParentComponent. Be mindful that props are read-only in their child components.

Gaining an understanding of how to effectively transfer data and functions between components is paramount in functional programming with JavaScript and TypeScript. Be alert to the common mistakes that are often made by developers that will aid in writing comprehensible, sustainable code.

For the challenge:

// Example layout
function ParentComponent() {
  const [userData, setUsersData] = useState([
    {name: 'John Doe', email: 'johndoe@mail.com', role: 'Admin'}, 
    {name: 'Jane Doe', email: 'janedoe@mail.com', role: 'User'}
  ]);

  return <ChildComponent userDataProp={userData} updateUsersProp={setUsersData} />;
}

Create a ChildComponent now, which displays these users, and also has a delete button to delete a specific user, updating the parent state as well.

// ChildComponent example for the challenge.
function ChildComponent(props) {
  const deleteUser = (index) => {
    let usersDataPropCopy = [...props.userDataProp];
    usersDataPropCopy.splice(index, 1);
    props.updateUsersProp(usersDataPropCopy);
  }

  return (
    <div>
      {props.userDataProp.map((user, index) => (
        <div key={index}>
          <p>{user.name}</p>
          <p>{user.email}</p>
          <p>{user.role}</p>
          <button onClick={() => deleteUser(index)}>Delete User</button>
        </div>
      ))}
    </div>
  );
}

Reusability and Context with Functional Components

Functional components in JavaScript and TypeScript have gained prominence due to their light weight, simplicity, and predictability. They help developers build more manageable code, but one of the true beauty of these components is their reusability.

Reusability in the context of functional components revolves around breaking down complicated user interfaces into smaller pieces (components), and reusing these smaller components to build other components. Not only does this enhance readability, but it also makes maintenance a lot easier. Let's take a look at an example of a functional component in JavaScript:

const DisplayMessage = ({ message }) => {
  return <p>{message}</p>;
};

Now, this DisplayMessage component can easily be reused anywhere we need to display a message by simply passing the message as a prop:

<DisplayMessage message="Hello, World!" />

Although the example provided above is simple, imagine a scenario with a more complex component. One that, for example, fetches data from an API and renders it. If we factor such component into smaller reusable components, it not only simplifies the work of a developer but also minimizes the risk of errors.

Next, let's talk about context in functional components. Context provides a way to share data between components without having to pass props down manually at every level in your application. It makes data access at various levels of your component tree more efficient. Here's an example of context usage within functional components:

import React, { useContext } from 'react';

const MyContext = React.createContext({});

const ChildComponent = () => {
  const context = useContext(MyContext);
  return <p>{context.myValue}</p>;
};

const ParentComponent = () => {
  return (
    <MyContext.Provider value={{ myValue: 'Hello, Context!' }}>
      <ChildComponent />
    </MyContext.Provider>
  );
};

In the above example, ChildComponent uses the useContext hook to access data from the context provided by ParentComponent. This ensures that ChildComponent can access the value directly from the context without having to receive it as a prop from ParentComponent.

However, despite the practical benefits of component reusability and context, there are a few pitfalls that can occur if not properly managed. For instance, misusing context can lead to unnecessary renders, hurting performance. It's important to only use context for data that needs to be globally accessible within the Component tree.

On reusability, avoid forcing component reuse when it doesn't naturally fit. Over-engineering reusability can make your code more convoluted and harder to maintain. It's preferable to duplicate some code if it enhances clarity and maintainability.

Overall, correctly using context and reusability in functional components helps in maintaining clean and efficient codebases. When balanced with appropriate considerations, these two practices are fundamental for developing effective React applications. It's important to always approach them with a deep understanding of your application's specific needs and requirements.

The Evolution of React Components

React.js, a JavaScript library created by Facebook, has significantly evolved over the years concerning how we can create components. Its evolution in component creation is characterized by a transition from class-based components to functional components with hooks.

Class Components

Originally, in React, we had only class components. A class component is a JavaScript ES6 class that extends from React. Inside, it has a render method that returns JSX. These components cater to stateful scenarios by offering the React component lifecycle methods such as componentDidMount, componentDidUpdate, componentWillUnmount, etc. A typical class component looks like this:

class MyComponent extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: 'React',
        }
    }

    render() {
        return <h1>Hello, {this.state.name}</h1>
    }
}

The Introduction of Functional Components

For a long time, class components were the only way to use state and lifecycle events in a React application. However, in response to several challenges associated with class components, functional components were introduced. Functional components use functions which return JSX, and traditionally, were stateless and did not have access to lifecycle methods. For instance:

const MyComponent = (props) => {
    return <h1>Hello, React</h1>
}

Despite their simplicity, functional components initially lacked the features which made class components versatile.

The Emergence of Hooks

To bridge this gap, React introduced Hooks in version 16.8. These give functional components the ability to have their state and lifecycle methods, thereby providing a more modern, cleaner, and simpler way to encapsulate state and side effects in React components. The most popular React Hooks include useState and useEffect, among others.

Re-writing the earlier class component example using a functional component with hooks:

import React, { useState } from 'react';

const MyComponent = () => {
    const [name, setName] = useState('React');
    return <h1>Hello, {name}</h1>;
};

Functional Components vs Class Components

Functional components with hooks have become more popular because they're generally thought to be more readable, concise, and easier to test. They also make it easy to extract and reuse logic, and help handle complex state logic and side effects.

However, it's crucial to understand that functional components weren't introduced to replace class components entirely. Class components will continue to work, and their usage is perfectly fine. The introduction of functional components with hooks is mainly about offering alternatives and enhanced capabilities. This allows developers to choose the style that is most suitable for their particular use-case or taste.

It's common for developers to misunderstand why functional components were introduced. They weren't introduced to replace class components, but rather to solve common problems linked with classes like complex reuse of stateful logic and poor understanding of lifecycle methods.

So far, we've journeyed through the evolution of component types in React, starting from class components to functional components accompanied by Hooks. Understanding the history of these components not only enriches our knowledge but informs us of the rationale behind their separate developments and how they complement each other in enhancing our development experience with React.

Looking forward, we might ask ourselves: what will be the next step in the evolution of React components?

Converting Class Components to Functional Components

While working with React, you might find yourself in a scenario where you're very comfortable with class components. But with the advent of Hooks in React, the shift towards functional components is real. Functional components not only simplify your React codebase and enhance readability, but they also facilitate code reuse with custom hooks, and optimize component re-rendering.

Consider a class component named someClassComponent:

class SomeClassComponent extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            name: ''
        };
    }

    render(){
        return(
            <div>
                <p>{this.state.name}</p>
            </div>
        );
    }
}

Now, let's delve into converting this class component to a functional one step by step:

1. Identify Component State

The first step to converting a class component to a functional one involves identifying all the state objects within the class component. Recognizing the state is a pivotal step as it builds the foundation for our conversion process. For someClassComponent, the state object is name. Ensure you look out for any state updates that might be occurring directly within the render() method.

2. Define Functional Component and State Hook

The next phase of conversion involves defining our functional component. Here, the intent is to apply React's useState hook to mirror the state from our class component.

import { useState } from 'react';

const SomeFunctionalComponent = (props) => {
    const [name, setName] = useState('');
}

The usage of useState('') demonstrates that our name state is a string. We can swap the use of this.setState({name: name}) in class components with setName that serves as the state update method. This approach simplifies state management within components. However, care must be taken to ensure all state variables are accounted for.

3. Implement Component JSX

The final stride towards converting our class component to a functional one is the implementation of the JSX.

const SomeFunctionalComponent = (props) => {
    const [name, setName] = useState('');

    return(
        <div>
            <p>{name}</p>
        </div>
    );
}

Unlike class components, where you need to use the this keyword to access state or props, with functional components, the states or props are directly accessible. Hence, instead of writing this.state.name, we can simply write name. This leads to cleaner and more readable code.

In conclusion, transitioning from class components to functional components is pivotal for modern React development. This guide covered the conversion of a basic class component to a functional one, without diving into components with lifecycle methods. Embrace the change, and make the transition to thinking in functional components!

Performance and Limitations of Functional Components

Functional components, whether written in Javascript or TypeScript, are a staple in many modern web development stacks. They are essential to the React library and can greatly streamline codebases and improve performance when used properly.

Performance advantages

Functional components have significant performance benefits. The foremost advantage is that they are stateless. This means they do not manipulate or keep track of their state, which reduces their memory usage and hence improves performance.

Consider the following functional component:

function Greeting(props){
    return <h1>{'Hello, ' + props.name}</h1>;
}

It is stateless, and when rendered, it only requires the properties passed to it to define its output. This functional component is much more performance efficient compared to class components that keep track of their state. Stateless components result in cleaner, more readable code and reduce memory footprint, which, in turn, increases the loading speed of your web application.

Functional components also support lazy loading, another significant performance boost. Lazy loading is a technique where your application only loads components as they are needed. It reduces the initial load time of your app, resulting in a faster time to interact. This can be leveraged using React's built-in hook, React.lazy. Here's an example:

const LazyComponent = React.lazy(() => import('./LazyComponent'));

This code snippet instructs the app to load the LazyComponent only when required, rather than during the initial page load.

Performance limitations

Despite their advantages, it's essential to be aware of functional components' limitations, as misuse or misunderstanding their capabilities can negatively impact performance.

For instance, functional components are rerendered every time their parent component rerenders, even if their props haven't changed. This can lead to performance drawbacks in large applications or those with complex component trees.

Here's an illustration:

function ParentComponent (props) {
    const [count, setCount] = useState(0);

    return (
        <div>
            <button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
            <MyChildComponent name="This is a test" />
        </div>
    );
}

In the example above, whenever the Increment button is clicked, MyChildComponent is also rerendered, even though its props remain the same.

This issue can be mitigated by using React.memo, a higher-order function that memoizes your functional component and prevents unnecessary rerenders if the props haven't changed.

This is how you could use React.memo in the example above:

const MyChildComponent = React.memo(function MyChildComponent(props) {
    // Your component code here
});

In conclusion, understanding the capabilities and limitations of functional components is essential in optimizing your Javascript or TypeScript web applications. While they have significant performance benefits due to statelessness and support for lazy loading, they do have some limitations. Misunderstanding these can lead to performance drawbacks. Being mindful of these factors will enable you to leverage functional components effectively for creating efficient web applications.

Challenge - Analyze your existing JS or TS application, and convert class components to functional ones where it makes sense. Experiment with lazy loading to improve performance. Monitor your app closely and observe the difference in performance. Did the changes you made improve application load time?

React Hooks and Functional Components

Understanding how React Hooks interact with functional components is paramount for modern React developers. Hooks are a critical part of the React ecosystem and can heavily simplify the codebase when used properly. They improve the readability and reusability of the code, however, their misuse can lead to unexpected behavior or bugs that are hard to debug.

The Basic Connection

React Hooks are functions that let us use state and other React features within functional components. From React 16.8 and forward, functional components accompanied by Hooks are fully capable of performing the same tasks that previously could be accomplished only using class components.

Suppose we are building a counter component in React. Traditionally, we need to utilize React's class components and a constructor method to define our state object.

class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            'count': 0
        };
        this.increment = this.increment.bind(this);
    }

    increment() {
        this.setState({'count': this.state.count + 1});
    }

    render() {
       return (
           <div>
               <p>You clicked {this.state.count} times</p>
               <button onClick={this.increment}>
                   Click me
               </button>
           </div>
       );
    }
}

However, with Hooks, we can achieve the same functionality using a functional component instead of a class component.

import { useState } from 'react';

function Counter(){
    const [count, setCount] = useState(0);

    function increment(){
        setCount(count+1);
    }

    return(
        <div>
            <p>You clicked {count} times</p>
            <button onClick={increment}>Click me</button>
        </div>
    );
}

UseState and UseEffect

The useState and useEffect hooks have been used in the above counter example.

The useState is a Hook we call inside our functional component to add some local state to it--and it doesn’t require writing a class!

useEffect does something after render. In effect this Hook says “React, after you render, do something.” This behavior is similar to componentDidMount and componentDidUpdate in class components.

The useState and useEffect hooks need to be used in the body of the functional components, not inside loops, conditions, or nested functions to follow React’s Hooks rules.

Common Mistake: Not Using Hooks Correctly

A common mistake with using hooks is not correctly handling dependencies in useEffect. If you forget to add all necessary elements of the state that the effect depends on into the dependency array, you might encounter an infinite loop or stale state. It's essential to carefully handle dependencies to ensure optimal performance and to avoid unnecessary side effects.

Here's an example of a common mistake using useEffect:

const [count, setCount] = useState(0);

useEffect(() => {
    const interval = setInterval(() => {
        setCount(count + 1);
    }, 1000);

    return () => clearInterval(interval);
}, []);

In the above example, count is not added to the dependency array of useEffect. It means that the count inside setInterval remains at its initial value of 0 because it only captures the initial value of count.

Combining Theoretical and Practical Perspectives

Further, the theoretical understanding leads us to a practical perspective where we can see how Hooks empower us to create reusable logic. The concept of creating custom hooks allows us to extract component logic into reusable functions.

For instance, let's create a useCounter custom hook:

function useCounter() {
    const [count, setCount] = useState(0);

    const increment = () => {
        setCount(count + 1);
    };

    return { count, increment };
}

Now, any functional component can utilize this hook to access the count state and the increment function. Thus, we have successfully decoupled the logic which can now be used in multiple components.

Hooks provide powerful capabilities to functional components and reduce code clutter. Nevertheless, misuse or misunderstanding of hooks or simply overlooking the implications can lead to unexpected behavior. As with any robust tool, it requires a good understanding and careful use.

Don't Get Left Behind:
The Top 5 Career-Ending Mistakes Software Developers Make
FREE Cheat Sheet for Software Developers