React Hooks

React Hooks

React Hooks changed the way developers write components the moment they landed in React 16.8, and years later they still trip up beginners and even experienced developers who jumped straight into hooks without understanding what problem they actually solve. If you have ever stared at useEffect wondering why your component keeps re-rendering in an infinite loop, or wondered why useState sometimes feels like it is lying to you about the current value, this guide is for you. We are going to break down React Hooks in plain language, with real examples you can copy into your own projects today.

React Hooks Simplified: What They Really Are

At their core, hooks are just functions that let you tap into React features from inside function components. Before hooks existed, if you wanted state or lifecycle methods, you had to write a class component. Classes brought a lot of baggage with them including confusing this bindings, verbose syntax, and logic that got scattered across multiple lifecycle methods like componentDidMount and componentDidUpdate even when that logic was really about a single concern.

Hooks solved this by letting you write everything inside a function component. No more this.state, no more binding methods in a constructor, and no more splitting one feature across three different lifecycle methods. Instead you write small, focused functions that each handle one job, and React gives you special functions called hooks to plug into rendering, state, and side effects.

The two hooks you will use constantly are useState and useEffect, so let us start there.

Understanding useState Without the Confusion

useState is how a function component remembers something between renders. Every time your component renders, all its local variables normally get wiped out and recreated. useState is special because React stores the value outside the render cycle and hands it back to you every time.

Here is the simplest possible example.

const [count, setCount] = useState(0)

This line does two things. It creates a variable called count that starts at zero, and it gives you a function called setCount that you use to update that value. You never change count directly. You always call setCount, and React handles re-rendering your component with the new value.

A common beginner mistake is trying to update state like this:

count = count + 1

This will not work and will likely throw an error or simply do nothing visible. State in React is immutable from the outside. You always go through the setter function:

setCount(count + 1)

Another subtlety that trips people up is that state updates are not always immediate. If you call setCount and then immediately try to log count on the next line, you will often see the old value. That is because React batches updates and schedules a re-render rather than updating the variable instantly. If you need to update state based on its previous value, use the functional form instead:

setCount(prevCount => prevCount + 1)

This pattern avoids bugs where multiple rapid state updates step on each other, which is especially important in things like counters, form inputs, and anything triggered by fast user interaction like scrolling or typing.

Getting Comfortable With useEffect

If useState is about remembering values, useEffect is about reacting to changes and running side effects. A side effect is anything that reaches outside the pure rendering of your component, things like fetching data from an API, subscribing to an event, manually changing the DOM, or setting up a timer.

The basic shape looks like this.

useEffect(() => {
console.log(‘Component rendered’)
})

Without a second argument, this effect runs after every single render. That is rarely what you want, so React lets you control exactly when the effect fires using a dependency array.

useEffect(() => {
console.log(‘Runs once on mount’)
}, [])

An empty array means the effect runs only once, right after the first render, similar to componentDidMount in the old class world. This is the pattern you will use constantly for things like fetching initial data when a page loads.

useEffect(() => {
console.log(‘Runs when userId changes’)
}, [userId])

Adding a value to the array tells React to re-run the effect only when that specific value changes between renders. This is where a lot of bugs come from. If you use a variable inside your effect but forget to add it to the dependency array, your effect will use a stale, outdated version of that variable, leading to subtle and frustrating bugs that are hard to trace.

A practical example is fetching user data whenever a userId prop changes.

useEffect(() => {
fetch(https://api.example.com/users/${userId})
.then(response => response.json())
.then(data => setUser(data))
}, [userId])

Every time userId changes, this effect fires again and pulls fresh data. Miss adding userId to that array and your component will keep showing data for the very first user it loaded, even after the prop updates.

Cleaning Up After Yourself

Some side effects need to be cleaned up when a component unmounts or before the effect runs again. A classic example is a timer.

useEffect(() => {
const timer = setInterval(() => {
console.log(‘tick’)
}, 1000)

return () => clearInterval(timer)
}, [])

The function you return from inside useEffect is the cleanup function. React calls it automatically before running the effect again and when the component is removed from the screen. Skipping cleanup is one of the most common causes of memory leaks in React apps, especially with event listeners and subscriptions that keep firing long after a component has disappeared.

Custom Hooks: Reusing Logic Without Repeating Yourself

Once you understand useState and useEffect, the real power of hooks becomes obvious when you start writing your own. A custom hook is simply a function whose name starts with use and that calls other hooks inside it. This lets you extract logic and reuse it across multiple components without copying and pasting code.

Here is a small custom hook that tracks whether the browser window is currently online.

function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine)

useEffect(() => {
const goOnline = () => setIsOnline(true)
const goOffline = () => setIsOnline(false)

window.addEventListener('online', goOnline)
window.addEventListener('offline', goOffline)

return () => {
  window.removeEventListener('online', goOnline)
  window.removeEventListener('offline', goOffline)
}

}, [])

return isOnline
}

Now any component in your app can use this with a single line.

const isOnline = useOnlineStatus()

This is the real magic of hooks. Logic that used to require higher order components or render props, both of which added extra layers of nesting to your component tree, can now be extracted into a clean, readable function that any component can call directly.

Other Hooks Worth Knowing

Beyond useState and useEffect, a handful of other built in hooks solve specific problems well.

useContext lets you read values from React context without wrapping your component in a consumer. This is useful for things like themes, authentication status, or language settings that many components across your app need access to without passing props down manually through every level.

useRef gives you a mutable object that persists across renders without causing a re-render when it changes. It is commonly used to grab a reference to a DOM element directly, for example focusing an input field automatically.

const inputRef = useRef(null)

useEffect(() => {
inputRef.current.focus()
}, [])

return <input ref={inputRef} />

useMemo and useCallback are performance tools. useMemo caches the result of an expensive calculation so it does not run on every render unless its dependencies change. useCallback does something similar but for functions, which matters when you are passing callbacks down to child components that are wrapped in React.memo, since a new function reference on every render would defeat the memoization.

const expensiveResult = useMemo(() => computeSomethingHeavy(data), [data])

const handleClick = useCallback(() => {
doSomething(id)
}, [id])

A word of caution here. Do not reach for useMemo and useCallback everywhere by default. They add their own overhead and complexity, and in many components the cost of recalculating something small is lower than the cost of managing the memoization itself. Use them when you have actually measured a performance problem, not as a habit.

Common Mistakes to Avoid

The rules of hooks matter more than most people realize at first. Hooks must always be called in the same order on every render, which means you cannot put them inside conditions, loops, or nested functions. If you write something like this:

if (isLoggedIn) {
useEffect(() => { … })
}

React will throw an error or behave unpredictably, because it tracks hooks by the order they are called, not by name. If you need conditional behavior, put the condition inside the hook instead.

useEffect(() => {
if (isLoggedIn) {
// do something
}
}, [isLoggedIn])

Another mistake is treating useEffect as a replacement for every lifecycle need without thinking about whether you actually need an effect at all. If you are just deriving one piece of state from another, you often do not need useEffect at all. You can calculate the derived value directly during render.

Instead of this pattern:

useEffect(() => {
setFullName(firstName + ‘ ‘ + lastName)
}, [firstName, lastName])

You can simply write:

const fullName = firstName + ‘ ‘ + lastName

This avoids an unnecessary extra render and keeps your component simpler to reason about.

Bringing It All Together

React Hooks were designed to make components easier to read, easier to test, and easier to reuse. Once the mental model clicks, the code you write becomes noticeably shorter and more predictable than the equivalent class based version. Start by getting comfortable with useState for local values and useEffect for anything that reaches outside the component. From there, learn to recognize when logic should be extracted into a custom hook, and use useContext, useRef, useMemo, and useCallback as targeted tools rather than defaults you sprinkle everywhere.

The best way to actually internalize hooks is to rebuild something you already know well, like a simple todo list or a small weather widget, using only function components and hooks. You will run into the dependency array issue at least once, you will probably forget a cleanup function at some point, and that is fine. Those small mistakes are exactly how the rules of hooks stop feeling like rules and start feeling like common sense.

Name

Frequently Asked Questions

What are React Hooks and why are they used?

React Hooks are functions that allow you to use state and other React features in functional components, making it easier to manage and reuse code. They provide a way to “hook into” React’s functionality and are a replacement for class-based components. This makes your code more concise and easier to understand.

What is the difference between useState and useEffect Hooks?

The useState Hook is used to add state to functional components, while the useEffect Hook is used to handle side effects, such as fetching data or setting timers. The useEffect Hook is typically used in conjunction with the useState Hook to update the state after a side effect has occurred. This helps to keep your code organized and easy to maintain.

Can I use multiple Hooks in a single component?

Yes, you can use multiple Hooks in a single component, and this is a common practice. You can use multiple useState Hooks to manage different pieces of state, or combine useState and useEffect Hooks to manage state and handle side effects. This makes it easy to break down complex functionality into smaller, more manageable pieces.

What are the rules for using React Hooks?

There are two main rules for using React Hooks: they must be used at the top level of a component, and they must be used in the same order every time the component is rendered. This ensures that the Hooks are always called in the correct order and that the component’s state is properly updated. Following these rules helps to prevent bugs and makes your code more predictable.

How do I know which Hook to use in a given situation?

The choice of Hook depends on the specific needs of your component, such as whether you need to manage state or handle side effects. The useState Hook is a good choice for managing simple state, while the useEffect Hook is better suited for handling side effects. You can also use other Hooks, such as useContext or useReducer, depending on the specific requirements of your component.

admin
admin
https://www.thefullstack.co.in