React Hooks revolutionized the way developers write functional components by allowing them to use state, lifecycle methods, and other React features without writing a class. Since their introduction in React 16.8, hooks have become an essential part of modern React development. In this article, we will explore all the built-in React hooks, explain their purpose, and provide TypeScript examples to demonstrate their usage.
1. useState
The useState
hook is the most commonly used hook in React. It allows functional components to manage state.
Syntax:
const [state, setState] = useState<Type>(initialState);
Example:
import React, { useState } from 'react';
const Counter: React.FC = () => {
const [count, setCount] = useState<number>(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default Counter;
Explanation:
-
useState
takes an initial state value and returns an array with two elements: the current state and a function to update it. - In the example,
count
is the state variable, andsetCount
is the function to update it.
2. useEffect
The useEffect
hook is used to perform side effects in functional components, such as fetching data, subscribing to events, or manually changing the DOM.
Syntax:
useEffect(() => {
// Side effect logic
return () => {
// Cleanup logic
};
}, [dependencies]);
Example:
import React, { useState, useEffect } from 'react';
const Timer: React.FC = () => {
const [seconds, setSeconds] = useState<number>(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
return () => clearInterval(interval); // Cleanup on unmount
}, []);
return <p>Seconds: {seconds}</p>;
};
export default Timer;
Explanation:
- The
useEffect
hook runs after every render by default. - The second argument is an array of dependencies. If any dependency changes, the effect runs again.
- If you return a function from
useEffect
, it will be used for cleanup (e.g., clearing timers or unsubscribing from events).
3. useContext
The useContext
hook allows you to access the value of a React context without wrapping your component in a Context.Consumer
.
Syntax:
const value = useContext<ContextType>(MyContext);
Example:
import React, { useContext } from 'react';
const ThemeContext = React.createContext<string>('light');
const ThemedComponent: React.FC = () => {
const theme = useContext(ThemeContext);
return <p>Current Theme: {theme}</p>;
};
export default ThemedComponent;
Explanation:
-
useContext
takes a context object and returns its current value. - The value is determined by the nearest
Context.Provider
above the component in the tree.
4. useReducer
The useReducer
hook is an alternative to useState
for managing complex state logic. It is inspired by Redux and uses a reducer function to handle state transitions.
Syntax:
const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, initialState);
Example:
import React, { useReducer } from 'react';
type State = { count: number };
type Action = { type: 'increment' } | { type: 'decrement' };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error('Unknown action type');
}
};
const Counter: React.FC = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
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;
Explanation:
-
useReducer
takes a reducer function and an initial state, and returns the current state and adispatch
function to trigger actions. - The reducer function determines how the state should be updated based on the action type.
5. useRef
The useRef
hook is used to create a mutable reference that persists across renders. It is commonly used to access DOM elements or store mutable values without triggering re-renders.
Syntax:
const ref = useRef<Type>(initialValue);
Example:
import React, { useRef, useEffect } from 'react';
const FocusInput: React.FC = () => {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return <input ref={inputRef} type="text" />;
};
export default FocusInput;
Explanation:
-
useRef
returns a mutable object with acurrent
property. - Changes to
ref.current
do not trigger re-renders.
6. useMemo
The useMemo
hook is used to memoize expensive calculations so that they are only recomputed when their dependencies change.
Syntax:
const memoizedValue = useMemo<Type>(() => computeExpensiveValue(a, b), [a, b]);
Example:
import React, { useMemo, useState } from 'react';
const ExpensiveComponent: React.FC = () => {
const [count, setCount] = useState<number>(0);
const [input, setInput] = useState<string>('');
const squaredValue = useMemo(() => {
console.log('Calculating squared value...');
return count * count;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Squared Value: {squaredValue}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<input value={input} onChange={(e) => setInput(e.target.value)} />
</div>
);
};
export default ExpensiveComponent;
Explanation:
-
useMemo
takes a function and an array of dependencies. It returns the memoized value, which is only recalculated when the dependencies change.
7. useCallback
The useCallback
hook is used to memoize functions so that they are only recreated when their dependencies change. This is useful for optimizing performance in child components that rely on reference equality.
Syntax:
const memoizedCallback = useCallback<Function>(() => {
// Function logic
}, [dependencies]);
Example:
import React, { useState, useCallback } from 'react';
const ChildComponent: React.FC<{ onClick: () => void }> = ({ onClick }) => {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Click Me</button>;
};
const ParentComponent: React.FC = () => {
const [count, setCount] = useState<number>(0);
const handleClick = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
};
export default ParentComponent;
Explanation:
-
useCallback
returns a memoized version of the function that only changes if one of the dependencies has changed.
8. useLayoutEffect
The useLayoutEffect
hook is similar to useEffect
, but it runs synchronously after all DOM mutations. Use this hook when you need to read layout from the DOM and re-render synchronously.
Syntax:
useLayoutEffect(() => {
// Synchronous logic
}, [dependencies]);
Example:
import React, { useLayoutEffect, useRef } from 'react';
const MeasureElement: React.FC = () => {
const ref = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
if (ref.current) {
console.log('Element width:', ref.current.offsetWidth);
}
}, []);
return <div ref={ref}>Measure Me</div>;
};
export default MeasureElement;
Explanation:
-
useLayoutEffect
is useful for DOM measurements or when you need to perform actions that depend on the DOM layout.
9. useImperativeHandle
The useImperativeHandle
hook customizes the instance value that is exposed when using ref
with forwardRef
.
Syntax:
useImperativeHandle(ref, () => ({
// Custom instance methods
}));
Example:
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
type InputRef = {
focus: () => void;
};
const CustomInput = forwardRef<InputRef>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => {
if (inputRef.current) {
inputRef.current.focus();
}
},
}));
return <input ref={inputRef} type="text" />;
});
const ParentComponent: React.FC = () => {
const inputRef = useRef<InputRef>(null);
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>Focus Input</button>
</div>
);
};
export default ParentComponent;
Explanation:
-
useImperativeHandle
is used to expose specific methods or properties to parent components.
10. useDebugValue
The useDebugValue
hook is used to display a label for custom hooks in React DevTools.
Syntax:
useDebugValue(value);
Example:
import { useState, useDebugValue } from 'react';
const useCustomHook = () => {
const [value] = useState<string>('Hello');
useDebugValue(value);
return value;
};
const DebugValueExample: React.FC = () => {
const value = useCustomHook();
return <p>{value}</p>;
};
export default DebugValueExample;
Explanation:
-
useDebugValue
is primarily used for debugging custom hooks.
Conclusion
React hooks provide a powerful and flexible way to manage state, side effects, and other features in functional components. By understanding and using these hooks effectively, you can write cleaner, more maintainable, and performant React applications. With TypeScript, you can add type safety to your hooks, making your code more robust and easier to debug.
Whether you're managing state with useState
, handling side effects with useEffect
, or optimizing performance with useMemo
and useCallback
, hooks are an essential tool in every React developer's toolkit. Happy coding!
Top comments (0)