Overview
With React Hooks, developers have introduced a paradigm shift in managing state and side effects in functional components. By combining it with TypeScript, developers can work with type-safe code. This guide will teach you how to use React Hooks in TypeScript, including useState, useEffect, useContext, and creating custom hooks.
Suppose you are new to React TypeScript or in your early days. I have published an article on “How to Get Started with React TypeScript”, which I highly recommend you read if you are experiencing difficulties or are still in your early days of understanding React TypeScript.
useState Hook
Using the useState hook, functional components can maintain state. It's used to create state variables within functional components, so they can hold and update state values as their lifecycle progresses.
Importing the Hook
To use the useState hook, you must import it from the React library. This hook is then used within your functional component to manage state.
import React, { useState } from 'react';
Defining the State
The state variable is defined within your functional component using the useState hook. The hook returns an array with two elements: a value that represents the current state, and a function that updates it.
const [count, setCount] = useState<number>(initialValue);
In the example above, count represents the current value of the state variable, and setCount updates its value. The useState function takes an initial value (initialValue) as its argument.
Updating the State
React will then re-render the component with the updated state by calling the setter function (setCount in this example).
const increment = () => {
setCount(count + 1);
};
With increment, setCount is incremented by 1 when called.
Rendering the State
To display the current value of the state variable, you can render it in the JSX of your component.
return (
<div>
<p>{`Count: ${count}`}</p>
<button onClick={increment}>Increment</button>
</div>
);
Count is displayed in the paragraph element ("count: $count"), and a button is provided to trigger the increment function.
By using the useState hook, functional components can have stateful behavior without the need to be converted into class components, simplifying state management. In React development, it's a fundamental tool for managing component state in a concise and readable way.
Complete UseState Hook Code Example
The useState Hook manages the state of components
UseState hooks allow functional components to manage state.
// Example: useState Hook with TypeScript
import React, { useState } from 'react';
interface CounterProps {
initialValue: number;
}
const Counter: React.FC<CounterProps> = ({ initialValue }) => {
const [count, setCount] = useState<number>(initialValue);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>{`Count: ${count}`}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
useEffect Hook
In React, the useEffect hook allows you to handle side effects in functional components. For example, data fetching, subscriptions, or manually changing the DOM can be handled by this hook.
Importing the Hook
First, you import the useEffect hook from the React library. This hook allows you to perform side effects on functional components.
import React, { useState, useEffect } from 'react';
Defining the Effect
As a first argument, you pass the function that represents the side effect you wish to perform to the useEffect hook within the functional component.
useEffect(() => {
const fetchDataAsync = async () => {
const result = await fetchData();
setData(result);
};
fetchDataAsync();
}, [fetchData]);
It runs after the component renders and whenever the fetchData function changes (as specified by the dependency array [fetchData]).
Cleaning Up
If your side effect requires any cleanup, such as unsubscribing from subscriptions or canceling network requests, you can return a cleanup function from it.
useEffect(() => {
// Side effect
return () => {
// Cleanup
};
}, [/* dependencies */]);
The cleanup function is omitted in this example since no cleanup is required.
Rendering the Result
The result of the side effect can be rendered in your component's JSX.
return (
<div>
<p>{`Data: ${data}`}</p>
</div>
);
The fetched data (data) is rendered in a paragraph element.
React apps use the useEffect hook to ensure side effects are handled predictably and consistently. It's an essential tool for managing component lifecycles and handling asynchronous operations.
Complete Code Example of UseEffect Hook
Managing side effects with useEffect Hook
In functional components, the useEffect hook is used to handle side effects.
// Example: useEffect Hook with TypeScript
import React, { useState, useEffect } from 'react';
interface DataDisplayProps {
fetchData: () => Promise<string>;
}
const DataDisplay: React.FC<DataDisplayProps> = ({ fetchData }) => {
const [data, setData] = useState<string>('');
useEffect(() => {
const fetchDataAsync = async () => {
const result = await fetchData();
setData(result);
};
fetchDataAsync();
}, [fetchData]);
return (
<div>
<p>{`Data: ${data}`}</p>
</div>
);
};
export default DataDisplay;
useContext Hook
React uses the useContext hook to access context values within functional components. Context provides a way to pass data through the component tree without having to manually pass props at every level.
Creating Context
A context is created by using React.createContext(). You provide an initial value and define the shape of the context value, usually using an interface. This example contains a user object and a function that updates the user.
const UserContext = React.createContext<UserContextValue | undefined>(undefined);
Providing Context
The next step is to create a provider component (UserProvider in this case) that wraps the part of the component tree where the context should be available. By using the value prop of the Provider component, you define the context value and provide it to the context.
const UserProvider: React.FC = ({ children }) => {
const [user, setUser] = useState<User>({ name: 'John Smith', age: 45 });
const updateUser = (newUser: User) => {
setUser(newUser);
};
const contextValue: UserContextValue = { user, updateUser };
return <UserContext.Provider value={contextValue}>{children}</UserContext.Provider>;
};
Consuming Context
When you use the useContext hook, you pass the context object (in this case, UserContext) as an argument, and useContext returns the current context value.
const { user, updateUser } = useContext(UserContext);
Using Context Values
In this example, the UserProfile component renders the user's name and age, and provides a button to update the user's information once it has been accessed.
const handleUpdateUser = () => {
updateUser({ name: 'Jane Doe', age: 30 });
};
The button clicks the updateUser function provided by the context, which updates the user's information.
With the useContext hook, you can create more modular and reusable components in your React applications by making it easier to access context values within functional components.
Complete Code Example of UseContextHook
Using the useContext hook to access context values
React context values can be accessed using the useContext hook.
// Example: useContext Hook with TypeScript
import React, { useContext, useState } from 'react';
interface User {
name: string;
age: number;
}
interface UserContextValue {
user: User;
updateUser: (newUser: User) => void;
}
const UserContext = React.createContext<UserContextValue | undefined>(undefined);
interface UserProviderProps {
children: React.ReactNode;
}
const UserProvider: React.FC<UserProviderProps> = ({ children }) => {
const [user, setUser] = useState<User>({ name: 'John Smith', age: 45 });
const updateUser = (newUser: User) => {
setUser(newUser);
};
const contextValue: UserContextValue = { user, updateUser };
return <UserContext.Provider value={contextValue}>{children}</UserContext.Provider>;
};
const UserProfile: React.FC = () => {
const context = useContext(UserContext);
const user = context?.user;
const updateUser = context?.updateUser;
const handleUpdateUser = () => {
if (updateUser) {
updateUser({ name: 'Jane Doe', age: 30 });
}
};
return (
<div>
{user && (
<>
<p>{`Name: ${user.name}, Age: ${user.age}`}</p>
<button onClick={handleUpdateUser}>Update User</button>
</>
)}
</div>
);
};
export { UserProvider, UserProfile };
Custom Hooks
Using React hooks, you can encapsulate and reuse logic across multiple components. These JavaScript functions use React hooks (like useState and useEffect) and can be shared and reused across your application. In the example provided, the custom hook useTimer works as follows:
Defining the Custom Hook
It defines a useTimer function that takes an object of options (UseTimerOptions) as its argument, and returns a result object (TimerResult). This hook initializes state and sets up an effect to handle the timer logic.
const useTimer = ({ interval }: UseTimerOptions): TimerResult => {
// State for storing the current time
const [time, setTime] = useState<number>(0);
// Effect to update time at a regular interval
useEffect(() => {
const timerId = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, interval);
return () => {
clearInterval(timerId); // Cleanup: clear interval on component unmount
};
}, [interval]);
// Function to reset the timer
const reset = () => {
setTime(0);
};
return { time, reset };
};
Using the Custom Hook
In any component that needs timer functionality, you can use the Custom Hook to destructure the object that contains the timer value and reset function.
const { time, reset } = useTimer({ interval: 1000 });
Time is the current time value maintained by the timer, and reset is the function that resets the timer back to its initial value.
Reusing Logic
By encapsulating timer logic within the useTimer custom hook, you can easily reuse it across multiple components without duplicating code. This promotes code reusability and maintainability.
const TimerComponent: React.FC = () => {
const { time, reset } = useTimer({ interval: 1000 });
return (
<div>
<p>{`Time: ${time}`}</p>
<button onClick={reset}>Reset Timer</button>
</div>
);
};
TimerComponent uses the useTimer hook to display the current time and allow users to reset the timer.
Using custom hooks in React, you can extract complex logic into reusable functions, improving code organization and simplifying testing.
Complete Code Example Custom Hooks
Reusable logic with custom hooks
Encapsulate and reuse logic across components with custom hooks.
// Example: Custom Hook with TypeScript
import { useState, useEffect } from 'react';
interface UseTimerOptions {
interval: number;
}
interface TimerResult {
time: number;
reset: () => void;
}
const useTimer = ({ interval }: UseTimerOptions): TimerResult => {
const [time, setTime] = useState<number>(0);
useEffect(() => {
const timerId = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, interval);
return () => {
clearInterval(timerId);
};
}, [interval]);
const reset = () => {
setTime(0);
};
return { time, reset };
};
export default useTimer;
Summary
In functional components, React Hooks enables the management of state and side effects in a powerful and concise way. TypeScript allows developers to capture potential errors at compile-time rather than run-time when combined with static typing. You can use these hooks to encapsulate reusable logic, explore other hooks provided by React, and create your own custom hooks. A powerful combination of React Hooks and TypeScript will make your React development experience easier and more productive.
Please do not forget to like this article if you have found it useful and follow me on my LinkedIn https://www.linkedin.com/in/ziggyrafiq/ also I have uploaded the source code for this article on my GitHub Repo: https://github.com/ziggyrafiq/react-hooks-typescript-guide and recently I have published a book on Amazon “Understanding C#12 Coding Standards, Best Practices, and Standards in the Industry: Developing Robust and Maintainable Code in Today's Development Environment” recommend you reading it as it provides lots of code examples and industry standards and best practices using C# 12.