What are React Hooks and Why Do I Need Them?

Hi friend!

Let‘s take an insightful walk together through the React Hooks revolution.

Hooks represent the biggest change to React since… well, since React! First released in early 2019, Hooks overhaul how React components are written.

I think it's fair to say that when it comes to Hooks, you'll find three types of React devs:

  • Those who love the concept of Hooks
  • Those who hate how much change they introduce
  • And developers who are just plain confused about what all the fuss is about

Wherever you currently stand, this guide will cover everything you need to know about React Hooks:

  • Why Hooks came to be in the first place
  • The problems Hooks seek to solve for React developers like you and me
  • An overview of built-in and custom Hook recipes
  • Practical examples of Hooks in action
  • Strategies for gradually adopting Hooks in your projects

Let‘s start by understanding the story behind why Hooks were added, and why they are in fact very much needed.

Why Were Hooks Introduced?

Since Facebook open sourced React in early 2013, component architecture patterns have rapidly evolved:

  • React began with simple function components for declaring UI
  • Class components were later introduced for stateful logic and lifecycles
  • Higher Order Components and Render Props emerged to share logic, but increased complexity

While these innovations served React well for many years, development teams at Facebook began reaching the limits of what classes could comfortably manage as their web and mobile apps grew exponentially more complex.

The React core team realized class components came with a few key pain points:

Confusing this Binding

A common footnote when writing React class components is binding event handler callbacks to ensure the proper this context is available:

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

    this.handleClick = this.handleClick.bind(this); 
  }

  handleClick() {
    // Correctly bound this
  } 

}

Forgetting to bind callback functions is a easy mistake that creates tricky bugs.

With the function components and closures provided by Hooks, this binding is no longer necessary.

Complex Lifecycle Methods

To execute key logic at certain times, class components rely on lifecycle methods like componentDidMount, componentDidUpdate and componentWillUnmount:

class MyComponent extends React.Component {

  componentDidMount() {
    // Setup logic
  }

  componentDidUpdate(prevProps, prevState) {
     // Respond to updates
  }

  componentWillUnmount() {
    // Tear down 
  }

}

Over time the lifecycle system grew confusing, with too many nuanced use cases to remember.

Hooks provide a simpler imperative API for these scenarios.

Tightly Coupled Components

Before Hooks, sharing stateful logic between components meant coupling them with render props or higher order components.

While useful at times, these patterns violated React principles of separation of concerns, often tying disparate components together just for reuse.

Custom Hooks enable much cleaner logic extraction without disruption.

But there are many other motivations behind the seismic shift introduced with Hooks:

  • Unifying the component model with less ???ifs and buts???
  • Eliminating confusion between function and class components
  • Encouraging smaller and more reusable functions
  • Avoiding intermediate variables leading to cleaner code
  • Increased opportunities for performance optimizations
  • Faster iteration on new features

In fact since adopting Hooks internally, Facebook teams reported being able to ship new code faster. Hooks influenced further improvements like Concurrent React for scheduling, priority and suspense.

Now that we‘ve covered why Hooks came to be, let‘s explore how they solve some long-standing React issues.

How Hooks Solve Key Problems with Class Components

While the motivation for Hooks is clear in the aggregate, understanding the specific problems they address helps explain the notable departure from classes.

Let‘s explore key areas where Hooks provide tangible improvements over prior patterns.

Eliminating Frustrating Prop Drilling

Imagine you have a user state defined in a top level AuthContext component:

<AuthContext.Provider value={{user}}>

  <SitePages>
    ...
    <Footer />
  </SitePages>

</AuthContext.Provider>

But now a nested Footer component needs to access and display the user.

Without Hooks, you would need to pass or ???drill??? the prop down through multiple intermediate layers first:

function SitePages(props) {
  return (
    <PagesLayout>
      <MainContent />
      <Footer user={props.user} />    
    </PagesLayout>
  );
}

This prop drilling makes components less reusable, loosely coupled and harder to maintain over time.

Hooks introduce a new useContext method that allows any component to plug directly into context providers above them:

function Footer() {

  const {user} = useContext(AuthContext);

  return (
    <footer>
      Welcome back {user.name}!
    </footer>
  );
}

No more drilling props through disinterested components!

Radically Simpler Shared Logic

Solutions did emerge for share stateful logic between class component with Higher Order Components and Render Callback props.

But both patterns came at a high cognitive cost for something so common:

class CommentList extends React.Component {
  // ...
}

// Higher Order Component
function withComments(Component) {
  return class WithComments extends React.Component {

    state = {
      comments: []
    }

    componentDidMount() {
      fetchComments().then(comments => this.setState({comments}));
    }

    render() {
      return <Component {...this.props} comments={this.comments} />
    }

  } 
}

export default withComments(CommentList);

This is a simple example, but in more complex scenarios HOCs can wrap components deep inside providers and consumers. This abstraction on abstraction makes code difficult to reason about.

With Custom Hooks, that same logic can be extracted using simple JavaScript functions:

function useComments() {
  const [comments, setComments] = React.useState([]);

  React.useEffect(() => {
    fetchComments().then(setComments); 
  })

  return comments; 
}


function CommentList(props) {
  const comments = useComments();
  return // display comments 
}

function OtherComponent() {
  const comments = useComments();
  return // reuse comments logic
}

Sharing stateful reusable logic is now clean and consistent across components.

Eliminating the Function vs Class Debate

When first learning React, developers often wrestle with whether to make new components using functions or classes:

  • Functions are simpler but limit you to only rendering logic
  • Classes enable state and lifecycles but require more code overhead

This early decision would have a cascading impact across application architecture further down the line.

Hooks allow function components to use most capabilities historically reserved for classes:

  • Local state with useState
  • Lifecycles with useEffect
  • Context via useContext
  • And more

This eliminates the need to prematurely choose one over the other ??? you can evolve the component as needed later without refactoring or rewriting entire files.

React finally has one unified component model instead of an awkward two.

Now that you see several compelling benefits provided by Hooks over strictly using classes, let‘s look at a few examples of Hooks in action.

Hooking Into Components: Practical Examples

While the documentation contains simple toy examples, real world usage of Hooks can look much different.

Let‘s walk through some practical scenarios where leveraging hooks would simplify component code.

Fetching Data

A common task in React apps is fetching data from an API endpoint:

class UserProfile extends React.Component {
  state = {
    user: null 
  };

  componentDidMount() {
    axios.get(‘/api/user‘)
      .then(user => this.setState({ user }));
  }

  render() {
    // reading this.state.user
  }
}

We can model the same logic with hooks:

function UserProfile() {

  const [user, setUser] = useState(null);  

  useEffect(() => {
    axios.get(‘/api/user‘).then(user => setUser(user));
  }, []);

  return (
    // reading user variable
  );
}

Fetching data or any side effects are handled cleanly in a single useEffect hook.

Using Multiple State Hooks

Components often need to manage multiple local state values or pieces of data:

class Form extends React.Component {

  state = {
    email: ‘‘,
    password: ‘‘,
    staySignedIn: false
  };

  // ...
}

With Hooks we can declare each value as standalone state variables using multiple state hooks:

function Form() {

  const [email, setEmail] = useState(‘‘);
  const [password, setPassword] = useState(‘‘);
  const [staySignedIn, setStaySignedIn] = useState(false);  

  // ...

}

Much cleaner approach without nested state objects!

Sharing Logic with Custom Hooks

Let‘s explore how reusable custom Hooks simplify logic sharing across components.

Suppose we have a common data fetching hook:

function useFetch(url) {

  const [data, setData] = useState(null);

  useEffect(() => {
    axios(url).then(response => setData(response.data)) 
  }, [url]);

  return data;
}

Any component can now reuse this hook:

function UsersList() {

  const users = useFetch(‘/api/users‘);

  // ...
}

function OrdersList() {

  const orders = useFetch(‘/api/orders‘);

  // ...
}

Custom hooks act as generic shelves to house logic applicable inside multiple unrelated components.

More Real-World Examples

To best showcase Hooks in action, let‘s explore fully implementing some common application scenarios:

Profile Dashboard Page

Dashboard profiles are common to display user data and statistics:

function Profile() {

  // Fetch profile data
  const {user, stats} = useProfileData(); 

  // Get favorites  
  const favorites = useFavorites(user.id);

  // Track visit count
  useTrackVisits(user.id)

  return (
    <header>
      <Avatar user={user} />

      <div>
        <UserName user={user} />  
        <Favorites favorites={favorites} />
      </div>

      <Stats stats={stats} />
    </header>
  );
}

With co-located hooks, components remain focused on UI code.

Shopping Cart

Shopping carts often involve multiple UI components with shared state:

function Cart() {

  const {items, total, qty} = useCart();

  return (
    <>
      <CartHeader qty={qty} />

      <CartList 
        items={items}
        onQuantityChange={newQty => updateItemQuantity(item.id, newQty)}  
      />

      <CartFooter total={total} onCheckout={checkout} />

    </>
  )

}

Again custom Cart hooks encapsulate related logic bits in one place.

Chat Application

Modern chat UIs have dynamic data loading needs:

function ChatThread() {

  const {messages} = useMessages(threadId);

  const {user} = useParticipantData(threadId);

  const atBottom = useRef(true);

  return (
    <div ref={chatThreadRef}>

      <Messages 
        messages={messages}
        onNewMessage={newMessage => 
          addMessage(threadId, newMessage)}
      />

      {atBottom &&
        <NewMessageInput 
          onSend={text => sendMessage(text)} />
      }

    </div>
  );
}

Here hooks help collect related data pieces for a feature.

Data Visualization Chart

Charts often involve aggregation and preparation logic:

function MetricsChart() {

  const [startDate, setStartDate] = useState(new Date());
  const [endDate, setEndDate] = useState(addDays(startDate, 7));

  const metrics = useMetrics(startDate, endDate);

  // ...

  return (
    <LineChart 
      metrics={metrics}
    />
  );
}


function useMetrics(start, end) {

  const [metrics, setMetrics] = useState();

  useEffect(() => {

    getMetrics(start, end).then(setMetrics);

  }, [start, end])

  return metrics;

}

Encapsulating chart data preparation avoids cluttering parent components.

These examples showcase how hooks improve component organization and readability.

Helpful Patterns for Adopting Hooks

While hooks can provide excellent long term improvements, incremental adoption is critical, especially for large codebases.

Here are some useful strategies as your team gets started with hooks:

Gradual Code Migration

  • First add hooks only to completely new screens and flows
  • Slowly refactor standalone components as needed
  • Set manageable goals for number of files migrated per sprint
  • Establish rules like "New federated modules must use hooks"

Hybrid Component Strategy

In larger apps it will be normal to have a mix of functional components with hooks and class-based components working together:

function ChatThread() {

  // uses hooks internally ...

  return (
     <OldMessageList />
     <OldMessageInput />
  )
}

class OldMessageList extends React.Component {
  // legacy class component
}

This hybrid approach allows incremental rewrite of complex areas over time.

Handle Deprecations

The React team has aimed to deprecate APIs like component lifecycles slowly:

class LegacyComponent extends React.Component {

  componentDidMount() {
    // ...
  }

  // Displays deprecation warning
  UNSAFE_componentWillMount() { 

  }

}

Opt-in to newer APIs like useEffect at your own pace while avoiding breakages.

Based on extensive internal testing at Facebook, Hooks are expected to completely supersede classes in most code within the next 1-2 years. But teams have flexibility over that timeline.

Key Takeaways and Continued Learning

We covered a lot of ground explaining React Hooks! Let‘s recap some key learnings:

  • Hooks resolve long-standing issues with classes like confusing lifecycles, prop drilling and complex logic sharing
  • useState, useEffect and useContext allow dramatic simplifications from legacy components
  • Custom hooks enable clean extraction and reuse of logic across components
  • Code examples showcase how hooks improve readability and organization
  • Teams have flexibility in migration approach given deprecation warnings

For more hands-on experience with hooks, some helpful resources:

I encourage you to try hooks for yourself in an upcoming project. I think you‘ll be delighted by the developer experience benefits they unlock!

Let me know if you have any other questions. Happy reacting my friend!