React ทำให้แนวคิด UI แบบคอมโพเนนต์เป็นเรื่องปกติ: การเรนเดอร์แบบเชิงประกาศ และการมอง UI เป็นผลจากสถานะ ช่วยให้ทีมเลิกพึ่งโค้ดแบบเน้นหน้าเพจและหันมาสร้างระบบและรูปแบบที่นำกลับมาใช้ใหม่ได้

React didn’t just introduce a new library—it changed what teams mean when they say “frontend architecture.” In practical terms, frontend architecture is the set of decisions that keep a UI codebase understandable at scale: how you split the UI into parts, how data moves between them, where state lives, how you handle side effects (like fetching data), and how you keep the result testable and consistent across a team.
Component thinking is treating every piece of UI as a small, reusable unit that owns its rendering and can be composed with other units to build whole pages.
Before React became popular, many projects were organized around pages and DOM manipulation: “find this element, change its text, toggle this class.” React pushed teams toward a different default:
These ideas changed day-to-day work. Code reviews started asking “where does this state belong?” instead of “which selector did you use?” Designers and engineers could align on a shared component vocabulary, and teams could grow libraries of UI building blocks without rewriting entire pages.
Even if a team later moves to another framework, many React-shaped habits remain: component-based architecture, declarative rendering, predictable data flow, and a preference for reusable design system components over one-off page code. React made these patterns feel normal—and that influenced the broader frontend ecosystem.
Before React, many teams built interfaces around pages, not reusable UI units. A common setup was server-rendered templates (PHP, Rails, Django, JSP, etc.) that produced HTML, with jQuery sprinkled on top for interactivity.
You’d render a page, then “activate” it with scripts: datepickers, modal plugins, form validators, carousels—each with its own markup expectations and event hooks.
The code often looked like: find a DOM node, attach a handler, mutate the DOM, and hope nothing else breaks. As the UI grew, the “source of truth” quietly became the DOM itself.
UI behavior rarely lived in one place. It was split between:
A single widget—say, a checkout summary—might be partially built on the server, partially updated with AJAX, and partially controlled by a plugin.
This approach worked for small enhancements, but it produced recurring problems:
Frameworks like Backbone, AngularJS, and Ember tried to bring structure with models, views, and routing—often a big improvement. But many teams still mixed patterns, leaving a gap for a simpler way to build UIs as repeatable units.
React’s most important shift is simple to say and surprisingly powerful in practice: the UI is a function of state. Instead of treating the DOM as the “source of truth” and manually keeping it in sync, you treat your data as the source of truth and let the UI be the result.
State is just the current data your screen depends on: whether a menu is open, what’s typed into a form, which items are in a list, which filter is selected.
When state changes, you don’t hunt through the page to update several DOM nodes. You update the state, and the UI re-renders to match it.
Traditional DOM-first code often ends up with scattered update logic:
With React’s model, those “updates” become conditions in your render output. The screen becomes a readable description of what should be visible for a given state.
function ShoppingList() {
const [items, setItems] = useState([]);
const [text, setText] = useState("");
const add = () => setItems([...items, text.trim()]).then(() => setText(""));
return (
<section>
<form onSubmit={(e) => { e.preventDefault(); add(); }}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button disabled={!text.trim()}>Add</button>
</form>
{items.length === 0 ? <p>No items yet.</p> : (
<ul>{items.map((x, i) => <li key={i}>{x}</li>)}</ul>
)}
</section>
);
}
Notice how the empty message, button disabled state, and list contents are all derived from items and text. That’s the architectural payoff: data shape and UI structure align, making screens easier to reason about, test, and evolve.
React made “component” the default unit of UI work: a small, reusable piece that bundles markup, behavior, and styling hooks behind a clear interface.
Instead of scattering HTML templates, event listeners, and CSS selectors across unrelated files, a component keeps the moving parts close together. That doesn’t mean everything must live in one file—but it does mean the code is organized around what the user sees and does, not around the DOM API.
A practical component usually includes:
The important shift is that you stop thinking in terms of “update this div” and start thinking in terms of “render the Button in its disabled state.”
When a component exposes a small set of props (inputs) and events/callbacks (outputs), it becomes easier to change its internals without breaking the rest of the app. Teams can own specific components or folders (for example, “checkout UI”) and improve them confidently.
Encapsulation also reduces accidental coupling: fewer global selectors, fewer cross-file side effects, fewer “why did this click handler stop working?” surprises.
Once components became the main building blocks, code started mirroring the product:
This mapping makes UI discussions easier: designers, PMs, and engineers can talk about the same “things.”
Component thinking pushed many codebases toward feature- or domain-based organization (for example, /checkout/components/CheckoutForm) and shared UI libraries (often /ui/Button). That structure scales better than page-only folders when features grow, and it sets the stage for design systems later.
React’s rendering style is often described as declarative, which is a fancy way of saying: you describe what the UI should look like for a given situation, and React figures out how to make the browser match that.
In older DOM-first approaches, you typically wrote step-by-step instructions:
With declarative rendering, you instead express the result:
If the user is logged in, show their name. If they’re not, show a “Sign in” button.
That shift matters because it reduces the amount of “UI bookkeeping” you have to do. You’re not constantly tracking which elements exist and what needs updating—you focus on the states your app can be in.
JSX is essentially a convenient way to write UI structure close to the logic that controls it. Instead of splitting “template files” and “logic files” and then jumping between them, you can keep related pieces together: the markup-like structure, the conditions, small formatting decisions, and event handlers.
That co-location is a big reason React’s component model felt practical. A component isn’t just a chunk of HTML or a bundle of JavaScript—it’s a unit of UI behavior.
A common concern is that JSX mixes HTML and JavaScript, which sounds like a step backward. But JSX isn’t really HTML—it’s syntax that produces JavaScript calls. More importantly, React isn’t mixing technologies as much as it’s grouping things that change together.
When the logic and the UI structure are tightly linked (for example: “show an error message only when validation fails”), keeping them in one place can be clearer than scattering rules across separate files.
JSX made React approachable, but the underlying concept extends beyond JSX. You can write React without JSX, and other frameworks also use declarative rendering with different template syntaxes.
The lasting impact is the mindset: treat UI as a function of state, and let the framework handle the mechanics of keeping the screen in sync.
Before React, a common source of bugs was simple: the data changed, but the UI didn’t. Developers would fetch new data, then manually find the right DOM nodes, update text, toggle classes, add/remove elements, and keep all of that consistent across edge cases. Over time, “update logic” often became more complex than the UI itself.
React’s big workflow shift is that you don’t instruct the browser how to change the page. You describe what the UI should look like for a given state, and React figures out how to update the real DOM to match.
Reconciliation is React’s process of comparing what you rendered last time with what you rendered this time, then applying the smallest set of changes to the browser DOM.
The important part isn’t that React uses a “Virtual DOM” as a magic performance trick. It’s that React gives you a predictable model:
That predictability improves developer workflow: fewer manual DOM updates, fewer inconsistent states, and UI updates that follow the same rules across the app.
When rendering lists, React needs a stable way to match “old items” to “new items” during reconciliation. That’s what key is for.
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
Use keys that are stable and unique (like an ID). Avoid array indexes when items can be reordered, inserted, or deleted—otherwise React may reuse the wrong component instance, leading to surprising UI behavior (like inputs keeping the wrong value).
One of React’s biggest architectural shifts is that data flows in one direction: from parent components down to children. Instead of letting any part of the UI “reach into” other parts and mutate shared state, React encourages you to treat updates as explicit events that move upward, while the resulting data moves downward.
A parent owns the state, and passes it to a child as props. The child can request a change by calling a callback.
function Parent() {
const [count, setCount] = React.useState(0);
return (
<Counter
value={count}
onIncrement={() => setCount(c => c + 1)}
/>
);
}
function Counter({ value, onIncrement }) {
return (
<button onClick={onIncrement}>
Clicks: {value}
</button>
);
}
Notice what doesn’t happen: the Counter doesn’t modify count directly. It receives value (data) and onIncrement (a way to ask for change). That separation is the core of the mental model.
This pattern makes boundaries obvious: “Who owns this data?” is usually answered by “the closest common parent.” When something changes unexpectedly, you trace it to the place where state lives—not through a web of hidden mutations.
That distinction helps teams decide where logic belongs and prevents accidental coupling.
Components that rely on props are easier to reuse because they don’t depend on global variables or DOM queries. They’re also simpler to test: you can render them with specific props and assert output, while stateful behavior is tested where the state is managed.
React nudged teams away from “class hierarchies for UI” and toward assembling screens from small, focused pieces. Instead of extending a base Button into ten variations, you typically compose behavior and visuals by combining components.
A common pattern is building layout components that don’t know anything about the data they’ll contain:
PageShell for header/sidebar/footerStack / Grid for spacing and alignmentCard for consistent framingThese components accept children so the page decides what goes inside, not the layout.
You’ll also see lightweight wrappers like RequireAuth or ErrorBoundary that add a concern around whatever they wrap, without changing the wrapped component’s internals.
When you need more control than “just children,” teams often use a slot-like approach via props:
Modal with title, footer, and childrenTable with renderRow or emptyStateThis keeps components flexible without exploding the API surface.
Deep inheritance trees usually start with good intentions (“we’ll reuse the base class”), but they become hard to manage because:
Hooks made composition even more practical. A custom hook like useDebouncedValue or usePermissions lets multiple feature components share logic without sharing UI. Pair that with shared UI primitives (buttons, inputs, typography) and feature components (CheckoutSummary, InviteUserForm), and you get reuse that stays understandable as the app grows.
React made it natural to start with local component state: a form field value, a dropdown being open, a loading spinner. That works well—until the app grows and multiple parts of the UI need to stay in sync.
As features expand, state often needs to be read or updated by components that aren’t in a direct parent-child relationship. “Just pass props” turns into long chains of props through components that don’t really care about the data. This makes refactoring riskier, increases boilerplate, and can lead to confusing bugs where two places accidentally represent the “same” state.
1) Lifting state up
Move the state to the nearest common parent and pass it down as props. This is usually the simplest option and keeps dependencies explicit, but it can create “god components” if overused.
2) Context for shared, app-wide concerns
React Context helps when many components need the same value (theme, locale, current user). It reduces prop drilling, but if you store frequently changing data in context, it can become harder to reason about updates and performance.
3) External stores
As React apps became larger, the ecosystem responded with libraries such as Redux and similar store patterns. These centralize state updates, often with conventions around actions and selectors, which can improve predictability at scale.
Prefer local state by default, lift state when siblings need to coordinate, use context for cross-cutting concerns, and consider an external store when many distant components depend on the same data and the team needs clearer rules for updates. The “right” choice depends less on trends and more on app complexity, team size, and how often requirements change.
React didn’t just introduce a new way to write UI—it nudged teams toward a component-driven workflow where code, styling, and behavior are developed as small, testable units. That shift influenced how frontend projects are built, validated, documented, and shipped.
When the UI is made of components, it becomes natural to work “from the edges inward": build a button, then a form, then a page. Teams started treating components as products with clear APIs (props), predictable states (loading, empty, error), and reusable styling rules.
A practical change: designers and developers can align around a shared component inventory, review behavior in isolation, and reduce last-minute page-level surprises.
React’s popularity helped standardize a modern toolchain that many teams now consider table stakes:
Even if you don’t choose the same tools, the expectation remains: a React app should have guardrails that catch UI regressions early.
As a newer extension of this “workflow-first” mindset, some teams also use vibe-coding platforms like Koder.ai to scaffold React frontends (and the backend around them) from a chat-driven planning flow—useful when you want to validate component structure, state ownership, and feature boundaries quickly before spending weeks on hand-built plumbing.
React teams also popularized the idea of a component explorer: a dedicated environment where you render components in different states, attach notes, and share a single source of truth for usage guidelines.
This “Storybook-style” thinking (without requiring any specific product) changes collaboration: you can review a component’s behavior before it’s wired into a page, and you can validate edge cases deliberately instead of hoping they appear during manual QA.
If you’re building a reusable library, this pairs naturally with a design system approach—see /blog/design-systems-basics.
Component-based tooling encourages smaller pull requests, clearer visual review, and safer refactors. Over time, teams ship UI changes faster because they’re iterating on well-scoped pieces instead of navigating tangled, page-wide DOM code.
A design system, in practical terms, is two things working together: a library of reusable UI components (buttons, forms, modals, navigation) and the guidelines that explain how and when to use them (spacing, typography, tone, accessibility rules, interaction patterns).
React made this approach feel natural because “component” is already the core unit of UI. Instead of copying markup between pages, teams can publish a <Button />, <TextField />, or <Dialog /> once and reuse it everywhere—while still allowing controlled customization through props.
React components are self-contained: they can bundle structure, behavior, and styling behind a stable interface. That makes it easy to build a component library that’s:
If you’re starting from scratch, a simple checklist helps prevent “a pile of components” from turning into an inconsistent mess: /blog/component-library-checklist.
A design system isn’t just visual consistency—it’s behavioral consistency. When a modal always traps focus correctly, or a dropdown always supports keyboard navigation, accessibility becomes the default rather than an afterthought.
Theming also gets easier: you can centralize tokens (colors, spacing, typography) and let components consume them, so brand changes don’t require touching every screen.
For teams evaluating whether it’s worth investing in shared components, the decision often ties to scale and maintenance costs; some organizations connect that evaluation to platform plans like /pricing.
React didn’t just change how we build UIs—it changed how we evaluate quality. Once your app is made of components with clear inputs (props) and outputs (rendered UI), testing and performance become architectural decisions, not last-minute fixes.
Component boundaries let you test at two useful levels:
This works best when components have clear ownership: one place that owns state, and children that mostly display data and emit events.
React apps often feel fast because teams plan performance into the structure:
A useful rule: optimize the “expensive” parts—large lists, complex calculations, and frequently re-rendered areas—rather than chasing tiny wins.
Over time, teams can drift into common traps: over-componentizing (too many tiny pieces with unclear purpose), prop drilling (passing data through many layers), and fuzzy boundaries where no one knows which component “owns” a piece of state.
When you’re moving quickly (especially with auto-generated or scaffolded code), the same pitfalls show up faster: components multiply, and ownership gets blurry. Whether you’re coding by hand or using a tool like Koder.ai to generate a React app plus a backend (often Go with PostgreSQL), the guardrail is the same: keep state ownership explicit, keep component APIs small, and refactor toward clear feature boundaries.
Server Components, meta-frameworks, and better tooling will keep evolving how React apps are delivered. The lasting lesson is unchanged: design around state, ownership, and composable UI building blocks, then let testing and performance follow naturally.
For deeper structure decisions, see /blog/state-management-react.
React เปลี่ยนกรอบความคิดของสถาปัตยกรรม frontend ให้วนรอบการตัดสินใจสำคัญไม่กี่ข้อ:
ผลเชิงปฏิบัติคือการลดงานบำรุงรักษา DOM แบบแมนนวลและขอบเขตการทำงานที่ชัดเจนสำหรับทีมและเครื่องมือ
Component thinking หมายถึงการมองแต่ละชิ้นของ UI เป็นหน่วยเล็ก ๆ ที่นำกลับมาใช้ใหม่ได้ ซึ่งเป็นเจ้าของการเรนเดอร์ของตัวเองและสามารถประกอบเป็นหน้าจอที่ใหญ่ขึ้นได้ ในเชิงปฏิบัติ คอมโพเนนต์จะรวม:
แนวคิดนี้เปลี่ยนงานจาก “อัปเดตโหนด DOM นี้” เป็น “เรนเดอร์คอมโพเนนต์นี้สำหรับสภาพนี้”
ในโค้ดแบบ DOM-first มักจะทำให้ DOM กลายเป็นแหล่งความจริง ดังนั้นคุณต้องคอยซิงก์องค์ประกอบหลายจุดด้วยตนเอง แต่ใน React คุณอัปเดต state แล้วเรนเดอร์ตามนั้น ดังนั้นเงื่อนไขอย่าง spinner, ปุ่ม disabled, และสถานะว่างจะคงสอดคล้องกันโดยธรรมชาติ
การทดสอบง่ายๆ: ถ้าคุณกำลังเขียนขั้นตอนจำนวนมากแบบ “หา element แล้วสลับ class” แปลว่าคุณกำลังสู้กับโมเดล; ถ้า UI หลุดจากสถานะ มักเป็นปัญหาเรื่องการเป็นเจ้าของ state
ก่อน React หลายแอพเป็นแบบ page-centric: template ฝั่งเซิร์ฟเวอร์ที่เสิร์ฟ HTML แล้วเติมด้วย jQuery และปลั๊กอิน พฤติกรรมกระจายอยู่ใน server views, attribute ใน HTML, และตัว initializer ของ JS
ปัญหาที่พบบ่อยได้แก่:
React ผลักดันทีมไปสู่คอมโพเนนต์ที่นำกลับมาใช้ใหม่ได้และการอัพเดตที่คาดเดาได้
Declarative rendering คือการบอก ว่าผลลัพธ์ควรเป็นอย่างไร สำหรับสภาวะหนึ่ง แทนที่จะบอก ขั้นตอน ในการแก้ไข DOM ทีละอย่าง
แทนที่จะเขียน:
คุณจะอธิบายเงื่อนไขในผลลัพธ์ที่เรนเดอร์ (เช่น “ถ้า logged in ให้โชว์ชื่อ ถ้าไม่ให้โชว์ปุ่ม Sign in”) แล้ว React จะจัดการอัปเดต DOM ให้
JSX ทำให้สะดวกที่จะวางโครงสร้าง UI ใกล้กับ logic ที่ควบคุมมัน (เงื่อนไข การจัดรูปแบบ handlers) ลดการสลับไปมาระหว่างไฟล์เทมเพลตกับไฟล์ logic
JSX ไม่ใช่ HTML ตรงๆ มันคอมไพล์เป็นคำสั่ง JavaScript ข้อดีสำคัญคือการจัดกลุ่มสิ่งที่เปลี่ยนแปลงพร้อมกัน (UI + พฤติกรรม) ไว้ด้วยกันในคอมโพเนนต์ ทำให้ง่ายต่อการดูแลรักษา
Reconciliation คือกระบวนการที่ React เปรียบเทียบผลลัพธ์การเรนเดอร์ครั้งก่อนกับครั้งนี้ แล้วใช้การเปลี่ยนแปลงน้อยที่สุดกับ DOM จริง
ข้อสรุปเชิงปฏิบัติ: คุณเขียน logic เหมือนกับว่าเราสร้าง UI ใหม่ทั้งหมด แล้ว React จะอัปเดตแบบเพิ่มทีละน้อย
สำหรับรายการ ให้ใช้ค่า key ที่คงที่และไม่ซ้ำ (เช่น ID) หลีกเลี่ยงดัชนีของอาร์เรย์เมื่อตัวรายการอาจถูกสลับ แทรก หรือลบ เพราะ React อาจจับคู่ผิดและนำไปสู่พฤติกรรมที่น่าประหลาดใจ (เช่น input เก็บค่าไม่ถูกต้อง)
One-way data flow คือข้อมูลไหลจากพ่อแม่ลงสู่ลูกผ่าน props ขณะที่ลูกขอการเปลี่ยนแปลงโดยเรียก callback
ข้อนี้ช่วยให้ขอบเขตชัดเจน:
การดีบักมักกลายเป็น “หาให้เจอว่า state อยู่ที่ไหน” แทนที่จะไล่ตามการเปลี่ยนแปลงที่ซ่อนอยู่ในโค้ดส่วนอื่น
Composition หมายถึงการประกอบพฤติกรรมโดยการรวมคอมโพเนนต์ แทนที่จะสร้างต้นแบบคลาสลึก ๆ
รูปแบบที่พบบ่อย:
ลำดับการรับมือกับ state เมื่อแอพเติบโตคือ:
เลือกวิธีตามความซับซ้อนของแอพและความต้องการของทีม แทนที่จะตามกระแส
children (เช่น PageShell, Stack, Grid, Card)RequireAuth หรือ ErrorBoundaryfooter, emptyState, renderRow) เมื่อ children ไม่พอแนวทางนี้ยังยืดหยุ่นโดยไม่ก่อให้เกิดปัญหาจากต้นแบบคลาสที่ซับซ้อนและผลกระทบแบบลูกโซ่เมื่อแก้ไข