React Hooks: Simplifying Your React Development

Ashish Misal
5 min readJan 28, 2025

--

If you're working with React, you’ve probably heard about React Hooks. But if you're still unsure about what they are or how they can make your life easier as a developer, you're in the right place. In this article, I'll break down React Hooks in a simple, straightforward way.

What Are React Hooks?

In simple terms, React Hooks are JavaScript functions that let you use React features like state and lifecycle methods in functional components. Before hooks, React developers had to rely on class components for managing things like state and lifecycle events. Hooks brought a better, simpler way to handle these features inside functional components, making them a game-changer for React development.

Why Should You Care About Hooks?

If you’re like most developers, you probably prefer writing clean, easy-to-read code. Functional components are already simpler and more concise than class components, and hooks take that simplicity even further. They let you manage state, handle side effects, and much more, all without needing to deal with the complexity of class-based components.

Common React Hooks You’ll Use

1. useState()

The useState hook is one of the most used hooks in React. It lets you add state to your functional components.

Example:

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0); return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}

export default Counter;

In the example above:

  • useState(0) initializes the state variable count with a value of 0.
  • setCount is the function you use to update the state.

2. useEffect()

The useEffect hook is for handling side effects in your components. Side effects include tasks like data fetching, updating the DOM, or subscribing to an external data source.

Example:

import React, { useState, useEffect } from 'react';
function FetchData() {
const [data, setData] = useState([]);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
}, []); // Empty dependency array means this effect runs once after the component mounts
return (
<div>
<h2>Data</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default FetchData;

Here, useEffect is used to fetch data when the component mounts. The empty dependency array [] ensures that this effect runs only once.

3. useContext()

The useContext hook makes it easier to share state or values across different components, without passing props manually at every level.

Example:

import React, { createContext, useContext, useState } from 'react';

// Create a Context
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');

return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

function ThemedComponent() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button>
</div>
);
}

function App() {
return (
<ThemeProvider>
<ThemedComponent />
</ThemeProvider>
);
}

export default App;

In this example, useContext helps us access the theme value and setTheme function from any component that is within the ThemeProvider.

4. useReducer()

If you have more complex state logic, useReducer is a great alternative to useState. It’s perfect for managing state that involves multiple values or actions.

Example:

import React, { useReducer } from 'react';

const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}

export default Counter;

With useReducer, we can define a reducer function to manage more complex state logic, making it easier to update the state based on different actions.

5. useRef()

The useRef hook allows you to persist values across renders without causing re-renders. It’s commonly used to reference DOM elements or store mutable values.

Example:

import React, { useRef } from 'react';

function FocusInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}

export default FocusInput;

Explanation:

  • useRef(null) creates a reference object.
  • inputRef.current points to the DOM element.
  • Clicking the button calls focusInput, setting focus to the input field.

6. useMemo()

The useMemo hook optimizes performance by memoizing expensive calculations and recomputing them only when dependencies change.

Example:

import React, { useState, useMemo } from 'react';

function ExpensiveCalculation({ num }) {
const squaredNumber = useMemo(() => {
console.log('Calculating square...');
return num * num;
}, [num]);
return <p>Squared Number: {squaredNumber}</p>;
}
function App() {
const [count, setCount] = useState(0);
return (
<div>
<ExpensiveCalculation num={count} />
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

export default App;

Explanation:

  • The useMemo hook caches the squared number and only recalculates it when num changes.
  • This improves performance by preventing unnecessary recalculations.

7. useCallback()

The useCallback hook memoizes functions to prevent unnecessary re-creations when components re-render.

Example:

import React, { useState, useCallback } from 'react';

function Button({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<Button onClick={handleClick} />
</div>
);
}

export default App;

Explanation:

  • useCallback ensures handleClick is not recreated unless count changes.
  • This avoids unnecessary re-renders of the Button component.

8. useLayoutEffect()

The useLayoutEffect hook works similarly to useEffect, but it runs synchronously after DOM mutations, before the browser paints the screen.

Example:

import React, { useState, useLayoutEffect, useRef } from 'react';

function LayoutEffectExample() {
const inputRef = useRef(null);
const [text, setText] = useState('Hello');
useLayoutEffect(() => {
console.log('useLayoutEffect running');
inputRef.current.value = text;
}, [text]);
return (
<div>
<input ref={inputRef} />
<button onClick={() => setText('Updated Text')}>Update</button>
</div>
);
}

export default LayoutEffectExample;

Explanation:

  • useLayoutEffect updates the input field before the screen paints, ensuring no flickering.
  • Runs synchronously after rendering but before the browser updates the UI.

9. useImperativeHandle()

The useImperativeHandle hook customizes the instance value exposed when using React.forwardRef.

Example:

import React, { useImperativeHandle, useRef, forwardRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
focus: () => {
ref.current.focus();
},
}));
return <input ref={ref} />;
});
function App() {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</div>
);
}
export default App;

Explanation:

  • useImperativeHandle exposes a focus method that allows the parent to control the child component.
  • React.forwardRef is used to pass the ref to CustomInput.

10. useDebugValue()

The useDebugValue hook is used to display custom labels in React DevTools for debugging.

Example:

import { useState, useDebugValue } from 'react';
function useCustomHook(initialValue) {
const [value, setValue] = useState(initialValue);
useDebugValue(value > 5 ? 'High' : 'Low');
return [value, setValue];
}
function App() {
const [count, setCount] = useCustomHook(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App;

Explanation:

  • useDebugValue provides useful labels for debugging custom hooks in React DevTools.

When Should You Use Hooks?

  • React Hooks are the best option when:
  • You’re working with functional components.
  • You need to manage state, side effects, or context.
  • You want to write more readable and maintainable code without dealing with class components.

React Hooks have made it easier than ever to manage state, side effects, and other React features in functional components. By embracing hooks, you can write simpler, more readable code while keeping all the power of React at your fingertips. If you’re not using hooks yet, now is the perfect time to start.

Give them a try once you get the hang of hooks, you'll wonder how you ever built React apps without them!

Feel Free to Reach me on Linkedin : Ashish Misal

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Ashish Misal
Ashish Misal

Written by Ashish Misal

Software Developer | Expert in JavaScript, Node.js, React, MERN Stack | Building scalable apps | Mentor for developers | Passionate about innovation

No responses yet

Write a response