In this guide, we’ll learn how to use the `useEffect` hook in React to manage side effects such as triggering actions after a state change.
This is a powerful pattern, especially when you want to respond to a state like `loading` and do something like:
- Fetch data
- Start a timeout
- Perform animations
- Or any side-effect that should happen after a certain condition.
Let’s build it step-by-step!
Props and Union Types Setup
First, we define the props for our button and a union type to manage different states.
This gives us the structure for a component that can react to different request states in a type-safe way.
The MyButton Component with useEffect
Now we build the component logic. This time, instead of triggering the state change inside the click handler, we just set it to `'loading'`.
The actual work (side-effect) happens inside `useEffect`.
What's happening here?
- On button click → state becomes `'loading'`.
- `useEffect` sees that status changed to `'loading'`, and starts a `setTimeout`.
- After 1 second, the request becomes `'success'`, and button toggle logic runs.
- There’s a cleanup function to clear timeout if the component unmounts.
MyApp Component to Render the Button
Finally, we wrap the `MyButton` inside a simple app:
Why useEffect is Better Here?
This pattern separates the event trigger (click) from the side effect logic (timeout and state change).
That makes the code more declarative, easier to test, and scalable for things like:
- Fetching data from APIs
- Reacting to prop changes
- Animations or DOM manipulations
Summary
- We used useEffect to handle what happens after a click event.
- Combined with useState and union types, this is a powerful structure for real-world apps.
- Always remember to cleanup side effects (like timeouts) inside `useEffect`.
Let me know if you'd like to see this example expanded with API calls or error handling next!
This is a powerful pattern, especially when you want to respond to a state like `loading` and do something like:
- Fetch data
- Start a timeout
- Perform animations
- Or any side-effect that should happen after a certain condition.
Let’s build it step-by-step!

First, we define the props for our button and a union type to manage different states.
JavaScript:
interface MyButtonProps {
title: string;
disabled: boolean;
}
type RequestState<T = unknown> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };

Now we build the component logic. This time, instead of triggering the state change inside the click handler, we just set it to `'loading'`.
The actual work (side-effect) happens inside `useEffect`.
JavaScript:
import { useEffect, useState } from "react";
function MyButton({ title, disabled }: MyButtonProps) {
const [requestState, setRequestState] = useState<RequestState<string>>({ status: 'idle' });
const [enabled, setEnabled] = useState<boolean>(true);
const handleClick = () => {
setRequestState({ status: 'loading' }); // Trigger side-effect
};
useEffect(() => {
if (requestState.status === 'loading') {
const timeout = setTimeout(() => {
setRequestState({ status: 'success', data: 'Success!' });
setEnabled((prev) => !prev);
}, 1000);
// Cleanup function
return () => clearTimeout(timeout);
}
}, [requestState.status]);
return (
<>
<button disabled={disabled || !enabled} onClick={handleClick}>
{title}
</button>
<p>Status: {requestState.status}</p>
{requestState.status === 'success' && <p>Result: {requestState.data}</p>}
</>
);
}
- On button click → state becomes `'loading'`.
- `useEffect` sees that status changed to `'loading'`, and starts a `setTimeout`.
- After 1 second, the request becomes `'success'`, and button toggle logic runs.
- There’s a cleanup function to clear timeout if the component unmounts.

Finally, we wrap the `MyButton` inside a simple app:
JavaScript:
export default function MyApp() {
return (
<div>
<h1>My App</h1>
<MyButton title="I am a button" disabled={false} />
</div>
);
}

This pattern separates the event trigger (click) from the side effect logic (timeout and state change).
That makes the code more declarative, easier to test, and scalable for things like:
- Fetching data from APIs
- Reacting to prop changes
- Animations or DOM manipulations

- We used useEffect to handle what happens after a click event.
- Combined with useState and union types, this is a powerful structure for real-world apps.
- Always remember to cleanup side effects (like timeouts) inside `useEffect`.
Let me know if you'd like to see this example expanded with API calls or error handling next!
