Sitemap
JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.

Frontend Interview Question: What Problems Do React and Other Frameworks Actually Solve?

6 min readOct 14, 2026

--

What is a framework, and why do we need one? Why don’t we write apps in plain JavaScript? Let’s dive into reactivity and explore how we would write an app in vanilla JavaScript. What pain points do frameworks take away from us?

Press enter or click to view image in full size

📚 Part of a Frontend Interview Concepts Series

This article is part of my Frontend Interview Concepts series, where I break down essential JavaScript fundamentals, common programming patterns, data types, and frontend frameworks to help developers strengthen their knowledge and prepare for technical interviews. Explore all three parts of the series:

Whether you’re preparing for your next frontend interview or strengthening your JavaScript knowledge, I hope this series helps you build a stronger foundation.

Let’s start with the claim that a framework is a set of best practices for specific tasks in JavaScript — tasks that could be solved in many ways but come with their own edge cases. For example, routing, reactivity, updating the DOM, etc. Even when we understand these tasks, each framework has its own point of view on how to solve them.

Routing

/about-us
/about-us.html

/our-services
/our-services.html

How can we achieve something like this with the help of JavaScript?

// 1. Map route URLs to external HTML template files
const routes = {
"/": "views/home.html",
"/about": "views/aboutUs.html",
"/contact-us": "views/contactUs.html",
404: "views/404.html"
};

// 2. Fetch the HTML file and load it into the app container
async function router() {
const path = window.location.pathname;
const templatePath = routes[path] || routes[404];

try {
const response = await fetch(templatePath);
if (!response.ok) throw new Error("Page not found");

const html = await response.text();
document.getElementById("app").innerHTML = html;
} catch (error) {
document.getElementById("app").innerHTML = "<h1>404</h1><p>Page failed to load.</p>";
}
}

// 3. Navigate programmatically
function navigateTo(url) {
window.history.pushState(null, null, url);
router();
}

// 4. Intercept link clicks
document.addEventListener("click", (e) => {
if (e.target.matches("[data-link]")) {
e.preventDefault();
navigateTo(e.target.href);
}
});

// 5. Handle back/forward buttons & initial load
window.addEventListener("popstate", router);
document.addEventListener("DOMContentLoaded", router);

You might say, “Well, that seems easy. I can do it and continue using vanilla JavaScript.”

But frameworks handle many edge cases. You can combine the router with CSR and SSR components, preload data, apply route guards, and much more. They also provide a consistent way of handling routing in code, giving you a similar coding experience to working with other features, such as forms.

Reactivity

As a user, I want to click a button and see the counter’s value increase.

Get Marek Panti’s stories in your inbox

Join Medium for free to get updates from this writer.

Well, that’s quite simple, isn’t it? This is how you would implement it in JavaScript:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Counter Example</title>
</head>
<body>

<h2>Counter: <span id="count">0</span></h2>
<button id="counter-btn">Click Me!</button>

<script>
let count = 0;

const countDisplay = document.getElementById("count");
const counterBtn = document.getElementById("counter-btn");

counterBtn.addEventListener("click", () => {
count++;
countDisplay.textContent = count;
});
</script>

</body>
</html>

But wait. Our application looks something like that:

Press enter or click to view image in full size
Typical App has so many dynamic properties and each of them is calculated with specific business logic.

Now this is how would you do that with PREACT:

<script type="module">
import { h, render } from "https://esm.sh/preact@10.19.2";
import { signal, computed } from "https://esm.sh/@preact/signals@1.2.2";

const passengerCount = signal(1);
const ticketClass = signal("Economy");
const hasExtraLuggage = signal(false);

const classPrices = { "Economy": 200, "Business": 500, "First Class": 1200 };

const totalPrice = computed(() => {
const basePrice = classPrices[ticketClass.value];
const luggageFee = hasExtraLuggage.value ? 50 : 0;
return passengerCount.value * (basePrice + luggageFee);
});

// --- PREACT COMPONENT ---
function FlightBooking() {
return h("div", { class: "card" },
h("h3", null, "✈ Flight Booking Summary"),

// Property 1: Passengers
h("div", { class: "row" },
h("span", null, "Passengers:"),
h("div", null,
h("button", { onClick: () => passengerCount.value > 1 && passengerCount.value-- }, "-"),
h("span", { style: { margin: "0 10px" } }, passengerCount), // Directly bind signal!
h("button", { onClick: () => passengerCount.value++ }, "+")
)
),

// Property 2: Ticket Class
h("div", { class: "row" },
h("span", null, "Class:"),
h("select", {
value: ticketClass,
onChange: (e) => (ticketClass.value = e.target.value)
},
h("option", { value: "Economy" }, "Economy ($200)"),
h("option", { value: "Business" }, "Business ($500)"),
h("option", { value: "First Class" }, "First Class ($1200)")
)
),

// Property 3: Extra Luggage
h("div", { class: "row" },
h("span", null, "Extra Luggage (+$50):"),
h("input", {
type: "checkbox",
checked: hasExtraLuggage,
onChange: (e) => (hasExtraLuggage.value = e.target.checked)
})
),

// Computed Summary
h("div", { class: "row total" },
h("span", null, "Total Cost:"),
h("span", null, `$`, totalPrice) // Computed signal automatically updates text
)
);
}

// Render the Preact component into the HTML
render(h(FlightBooking), document.getElementById("app"));
</script>

With almost no additional logic, you can simply describe what you intend to do, and the framework takes care of the rest, including the mapping between your data and the UI.

Updating the DOM

In plain Javascript you would have to take care about each change and manually with event listeners and DOM selection update the piece of html that needs to be changed, or you can create something creative on your own like signals, but:

Signal.js, index.html, and app.js

// A global stack to track currently running effects for automatic dependency tracking
let currentEffect = null;

export function createSignal(initialValue) {
let value = initialValue;
const subscribers = new Set();

const read = () => {
// If an effect is currently running, subscribe it to this signal
if (currentEffect) {
subscribers.add(currentEffect);
}
return value;
};

const write = (newValue) => {
if (value !== newValue) {
value = typeof newValue === 'function' ? newValue(value) : newValue;
// Notify all subscribed effects
subscribers.forEach((effect) => effect());
}
};

return [read, write];
}

export function createEffect(fn) {
const execute = () => {
currentEffect = execute;
try {
fn();
} finally {
currentEffect = null;
}
};

// Run once immediately to collect dependencies and render initial state
execute();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Signal Demo</title>
</head>
<body>
<div id="app">
<h1>Count: <span id="counter"></span></h1>
<button id="decrement">-</button>
<button id="increment">+</button>
</div>

<script type="module" src="./app.js"></script>
</body>
</html>
import { createSignal, createEffect } from './signal.js';

// 1. Create a reactive signal for the count
const [count, setCount] = createSignal(0);

// 2. Grab DOM elements
const counterEl = document.getElementById('counter');
const incBtn = document.getElementById('increment');
const decBtn = document.getElementById('decrement');

// 3. Create an effect to automatically update the DOM when `count` changes
createEffect(() => {
// Reading `count()` automatically registers this effect as a subscriber
counterEl.textContent = count();
});

// 4. Wire up event listeners to update the signal
incBtn.addEventListener('click', () => {
setCount(count() + 1);
});

decBtn.addEventListener('click', () => {
setCount(count() - 1);
});

This is a basic example of how signals work in frameworks. It also demonstrates how you could implement a similar mechanism in JavaScript and have your properties update dynamically.

BUT…

WAIT…

A…

MINUTE…

Where are all the effects, linked signals, computed values, conversions from HTTP requests to signals, and much more? And what about signals and forms?!?

Security

If you’ve ever created a web component, such as a multiselect or datepicker, from scratch, do you remember how many edge cases it had? And what about that tiny new bug you discovered every day for at least six months after its release?

Security on the frontend is another major concern. For example, Angular (Angular Security Best Practices) provides many security mechanisms under the hood that you would otherwise need to consider when working with plain JavaScript.

HTML sanitization, XSS attacks, HTTP security headers, etc.

State Management and Data Persistence

Check out my earlier article about how to design your state correctly on the frontend: NgRx Signal Store vs. Signal State vs. Simple Signal.

Well, technically, you can manage your own state in plain JavaScript, but you would need to set up your own reactive design, etc. In the end, you might find yourself building your own custom framework.

Conclusion

The goal isn’t to avoid vanilla JavaScript. It’s to understand when vanilla JavaScript is enough — and when a framework becomes the better engineering decision.

--

--

Marek Panti
Marek Panti

Written by Marek Panti

JavaScript-focused Front-End developer. Angular, Vue, React. Back end exclusively with NestJS.

JavaScript in Plain English
JavaScript in Plain English

Published in JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.