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.
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
Navigate into the directory and install the dependencies.
Now 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
Add the styles for it.
//components/Input/styles.css
Clear everything in the App.tsx and get started with the code below.
//src/App.tsx
We can now add a form to our UI to begin adding todos to our application.
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.
Similarly, add the onChange function to add our todo.
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.
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.
We can update the active tab value by listening to a click event in the tabs.
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.
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.
Add styles to the page.
//src/index.css
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.
Explore more articles on software architecture, frontend craftsmanship, and backend engineering.

Let's start by cloning our already made todo app. We will be testing the crud function and also checking for UI updates. Vitest is easy to get started with. Just follow along and you will begin testing your apps soon.
Add Vitest,jsdom and necessary testing libraries for UI testing to get started.
npm i -D vitest @testing-library/jest-dom @testing-library/user-event @types/jest @vitest/ui jsdomYou can additionally add the following scripts to package .json
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"coverage": "vitest run --coverage"
}Before testing our application, we need to set up some files to get started. Start by creating a new file test-utils that will have all the utilities for our tests.
//tests/test-utils.tsx
import { cleanup, render } from "@testing-library/react";
import { afterEach } from "vitest";
afterEach(() => {
cleanup();
});
function customRender(ui: React.ReactElement, options = {}) {
return render(ui, {
// wrap provider(s) here if needed
wrapper: ({ children }) => children,
...options,
});
}
export * from "@testing-library/react";
export { default as userEvent } from "@testing-library/user-event";
// override render export
export { customRender as render };//tests/setup.ts
import '@testing-library/jest-dom';We need now to update our Vite config to include test files.
// vite.config.ts
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
// you might want to disable it, if you don't have tests that rely on CSS
// since parsing CSS is slow
css: true,
},
})First, let's start by performing component testing. For this, we can create a test file to test our input component. We can test as many scenarios as we like. Here, we will test if the component is rendered properly and if the input can change values and update as we require. Initially, we will test to see if the component in rendered to the document.
//components/Input/Input.test.tsx
import { render, screen, userEvent } from "../../utils/test-utils";
import Input from "./Input";
test("should render the input component.", () => {
render(<Input label="Add Todo" inputProps={{}} />);
expect(screen.getByText(/Add Todo/i)).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: "Add Todo" })).toBeInTheDocument();
});First, we render the component making use of @testing-library/react and we will check if the component is rendered. We can additionally check for props and values the input component should have as the props have been passed in. for this we can again make use of helper functions provided by @testing-library/react to get the component properties. Here, we test to see if the input component has the label of "Add Todo" and we also check for the input box having the name 'Add Todo'.
We can now test our input for value changes and updates.
test("should update input value on change.", async () => {
render(<Input label="Add Todo" inputProps={{}} />);
const input = screen.getByRole("textbox", { name: "Add Todo" });
expect(input).toBeInTheDocument();
await userEvent.type(input, "new todo");
expect(input).toHaveValue("new todo");
});Here, we render the input then we check if the input box is present in the document. If the input box check passes we can then type anything we want into the input box. We then check if the input has the same value as we typed.
Now we can test our addtodo functionality in the app. Continue by creating a new file AddTodo.test.tsx . We can begin by checking if the UI for the form is present in the document.
//src/AddTodo.test.tsx
import App from "./App";
import { render, screen, userEvent } from "./utils/test-utils";
test("should render the todo form.", () => {
render(<App />);
expect(screen.getByText(/Add Todo/i)).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: "Add Todo" })).toBeInTheDocument();
const button = screen.getByRole("button", { name: /add/i });
expect(button).toBeInTheDocument();
});Now we can test the functionality of the add todo form. Here, we again test by rendering the App component and then check for the title of the remaining todo in the document. Then we add a new todo to the input and click on the button for adding. We then finally check if the newly added todo is also present in the UI document.
test("should add todo to the remaining todos list.", async () => {
render(<App />);
expect(screen.getByText(/Remaining Todos/i)).toBeInTheDocument();
const input = screen.getByRole("textbox", { name: /add todo/i });
expect(input).toBeInTheDocument();
await userEvent.type(input, "new todo");
const button = screen.getByRole("button", { name: /add/i });
expect(button).toBeInTheDocument();
await userEvent.click(button);
expect(screen.getByText(/new todo/i)).toBeInTheDocument();
});We successfully tested different scenarios for the todo app. We also made it possible to pass all the tests. Implement similarly in your apps now.

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.

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.

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.