Front-End Learning Shifts from API Recall to Trade-Off Judgment as Agents Write Code
I've been working on DocFlow, a full-stack project integrating Agents, RAG, and knowledge graphs. It supports real-time collaboration for a hundred people, imports Word (DOCX) and PDF, and lets Agents search materials, rewrite documents, and operate tables. Under the hood, it implements a main Agent orchestrating, sub-Agents verifying in parallel, and result review, plus automatic context compression, long/short-term memory, and task recovery. RAG covers parent-child chunking, hybrid BM25+vector retrieval, RRF fusion, reranking, and source citation. It's good for anyone who wants to dive into source code and build AI applications.
I've also compiled 80 articles on Agent development, 72 on RAG, and 70 on knowledge graphs, 222 articles total. From fundamentals to engineering practice, you can learn alongside the source code. If interested, add my WeChat yunmz777 to learn about the project and materials, and to discuss AI development.
Recently I've seen discussions about front-end online. Some say departments no longer have pure front-end roles; pages, APIs, and deployment all fall to the same person. Others lament that even though they applied for a front-end position, the job description asks for full-stack, AI applications, and Agent development.
Look at how Agents generate pages, modify components, and integrate APIs — it's easy to get anxious: is front-end going away? Do the React, CSS, state management, and engineering practices I learned before still need to be studied?
I don't think front-end is dead, but our learning approach should change.
We don't need to memorize every package's API, every parameter, or every implementation. What's more worth mastering is: what problems these technologies solve, what scenarios they fit, and how to tell whether an Agent has used them correctly.
For example, we don't need to write Zustand initialization code from memory, but we should know what kind of state it's good for. We don't need to memorize every Redux Toolkit API, but we should understand why a project adopted it, and whether new features should follow the existing approach.
Specific code can be left to Agents, which consult docs and reference project implementations. When we understand the technology, we can give it better requirements and constraints, and we can see the trade-offs in its proposed solutions.
So the question isn't whether to learn front-end in the future, but: when implementation moves to Agents, where should we spend our learning time to complete a feature well?
Job roles are expanding; front-end skills still apply
Looking at public job postings, front-end fundamentals haven't been entirely replaced by AI skills.
Component development roles still care about TypeScript, React, component libraries, continuous integration, and page response speed. Some AI-native product roles put React rendering performance and model interaction in the same requirement list. These public job descriptions show that building AI products also requires handling front-end engineering.
Some roles are expanding their responsibilities. Agent product roles begin to involve streaming output, dynamic component rendering, and front-end/back-end collaboration. Desktop and AI front-end roles mention Electron architecture, multitasking, tool calling, and cross-end interaction.
These samples don't represent the whole market, nor can we merge every requirement into a checklist every front-end developer must master. But they remind us that learning shouldn't stop at how to write components or call APIs; we also need to understand how a feature goes from requirement to delivery.
Two directions need to be distinguished.
Using Agents to develop products mainly addresses development efficiency and delivery quality. Developing Agent products requires handling task execution, tool calling, human confirmation, and result display.
We can develop both kinds of abilities, but we don't need to start learning model training, low-level inference, and every Agent framework just because we've started using coding Agents.
It's more valuable to let existing knowledge support technical choices, then expand along project needs, than to continually relearn an entire tech stack.
When learning a technology, first clarify what it does for us
In the past, we might go through installation, initialization, API lists, and then judge mastery by whether we could write a sample from memory.
Now we can reverse the order.
I first look at what kinds of problems it solves, whether the project has equivalent solutions, what work it can save, and what limitations come with it. When a specific need arises, I let the Agent consult docs and project versions to create the implementation.
For example, with an increasingly laggy long list, we don't need to memorize all the parameters of a virtual list component, but we should know that list virtualization is a plausible option, and that dynamic height, scroll positioning, and content search need to be verified together.
With frequent repeated requests, we don't need to hand-write a cache. We should think that the project may already have request caching tools; let the Agent check existing capabilities, then decide whether to adjust configuration or add functionality.
The role of technical knowledge is to know which options exist when we hit a problem, not to require all options be hand-writable without docs.
Specific syntax becomes familiar with use, but memorization shouldn't be the learning goal. For unfamiliar details, we can ask the Agent to help verify. What we need to build is an understanding of purpose, scenarios, and constraints.
State management: classify data first, then choose the tool
State management best illustrates the difference in learning style.
We ask an Agent to build a workspace. It might quickly create a global store and stuff the current session, dialog toggles, API results, task progress, and form inputs into it.
The code runs, but that doesn't mean all that data should be maintained in one place.
React's guidance on state structure emphasizes avoiding contradictory, redundant, and duplicated state. In practice, I first judge who owns the data, which areas need to read it, and how long it lives, then choose a solution.
| State type | Common examples | Preferred management approach |
|---|---|---|
| Component-internal state | Dialog toggles, input content, current expand/collapse | Stay in the component or feature; use useState or useReducer |
| Cross-component client state | Workspace layout, shared selection across areas | Consider Zustand, Redux Toolkit, etc., based on architecture |
| Page state to share and restore | Search conditions, pagination, current resource ID | URL-expressed parts, where suitable; URL as source of truth |
| Local cache of server data | User profile, doc list, task query results | Consider TanStack Query or RTK Query |
| Persistent task facts | Tool results, approval validity, task completion | Maintained by server or a separate task runtime; front-end syncs and displays |
These tools aren't mutually exclusive, and not every project should install all of them.
Zustand provides a store, state updates, selective subscriptions, and more. If a workspace needs to share client state across areas and wants less ceremony, consider it.
If a project already has a collaborative structure around action, reducer, and middleware, and multiple modules depend on those conventions, I usually prefer sticking with Redux. Discussions about Redux practice today should be based on the officially recommended Redux Toolkit, not on the old impression of lots of hand-written boilerplate.
This isn't a fixed formula of small projects use Zustand, large projects use Redux. Existing code, team conventions, state relationships, and maintenance cost all enter the decision.
API data has another set of questions: when do results expire, when to refetch, and how to update the cache after a mutation. TanStack Query and RTK Query provide these capabilities, so there's no need to indiscriminately copy the same result into another store.
Local edit drafts can be saved separately, but we need to define when to initialize, when to commit, and when to discard, to avoid treating two copies as the latest state.
We don't need to remember every way to write these libraries, but we should be able to tell the Agent: dialog state stays in the component, workspace layout uses the existing store, doc list goes to the query cache, and task completion is based on server state.
Understanding these differences is what lets the Agent organize state per project needs, rather than just returning a complete-looking set of code.
JavaScript and TypeScript: focus on behavior and constraints
JavaScript is still worth learning, but there's no need to spend lots of time on obscure syntax trivia and memorizing implementations.
More useful is understanding how async, references, scope, and error propagation affect behavior.
For example, a user searches A, then B. B's request returns first, the page shows the correct result, then A's request finishes and overwrites the page with old content.
We don't need to write out the fix on the spot, but we should recognize this as a mismatch between async results and the current operation. Then we can ask the Agent to check request cancellation, stale-response filtering, and write conditions, rather than continuing to tweak the debounce time.
AbortController can cancel requests and related reads, but it doesn't replace the application's judgment about which results belong. At minimum, we need to know that reducing requests, canceling reads, and preventing stale overwrites are separate problems.
Closures are the same. Understanding that closures retain access to their lexical environment is about knowing where to look when callbacks, timers, and async logic show stale data, not about reciting the definition.
TypeScript helps express business constraints.
A task awaiting approval should carry approval information, a successful task should allow reading final results, and a failed task needs a handleable error. Knowing that discriminated unions, type narrowing, and exhaustive checks can express these differences lets us ask the Agent to model according to those constraints.
External data needs another layer of checking. Writing as TaskResult doesn't validate API responses or model output, because type assertions provide no runtime checks.
So when an Agent uses a type assertion to silence an error, we should keep digging: is the data truly verified here, or is it just making the compiler stop complaining?
We need to understand what these mechanisms guarantee and what they don't. How to write a specific type or cancel logic can be left to the Agent and docs.
React: understand state, rendering, and effects' purposes
Learning React can shift from memorizing Hook signatures to understanding what each solves.
How useState and useReducer help organize component state, what external synchronizations Effect is for, and how component identity affects state retention will directly influence design.
For example, why does form content persist after switching pages? Why does input disappear after changing key? These behaviors relate to a component's position and identity in the tree. React's documentation on preserving and resetting state helps us decide whether to keep, reset, or lift state.
When hitting a problem, letting the Agent toss in a key can sometimes make the symptom disappear, but it might also wipe out state that should be kept.
Effect is often used as a catch-all patch.
If a value can be directly derived from existing data, there's no need to store it separately and sync it via Effect. React's official note on unnecessary Effects addresses exactly that.
For subscribing to events, connecting to external systems, and maintaining timers, we need clear setup and cleanup timing. If the user still receives old events after leaving the page, or repeated messages appear after re-entering, we should check side effects and cleanup logic.
We can let the Agent handle dependency analysis and code changes, but we should know that removing a dependency or disabling a lint rule doesn't automatically fix the issue.
When using Next.js, we also need to understand server and client component responsibilities. Where browser interactions live, who does data reading, and which content shouldn't enter the client are all judgment calls.
We don't need to memorize every framework limitation, but we need to know these distinctions exist. That way, when the Agent proposes making the whole page a client component, we'll ask: which parts actually need interaction, and is it necessary to widen it to the entire page?
HTML, CSS, and interaction: what makes an interface actually good
HTML and CSS can also shift from memorizing syntax to understanding their capabilities.
Layout, scrolling, semantic structure, focus, and responsiveness remain core front-end interaction foundations. We don't need to remember every value of every property, but we should know which mechanisms can solve the current problem.
Screenshots only prove a visual effect at a moment in time; they don't show that continuous interaction behaves correctly.
In a chat workspace, while a user is scrolling up to read history, should a new message auto-scroll to the bottom? When the sidebar narrows, does a code block break the layout? After the mobile keyboard pops up, can the input box and send button still be used?
These behaviors need to be defined up front, then the Agent implements them.
Modals are more than an overlay and centered container. Modal dialog interaction requirements also cover focus entry, keyboard operation, and focus restoration after closing.
Knowing these requirements leads us to prefer reusing components that already handle these behaviors, rather than assembling a new modal that only looks right.
Component abstraction also needs judgment. Two interfaces that look similar may not need to be combined into one public component with many parameters. Look at whether their behavior is the same and whether future changes will happen together.
When each new scenario adds several boolean parameters, it's worth revisiting the abstraction instead of piling conditions into a shared component.
These abilities don't depend on how much CSS we remember, but they affect whether we can explain interaction requirements clearly and spot problems in generated results.
How does resumable file upload work?
Such questions test whether we understand the communication process, not whether we can write a full implementation from memory.
In a chunked upload approach, you first create an upload session, then determine each chunk's ID, expected length, and necessary verification info. The server verifies and saves it, then records it as received.
When the connection fails, the client queries the server to confirm progress, then uploads missing chunks. Don't trust the local progress bar alone, because a request being sent doesn't mean the server saved it completely.
The last chunk can be smaller than the standard size; its completeness should be judged by its own expected length and verification result. Duplicate uploads of the same chunk need clear handling rules, to avoid duplicates from retries.
Exactly which API to use for slicing, how to schedule concurrency, and how to wrap request functions can all be left to the Agent. What we need to define is what counts as a successful upload and what to resume based on after failure.
The same distinction applies to Agent streaming interactions.
When the connection breaks, the task may still be running. When the page reconnects, we shouldn't default to recreating the task. Native EventSource supports reconnection, and the SSE event format includes fields like id and retry, but these mechanisms don't mean the application already has full task recovery.
For interfaces that need recovery, I require the task to have a stable ID, be able to query current status, and fill in events after a specific position. Duplicate events need deduplication, and when history is incomplete, refetch the full state.
When the user clicks stop, we also need to say whether it means stop receiving content or cancel the actual task. The interface shouldn't mark a task as canceled just because the connection closed.
Knowing what network APIs do, and knowing how many steps stand between that and business completion, is what gives an Agent the right implementation goal.
Performance optimization: find out what's slow, then choose a tool
Performance tuning doesn't have to start from memorizing optimization tricks.
We should know what tools help locate the problem, and what type of problem each approach handles.
Chrome DevTools' Performance panel can record run-time activity and help analyze main-thread activity and rendering cost. When a page lags, capture a record, then decide whether time goes to computation, updates, or layout, rather than blindly adding a cache.
For example, a workspace that continuously outputs content might re-parse all text on every update, or cause the entire history list to re-render.
If the main cost is in text processing, check parsing and update scope. If it's in list rendering, consider reducing unnecessary updates or using virtualization. Just because we remember a performance API doesn't mean it belongs in every component.
An Agent can help analyze the record, propose hypotheses, and modify the implementation. We need to give it reproducible data, operation paths, and evaluation criteria, then compare before/after.
After high-frequency updates are buffered and merged, verify that the last chunk wasn't lost. After list virtualization, verify scroll position, search, and keyboard operation. After reducing computation, confirm results aren't stale.
Performance skill isn't about remembering many optimization patterns; it's about locating the problem in the right direction and comparing results under the same conditions.
When multiple Agents return content concurrently, how to display it?
This kind of requirement is first about information organization and state design, not knowing every property of a chat component.
Suppose a task searches materials, analyzes files, and generates modification suggestions at the same time. Putting all outputs in arrival order into one text area is easy, but the user can't tell which content belongs to which step, or whether a partial failure affects the overall result.
So tasks, steps, messages, and tool calls need clear ownership relationships.
The default view can show overall progress, key actions, pending items, and final results. Specific parameters, execution logs, and error details go into expandable sections, so the user first understands what is happening now.
Parallel tasks also need clear completion rules. One step completing doesn't mean the whole task completes. A step waiting for approval doesn't necessarily pause all unrelated steps. Those rules should come from task design; the front-end displays accordingly.
Human confirmation must be tied to concrete actions.
The user should know which files the system plans to modify, what content will be overwritten, and what happens after approval. The approval record must bind to the exact operation, so the user doesn't see an old suggestion while the system executes something else.
Cancellation also needs to distinguish request from result. After a user clicks cancel, we can show
Top 1 of 3 from juejin.cn, machine-translated. The original thread is authoritative.
True, agreed. Going forward, experience plus knowing what to use where and what it solves will matter more.
But you also need a spirit of experimentation, to see what new things can solve and whether they're better than the old ones. Otherwise, if you keep relying on experience the whole way, there's no way to improve.
Right, trying new approaches is important.