Whether you need a high-performance web application, full-stack architecture advisory, or an engineer who ships with urgency and care.
Jump directly on my calendar to talk about goals, timeline, and fit.
useEffect hook is one of the most powerful and misunderstood hooks. It handles side effects, runs after rendering, replaces lifecycle methods, and synchronizes your component with external systems such as APIs, subscriptions, DOM manipulation, timers, or logging. However, as your application grows, useEffect can become a source of bugs, race conditions, duplicate network calls, and performance regressions if not used correctly. This blog covers useEffect usage, best practices, patterns, and pitfalls that even experienced developers run into.
React's render function must be pure meaning it cannot modify variables outside its scope, perform I/O, or change the DOM. Anything that breaks purity is a side effect, for example:
Fetching data
Subscribing to WebSockets
Setting timers
Writing to localStorage
Manipulating the DOM
Syncing app state with URL or props
useEffect allows these operations in a safe, predictable way.
useEffect Runs After Render (Not During Render)
This guarantees:
The DOM is updated before the effect runs.
Effects never block rendering.
Effects run asynchronously.
The dependency array controls when the effect should re-run.
Run on every render
Run only once (on mount)
Equivalent to componentDidMount.
Run when dependencies change
Equivalent to componentDidUpdate(userId).
Run on unmount (cleanup)
Equivalent to componentWillUnmount.
Cleanup is essential for:
unsubscribing event listeners
aborting fetch requests
closing WebSockets
clearing intervals
canceling timeouts
Every time the effect re-runs, cleanup executes before running the next effect.
This code causes an infinite loop
Because setCount changes state -> triggers re-render -> triggers effect -> repeats forever.
Fix using dependencies
Beginners often write
This works but has problems
Cannot cancel outdated requests
Race conditions when dependency changes
Causes warnings in React Strict Mode
Use useEffect Only for Side Effects
Not everything requires an effect.
Bad (overusing effects)
Better (derive inside render)
Effects should not calculate derived state, they only handle side effects.
Every value used inside an effect must appear in its dependency array.
Example: Missing dependency
React will warn you.
Correct
Consider:
Here, count is always the initial value.
Solutions
Add count as dependency (causes interval resets)
Use functional state updates
Use refs
Improved version
componentDidMount -> useEffect(..., [])
componentDidUpdate -> useEffect(..., [deps])
componentWillUnmount -> useEffect(() => return cleanup, [])
getDerivedStateFromProps -> Not directly an effect (use memo/derived data)
Running effects too often
Fix: Use proper dependencies.
Using objects/arrays without memoization
Fix:
Fetching data incorrectly
Fix: Use AbortController or a library like SWR/React Query.
Using effects to perform calculations
Fix: Calculate inside render or use useMemo.
Why do effects run twice in React Strict Mode?
To detect unsafe side effects.
How do you avoid stale closure bugs inside effects?
Functional updates or refs.
Why must all dependencies be listed?
To maintain predictable behavior and avoid stale values.
What is the difference between an effect and layout effect?
useLayoutEffect runs before the browser paints. useEffect runs after the paint.
How do you cancel network requests inside an effect?
AbortController.
Avoid effects when:
You’re computing derived state
You can use CSS instead of JS to manipulate DOM
You’re syncing two pieces of state
Example of overkill
Better
useEffect is extremely powerful, but with great power comes great responsibility. Used correctly, it allows your app to sync with the outside world safely. Used incorrectly, it creates bugs, unnecessary renders, and spaghetti logic.
Focus on
Use correct dependencies
Perform cleanups
Avoid overusing useEffect
Understand async behaviour
Prevent stale closures
Explore more articles on software architecture, frontend craftsmanship, and backend engineering.

The useWindowSize hook in react is very useful when you require javascript to get the size of the device and render the contents as required. It very useful when working with various sizes of devices. It can be useful when working with responsive designs. The following code demonstrates how one can implement the useWindowSize hook in their next project.
import { useEffect, useState } from "react";
export default function useWindowSize() {
// state of the window
const [windowSize, setWindowSize] = useState({ width: 0, height: 0, });
// function to handle window resizing.
const resizeWindow = () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
};
useEffect(() => {
resizeWindow();
// event listener for handling window size change.
window.addEventListener("resize", resizeWindow);
// cleanup the side effects
return () => window.removeEventListener("resize", resizeWindow);
}, []);
// return window size
return windowSize;
}You can use the hook in your required tsx/jsx file as follows:
import useWindowSize from "./useWindowSize";
export default function MyResponsiveComponent(){
const {width, height} = useWindowSize()
if (width > 375) {
return <TabletView/>
} else if (width > 1200) {
return <DesktopView/>
} else if (height > 1000) {
return <LargeHeightView/>
} else {
return <MobileView/>
}
}You can use the above code or modify to your own use in react to handle contents in various devices.

React provides several powerful hooks to help developers manage state and side effects inside functional components. One of the most commonly used hooks and usually the very first one you learn is the useState hook. If you’ve ever wanted a way for your component to “remember”(in memory) something and react when it changes, this guide explains everything you need.
useState is a React hook that allows you to add a state variable to your functional component. A state variable is any piece of data that React should track over time, and whenever it changes, your component re-renders to reflect the update.
Basic Syntax
const [state, setState] = useState(initialState); This returns an array with:
state : current value
setState : a function to update that value
React remembers the state across renders, updates it when setState is called, and re-renders the component with the new value.
Let’s look at a simple example where we store and update text from an input box:
import { useState } from "react";
export default function MyComponent() {
// initial state is an empty string
const [value, setValue] = useState("");
const updateValue = (e) => { setValue(e.target.value); };
return (
<div>
<input onChange={updateValue} placeholder="Change Value" />
<p>The state value is {value}</p>
</div>
);
}What happens here?
The state variable value starts as "".
When you type into the input, updateValue runs.
setValue updates the state.
The component re-renders, showing the new value.
You can set the initial state to anything:
useState(0) // number
useState("") // string
useState(null) // null
useState([]) // array
useState({}) // object
useState(() => computeExpensive()) // lazy initializationNote: If you start with null or undefined, make sure your code handles it safely.
Two Approaches
Set state directly
Useful when new state does not depend on the previous one.
setState("next value"); Functional updates
Recommended when previous state matters.
setState(prev => prev + 1);This ensures React calculates the next state using the latest value.
React batches updates for performance, meaning calling setState does not update the value immediately. Example that does NOT work as expected:
const [count, setCount] = useState(0);
setCount(count + 1); // setCount(0 + 1)
setCount(count + 1); // setCount(0 + 1)
setCount(count + 1); // setCount(0 + 1)All three updates use the same initial value (0) because they are queued before the component re-renders. Final result is 1, not 3.
Correct version:
setCount(prev => prev + 1); // setCount(0 + 1)
setCount(prev => prev + 1); // setCount(1 + 1)
setCount(prev => prev + 1); // setCount(2 + 1)Now React uses the updated value each time. Final result is 3.
Expecting state to update immediately
Beginners often write:
setValue("new");
console.log(value); // still oldReact updates state on the next render, not instantly.
Updating state based on old value without functional update
Incorrect:
setCount(count + 1);
setCount(count + 1);Correct:
setCount(prev => prev + 1);Mutating state directly
Example:
obj.name = "Rajesh";React won’t re-render.
Correct:
setStateObj(prev => ({ ...prev, name: "John" }));Forgetting that initial state is used only once
This does NOT recalculate on every re-render:
useState(Math.random());It runs only on the first render.
Using state when a variable would suffice
Not everything needs to be stored in state. If something doesn’t need to trigger a re-render, use useRef or a normal variable.
What does useState return?
A tuple: [state, setState].
Why does React batch state updates?
For performance, multiple updates happen in one render cycle.
Why should you use a functional update?
When the next state depends on the previous one.
Can you explain why reading state immediately after setting it returns the old value?
Because setState is asynchronous and scheduled for the next render.
What is lazy initialization in useState?
Passing a function to useState: useState(() => expensiveComputation()); The function runs only on the initial render.
Why can’t we conditionally call useState?
React hooks rely on consistent order across renders. Conditional hooks break that order.
useState is simple but incredibly powerful. It lets your components store data, respond to user input, and re-render when needed. Once you understand that state updates are asynchronous and how functional updates work, you’ll write far fewer bugs and much cleaner React code.

Creating a todo app in JS is easy as we write the code. Before starting without a project we must have a basic understanding of HTML, CSS, and JavaScript.
Basic knowledge of HTML
Basic knowledge of CSS
Basic knowledge of JS
Let's understand the app we are going to build. It will be a simple to-do app where users can add, edit, and delete items.
First, create a directory for the todo app. Create a file inside for HTML, CSS, and JavaScript.
mkdir todo && cd todotouch app.js && touch styles.css && touch index.htmlAdd the following code to index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/meistericons@latest/fonts/mni.css"
/>
<link rel="stylesheet" href="./styles.css" />
<title>Todo App - HTML, CSS and JS</title>
</head>
<body>
<main class="container">
<h1>Todos</h1>
<form class="">
<input
type="text"
name="todo"
id="todo"
placeholder="+ Add New Todo"
name="Take a walk"
/>
<button type="submit">Add</button>
</form>
<div class="tab_container">
<span class="active_tab tab">Remaning Todos</span>
<span class="tab">Completed Todos</span>
</div>
<strong class="active__todo">Remaning Todos</strong>
<ul class="todo__container"></ul>
</main>
<script src="./app.js"></script>
</body>
</html>Add the following code to styles.css
body,
html,
:root,
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 20rem;
height: 30rem;
border-radius: 2rem;
border: 1px solid rgb(34, 36, 192);
padding: 2rem;
position: fixed;
top: 10rem;
left: 38%;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin-bottom: 1rem;
}
div {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin: 1rem 0;
}
.todo__active {
border-bottom: 4px solid rgb(34, 36, 192);
}
.active__todo {
font-size: 1.25rem;
font-weight: 600;
}
ul {
margin-top: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
li {
list-style-type: none;
display: flex;
gap: 0.25rem;
}
label {
font-size: 1rem;
font-weight: 500;
}
.tab_container {
display: flex;
gap: 1rem;
}
.tab {
font-weight: 600;
font-size: 0.85rem;
}
.tab:hover {
cursor: pointer;
}
.active_tab {
border-bottom: 4px solid rgb(34, 36, 192);
font-weight: 700;
font-size: 1rem;
}
.todo__container {
height: 15rem;
overflow-y: scroll;
}
.todo {
display: flex;
align-items: center;
justify-content: space-between;
}
.todo__item__span {
display: flex;
align-items: center;
gap: 0.5rem;
}Add the following code to the app.js file.
// List of Tasks
let todos = [
{ title: "Example 1", completed: false },
{ title: "Example 2", completed: false },
{ title: "Example 3", completed: false },
];
const todoContainer = document.querySelector(".todo__container");
const remainingTodoTab = document.querySelectorAll(".tab")[0];
const completedTodoTab = document.querySelectorAll(".tab")[1];
const todoHeader = document.querySelector(".active__todo");
// RemainingTasks event listerner.
remainingTodoTab.addEventListener("click", () => {
completedTodoTab.classList.remove("active_tab");
remainingTodoTab.classList.add("active_tab");
todoHeader.innerHTML = "Remaining Todos";
clearTodoList();
todos
.filter(({ completed }) => !completed)
.forEach((remainingTodo) => {
createTodoItem(remainingTodo.title);
});
});
// CompletedTasks event listener.
completedTodoTab.addEventListener("click", () => {
remainingTodoTab.classList.remove("active_tab");
completedTodoTab.classList.add("active_tab");
todoHeader.innerHTML = "Completed Todos";
clearTodoList();
todos
.filter(({ completed }) => completed === true)
.forEach((completedTodo) => {
createTodoItem(completedTodo.title, true);
});
});
// Clear Task List
const clearTodoList = () => {
todoContainer.innerHTML = "";
};
/**
* Remove task from tasks list.
* @param {*} removeTodo - String to remove from tasks list.
*/
const removeTodo = (removeTodo) => {
todos = todos.filter((todo) => todo.title !== removeTodo);
};
/**
*
* @param {*} task - String to add to tasks list.
* @param {*} isCompleted - Status of the task.
*/
const createTodoItem = (task, isCompleted = false) => {
const newListItem = document.createElement("li");
newListItem.classList.add("todo");
const newTodoSpan = document.createElement("span");
newTodoSpan.classList.add("todo__item__span");
const newTodo = document.createElement("label");
const input = document.createElement("input");
const deleteIcon = document.createElement("i");
deleteIcon.classList.add("mni-delete-aB");
// Delete Task Click Event Listner.
deleteIcon.addEventListener("click", () => {
removeTodo(task);
newListItem.remove();
});
if (isCompleted) {
input.checked = true;
newListItem.style.textDecoration = "line-through";
} else {
input.checked = false;
}
input.type = "checkbox";
/**
* Update checkbox status.
* @param {*} e - Events in input element.
*/
input.onchange = (e) => {
const currentTodo = todos.find((todo) => todo.title === e.target.name);
if (e.target.checked) {
e.target.parentElement.style.textDecoration = "line-through";
currentTodo.completed = true;
} else {
currentTodo.completed = false;
e.target.parentElement.style.textDecoration = "none";
}
newListItem.remove();
};
input.name = task;
newListItem.appendChild(newTodoSpan);
newTodoSpan.appendChild(input);
newTodoSpan.appendChild(newTodo);
newListItem.appendChild(deleteIcon);
newTodo.innerHTML = task;
todoContainer.appendChild(newListItem);
if (!todos.length) {
todos.push({ title: task, completed: false });
}
};
// Create a list of tasks
todos.forEach((todo) => {
createTodoItem(todo.title);
});
// Add task to the list.
const addTodo = (e) => {
e.preventDefault();
const todo = document.getElementById("todo").value;
if (todo.length > 0 && remainingTodoTab.classList.contains("active_tab")) {
todos.push({ title: todo, completed: false });
createTodoItem(todo);
}
document.getElementById("todo").value = "";
};
document.querySelector("form").addEventListener("submit", addTodo);
Let's understand each of the lines of code.
Initially, a list of example tasks is added.
let todos = [
{ title: "Example 1", completed: false },
{ title: "Example 2", completed: false },
{ title: "Example 3", completed: false },
];We will add an event listener for the form submission and add a function for it.
const addTodo = (e) => {
e.preventDefault();
const todo = document.getElementById("todo").value;
if (todo.length > 0 && remainingTodoTab.classList.contains("active_tab")) {
todos.push({ title: todo, completed: false });
createTodoItem(todo);
}
document.getElementById("todo").value = "";
};
document.querySelector("form").addEventListener("submit", addTodo);We also need to create a function to append a new task list item after the user submits the form.
const createTodoItem = (task, isCompleted = false) => {};First, we create the tags necessary for the list item to check if the selected task is completed.
if (isCompleted) {
input.checked = true;
newListItem.style.textDecoration = "line-through";
} else {
input.checked = false;
}
input.type = "checkbox";Add an event handler for the input to update the completed and remaining tasks.
/**
* Update checkbox status.
* @param {*} e - Events in input element.
*/
input.onchange = (e) => {
const currentTodo = todos.find((todo) => todo.title === e.target.name);
if (e.target.checked) {
e.target.parentElement.style.textDecoration = "line-through";
currentTodo.completed = true;
} else {
currentTodo.completed = false;
e.target.parentElement.style.textDecoration = "none";
}
newListItem.remove();
};
input.name = task;Finally, append all the created tags to the html body
newListItem.appendChild(newTodoSpan);
newTodoSpan.appendChild(input);
newTodoSpan.appendChild(newTodo);
newListItem.appendChild(deleteIcon);
newTodo.innerHTML = task;
todoContainer.appendChild(newListItem);
if (!todos.length) {
todos.push({ title: task, completed: false });
}Now, let us add an event listener to our delete icon.
// Delete Task Click Event Listner.
deleteIcon.addEventListener("click", () => {
removeTodo(task);
newListItem.remove();
});
and the function to remove the task.
// Clear Task List
const clearTodoList = () => {
todoContainer.innerHTML = "";
};
/**
* Remove task from tasks list.
* @param {*} removeTodo - String to remove from tasks list.
*/
const removeTodo = (removeTodo) => {
todos = todos.filter((todo) => todo.title !== removeTodo);
};We have built a complete to-do app where users can add tasks. View Remaining and completed tasks. Update tasks to be completed and/or uncompleted. We have done it all using our knowledge of HTML, CSS, and javascript. You can now easily update the UI to your liking and extra features to the app to make it even better.

Todo app is a simple web app that can get you started with React. React makes it easy to accomplish our task.
Basic knowledge of React.
Basic knowledge of React Hooks.
Basic knowledge of HTML, CSS, and JavaScript.
Let's start by Creating a React app with the following command. For this example, we will be using the Vite to create the Todo React app
npm create vite@latest todo --tempalate react-swc-tsNavigate into the directory and install the dependencies.
cd todo && npm i && npm run devNow we can see the starter for a Vite React project. Edit the src/App.tsx page to get started.
Initially, create a component for the Input so that we can reuse it across the App.
//components/Input/Input.tsx
import { ComponentPropsWithoutRef } from "react";
import "./styles.css";
interface IInput extends ComponentPropsWithoutRef<"fieldset"> {
inputProps: ComponentPropsWithoutRef<"input">;
}
export default function Input({ inputProps }: IInput) {
return (
<fieldset className="input__container">
<label htmlFor="todo" className="input__label">
Add Todo
</label>
<input
{...inputProps}
type="text"
name="todo"
id="todo"
className="input__input"
/>
</fieldset>
);
}
Add the styles for it.
//components/Input/styles.css
.input__container {
display: flex;
flex-direction: column;
gap: 0.25rem;
border: none;
}
.input__label {
font-size: 1rem;
font-weight: 700;
}
.input__input {
padding: 0.5rem 1rem;
border-radius: 1rem;
border: 1px solid rgba(0, 0, 0, 0.4);
outline: none;
}Clear everything in the App.tsx and get started with the code below.
//src/App.tsx
<main className="container">
<h1 className="title">Todos</h1>
</main>We can now add a form to our UI to begin adding todos to our application.
<form onSubmit={handleAddTodo} className="add__todo__form">
<Input inputProps={{ name: "todo", value: todo, onChange: handleOnChange }} />
<button type="submit" className="add__todo__button">
Add
</button>
</form>To update the todos we need to listen for changes in our input. We can save our values from the input in a useState React hook.
const [todo, setTodo] = useState("");Similarly, add the onChange function to add our todo.
const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
setTodo(value);
};In this function, we listen for changes in the input and append the resulting value to our todo state using setTodo.
Similarly, the form will be submitted to the handleAddTodo function. Here, we prevent the default behavior of the browser and add todos to our setTodos useState hook.
const [todos, setTodos] = useState<{ title: string; completed: boolean }[]>([]);
const handleAddTodo = (e: SyntheticEvent) => {
e.preventDefault();
setTodos((prev) => [...prev, { title: todo, completed: false }]);
setTodo("");
};Continue by building tabs to view the remaining and completed todos. We can handle the state of our active tab by again using the useState React hook.
const [activeTab, setActiveTab] = useState("Remaining Todos");We can update the active tab value by listening to a click event in the tabs.
<div className="tabs__container">
{["Remaining Todos", "Completed Todos"].map((tab) => (
<button
key={tab}
className={`tabs__tab ${
activeTab === tab ? "tabs__tab__active" : ""
}`}
onClick={() => setActiveTab(tab)}
>
{tab}{" "}
{tab === "Remaining Todos"
? remainingTodos.length > 0 && `(${remainingTodos.length})`
: completedTodos.length > 0 && `(${completedTodos.length})`}
</button>
))}
</div>In the above onClick function we make use of the setActiveTab hook to update our activeTab value.
Now we can memoize our remaining and completed todos.
const remainingTodos = useMemo(() => {
return todos.filter((todo) => !todo.completed);
}, [todos]);
const completedTodos = useMemo(() => {
return todos.filter((todo) => todo.completed);
}, [todos]);Finally, render the list of our todos to the UI. Before, rendering the UI we must also check for the active tab state to get the active tab todos.
<ul className="todos__container">
{(activeTab === "Remaining Todos"
? remainingTodos
: completedTodos
).map((todo) => (
<li key={todo.title} className="todo__list__item">
<span className="todo__list__item__input__container">
<input
type="checkbox"
checked={todo.completed}
name={todo.title}
onChange={(e) => {
setTodos((prev) =>
prev.map((t) =>
t.title === todo.title
? { ...t, completed: e.target.checked }
: t
)
);
}}
/>
{todo.title}
</span>
<DeleteB
onClick={() => {
setTodos((prev) => prev.filter((t) => t.title !== todo.title));
}}
/>
</li>
))}
</ul>Add styles to the page.
//src/index.css
html,
body,
:root,
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.title {
font-size: 2.5rem;
font-weight: 900;
}
.add__todo__form {
display: flex;
align-items: flex-end;
gap: 1rem;
}
.add__todo__button {
padding: 0.5rem 1rem;
border-radius: 1.5rem;
border: none;
outline: none;
}
.container {
width: 25rem;
position: absolute;
top: 25%;
left: calc(50% - 10rem);
display: flex;
flex-direction: column;
gap: 1.25rem;
padding: 2rem 2.5rem;
border-radius: 2rem;
border: 1px solid rgb(58, 40, 216);
}
.todo__list__item {
list-style-type: none;
display: flex;
gap: 0.25rem;
align-items: center;
justify-content: space-between;
padding: 0.25rem 1rem;
}
.tabs__container {
display: flex;
align-items: center;
gap: 1rem;
}
.tabs__tab {
white-space: nowrap;
font-size: 1rem;
font-weight: 500;
outline: none;
border: none;
background: transparent;
}
.tabs__tab__active {
border-bottom: 1px solid blue;
}
.todos__container {
height: 20rem;
overflow-y: scroll;
}
.todo__list__item__input__container {
display: flex;
gap: 0.25rem;
align-items: center;
}Here, we created a to-do app in React using Vite and React hooks. You can now further update the UI and add additional functionalities to your to-do app to make it even better.