<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleksandr Sainchuk</title>
    <description>The latest articles on DEV Community by Oleksandr Sainchuk (@oleksandr_sainchuk).</description>
    <link>https://dev.to/oleksandr_sainchuk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4182234%2Fbc397cbc-9f1c-4c89-8039-a5785bd44932.png</url>
      <title>DEV Community: Oleksandr Sainchuk</title>
      <link>https://dev.to/oleksandr_sainchuk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oleksandr_sainchuk"/>
    <language>en</language>
    <item>
      <title>I Took a Claude-Built SaaS UI into Figma and Back to React</title>
      <dc:creator>Oleksandr Sainchuk</dc:creator>
      <pubDate>Mon, 19 Oct 2026 20:33:34 +0000</pubDate>
      <link>https://dev.to/oleksandr_sainchuk/i-took-a-claude-built-saas-ui-into-figma-and-back-to-react-38mh</link>
      <guid>https://dev.to/oleksandr_sainchuk/i-took-a-claude-built-saas-ui-into-figma-and-back-to-react-38mh</guid>
      <description>&lt;p&gt;Claude gave me a tidy expense inbox. It had a table, statuses, buttons, and all the other signs of a functioning SaaS product. Then I looked at one expense that was missing a cost center. The reason it needed attention was easy to miss.&lt;/p&gt;

&lt;p&gt;That seemed like a good place to start. I wanted to see what would happen if I built a screen with Claude, refined its design system in Figma, and asked Claude to continue the React prototype using the new design. Could it keep the same product decisions across an inbox, an expense detail view, and a dashboard?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=xE_n3Bjoi3Y" rel="noopener noreferrer"&gt;I recorded the full experiment on YouTube&lt;/a&gt;. I used a spend management demo for the experiment, with a finance manager and a connected expense review flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first screen looked plausible. The queue still needed work.
&lt;/h2&gt;

&lt;p&gt;I started by telling Claude who would use the product: a finance manager reviewing employee expenses. I described the inbox, the information needed to make a decision, and the actions a manager could take. Claude built the first UI in React with reusable pieces.&lt;/p&gt;

&lt;p&gt;It was a useful starting point. But the merchant, the review reason, the urgency, and the available action competed for attention. If you're scanning a queue, you want to know why this expense is here before you spend time opening it. “Missing cost center” should be hard to miss. The button can wait its turn.&lt;/p&gt;

&lt;p&gt;I kept one expense in mind throughout the rest of the experiment: Alex Morgan submitted a $2,480 Atlas Cloud charge, and the cost center is missing. The manager opens it, checks the receipt and recent spending, then asks Alex for that information. The expense moves from Needs review to Waiting for employee, and the request appears in its activity.&lt;/p&gt;

&lt;p&gt;The short task note I would hand an AI coding tool includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;User:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;finance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;manager&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Task:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;review&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Alex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Morgan's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Atlas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Cloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;expense&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Problem:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;center&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;missing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Show:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;receipt,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;recent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;spending,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reason&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;review&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Action:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;center&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Alex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;After&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;action:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;employee&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Also&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;show:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;activity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;queue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there is something to check besides whether the screen looks nice.&lt;/p&gt;

&lt;h2&gt;
  
  
  I used Figma to define what Claude should repeat
&lt;/h2&gt;

&lt;p&gt;I moved the initial UI into Figma and worked through its foundations, components, and larger screen patterns. I changed the default indigo/purple direction to blue, adjusted the header and branding, and organized the components so I could use them across the product.&lt;/p&gt;

&lt;p&gt;The color change is the most obvious part of the video. The more useful part is deciding what a row, a status, and a review action mean in this product. If &lt;strong&gt;Waiting for employee&lt;/strong&gt; appears in both the queue and the expense detail, I want the label and treatment to agree. If a dashboard card points to &lt;strong&gt;Needs attention&lt;/strong&gt;, it should lead somewhere useful. Claude needs those patterns when it builds the next page.&lt;/p&gt;

&lt;p&gt;In Figma, I used variables for repeated values and components for repeated UI. &lt;a href="https://help.figma.com/hc/en-us/articles/15339657135383-Guide-to-variables-in-Figma" rel="noopener noreferrer"&gt;Figma's variables guide&lt;/a&gt; explains how values can be applied across a design; &lt;a href="https://help.figma.com/hc/en-us/articles/14506821864087-Overview-of-variables-collections-and-modes" rel="noopener noreferrer"&gt;aliases&lt;/a&gt; let one variable reference another. A token named for its role, such as &lt;code&gt;status/waiting&lt;/code&gt;, tells a future implementer more than a blue swatch does. &lt;a href="https://help.figma.com/hc/en-us/articles/39636737843735-Components-collection-Variants-and-component-set-fundamentals" rel="noopener noreferrer"&gt;Variants&lt;/a&gt; help when a component has predictable states.&lt;/p&gt;

&lt;p&gt;I also gave Claude patterns for choosing between tables and cards. A review queue needs dense rows you can scan. An expense detail needs room for the receipt, context, and action. Those are different jobs, even when both screens share the same buttons and statuses.&lt;/p&gt;

&lt;p&gt;I didn't resolve every UX question in Figma. I wanted to see what Claude would do once it had a stronger design reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I sent Claude back to the React prototype
&lt;/h2&gt;

&lt;p&gt;After the Figma pass, I connected the design through MCP and told Claude to use the refined system for the next implementation pass. Otherwise, it could happily keep following its first UI. AI tools can be loyal to an old decision long after you've changed your mind.&lt;/p&gt;

&lt;p&gt;Figma's &lt;a href="https://developers.figma.com/docs/figma-mcp-server/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; can provide an agent with design context, including components and variables. &lt;a href="https://developers.figma.com/docs/figma-mcp-server/tools-and-prompts/" rel="noopener noreferrer"&gt;Code Connect&lt;/a&gt; can map Figma components to their code counterparts. Those references help Claude understand what to build. I still inspected the React screen and clicked through the flow.&lt;/p&gt;

&lt;p&gt;For this expense, a blue &lt;strong&gt;Request information&lt;/strong&gt; button would be a very small victory if clicking it left the expense in &lt;strong&gt;Needs review&lt;/strong&gt;. The prototype needed to show the request, change the status, and update the activity. The queue then needed to reflect the new state.&lt;/p&gt;

&lt;p&gt;There's a straightforward React point here too. Give the expense one status value such as &lt;code&gt;needs_review&lt;/code&gt; or &lt;code&gt;waiting_for_employee&lt;/code&gt;; don't maintain several independent booleans that can disagree. The &lt;a href="https://react.dev/learn/choosing-the-state-structure" rel="noopener noreferrer"&gt;React docs on state structure&lt;/a&gt; explain why mutually exclusive states help avoid impossible combinations. A real finance product would need persistence and permission rules around this action. The demo tests the screen flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dashboard gave me another useful surprise
&lt;/h2&gt;

&lt;p&gt;Once the expense flow was in place, I asked Claude for a dashboard for the same finance manager. It brought in a &lt;strong&gt;Needs attention&lt;/strong&gt; area, spending against the monthly budget, categories and merchants, notifications, and recent activity. Those pieces made sense for the product we had described.&lt;/p&gt;

&lt;p&gt;One choice still bothered me: recent activity had its own white card and looked too prominent. It was getting prime dashboard real estate for telling me what had already happened. I removed that card treatment and put the activity on a quieter gray background. The items needing the manager's decision could take the lead.&lt;/p&gt;

&lt;p&gt;That is the part of an AI-generated dashboard I keep checking. The model can assemble all the requested sections. Someone still has to decide what the person should notice first. In this case, a pending expense deserves more attention than a routine activity log.&lt;/p&gt;

&lt;h2&gt;
  
  
  My quick check for the next generated page
&lt;/h2&gt;

&lt;p&gt;I would run through the same expense again before asking for five more screens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can I spot the missing cost center from the queue?&lt;/li&gt;
&lt;li&gt;Does the detail view give me enough context to decide what to do?&lt;/li&gt;
&lt;li&gt;When I request information, do I see the new status and an activity entry?&lt;/li&gt;
&lt;li&gt;Does the queue show the updated expense when I return?&lt;/li&gt;
&lt;li&gt;Does &lt;strong&gt;Needs attention&lt;/strong&gt; on the dashboard take me to the right work?&lt;/li&gt;
&lt;li&gt;Did the implementation use the current Figma components and values?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If one of those fails, I have a specific problem to give Claude. “Make the UX better” is a much harder prompt to review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions I've seen developers ask about Figma and AI coding tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does my Figma file need auto layout and components before Claude can use it?
&lt;/h3&gt;

&lt;p&gt;Claude can work from a visual reference. A well-structured file gives it more to work with: layout, named components, variants, and variables bound to the elements you care about. Start with one relevant frame and check what appears in code. You still have to describe behavior such as what happens after Request information. A static frame doesn't contain that whole story.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get Claude to use our existing components?
&lt;/h3&gt;

&lt;p&gt;Show it the relevant Figma design and point it to the components in your codebase. Say which existing variants and tokens the screen should use. If you have &lt;a href="https://developers.figma.com/docs/figma-mcp-server/tools-and-prompts/" rel="noopener noreferrer"&gt;Code Connect mappings&lt;/a&gt;, they can provide a more explicit link. Then inspect what Claude wrote: did it import the existing component, or make a slightly different button that will become everyone's problem next month?&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need one huge &lt;code&gt;design.md&lt;/code&gt; file?
&lt;/h3&gt;

&lt;p&gt;I prefer a short product context, a place to find the design-system rules, and a task brief for the current flow. Claude should be able to find the role, the current components, and what changes after a click. There's no magic file count. In the video, the finance scenario and the refined Figma system did more work than a long list of generic design adjectives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will MCP keep Figma and React in sync for me?
&lt;/h3&gt;

&lt;p&gt;It gives the agent access to Figma context. You still need to ask for the code change and review the rendered screen. I explicitly told Claude that the refined Figma work was the current design reference before it continued the React prototype. MCP doesn't know whether Alex's expense changed status just because the component looks right. &lt;/p&gt;

&lt;h3&gt;
  
  
  How do you test a coded prototype before putting it in front of users?
&lt;/h3&gt;

&lt;p&gt;Click through a real task. In this demo, I can request Alex's missing cost center and check the status, queue, and activity. That catches broken connections between screens. It doesn't tell me whether finance managers understand the flow; for that, I'd ask people who do the job to try it and watch what happens. The video shows the first kind of check.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you keep Claude consistent as it generates more screens?
&lt;/h3&gt;

&lt;p&gt;Give it the current components and a bounded task. Review the new page against existing screens, including states and behavior. A waiting expense should use the same status language in the inbox and detail view. Check the implementation for one-off colors or duplicate components too. &lt;a href="https://developers.figma.com/docs/figma-mcp-server/tools-and-prompts/" rel="noopener noreferrer"&gt;Figma's MCP tools&lt;/a&gt; can supply design context; the screen still needs your review.&lt;/p&gt;

&lt;p&gt;You can watch the full Claude, Figma, and React walkthrough here, including the initial inbox, the design-system pass, the expense request, and the dashboard change. At UITOP, this is the kind of B2B SaaS work we spend time on: what a screen needs to show, what changes when someone takes an action, and whether the next screen still makes sense.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>figma</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Structure a B2B SaaS Frontend Around Changing Operational Data</title>
      <dc:creator>Oleksandr Sainchuk</dc:creator>
      <pubDate>Thu, 15 Oct 2026 05:15:19 +0000</pubDate>
      <link>https://dev.to/oleksandr_sainchuk/how-to-structure-a-b2b-saas-frontend-around-changing-operational-data-5ghk</link>
      <guid>https://dev.to/oleksandr_sainchuk/how-to-structure-a-b2b-saas-frontend-around-changing-operational-data-5ghk</guid>
      <description>&lt;p&gt;Imagine you're working in a logistics dashboard. You filter shipments to see everything that's delayed. You open one record, check its ETA, and start investigating the problem.&lt;/p&gt;

&lt;p&gt;Meanwhile, new shipment information arrives from the server. One shipment changes status. Another gets a new ETA. A background request finishes just after you've changed your filters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should the interface do?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If every update replaces the entire screen state, users can lose their place. If the application keeps showing old records, the interface becomes unreliable.&lt;/p&gt;

&lt;p&gt;We worked with this type of operational data while designing and developing LogiCore, a logistics and warehouse SaaS application at UITOP.&lt;/p&gt;

&lt;p&gt;The product connects shipment monitoring, warehouse locations, inventory records, and operational metrics. Its frontend uses React, TypeScript, and Zustand, with data served through REST APIs backed by Node.js, Express, PostgreSQL, and Prisma.&lt;/p&gt;

&lt;p&gt;For shipment monitoring, our team used Zustand-managed polling to retrieve updated status, ETA, and timestamp information. That project gives us a useful starting point for a broader engineering question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How should a React application manage user interactions and changing server data at the same time?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start by separating three kinds of state
&lt;/h2&gt;

&lt;p&gt;A common source of frontend bugs is treating every piece of information on a screen as one large object.&lt;/p&gt;

&lt;p&gt;Consider a shipment monitoring page. It may contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current shipment records and their statuses&lt;/li&gt;
&lt;li&gt;Active filters and pagination&lt;/li&gt;
&lt;li&gt;The shipment the user has selected&lt;/li&gt;
&lt;li&gt;Loading and refresh indicators&lt;/li&gt;
&lt;li&gt;Errors from API requests&lt;/li&gt;
&lt;li&gt;A timestamp showing when the data was updated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These values have different responsibilities. I find it useful to separate them into three groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server data
&lt;/h2&gt;

&lt;p&gt;This represents information the application receives from its backend.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shipment ID&lt;/li&gt;
&lt;li&gt;Current shipment status&lt;/li&gt;
&lt;li&gt;ETA&lt;/li&gt;
&lt;li&gt;Last carrier update&lt;/li&gt;
&lt;li&gt;Total number of records matching a query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend determines the current business state. The frontend presents a snapshot of that information.&lt;/p&gt;

&lt;h2&gt;
  
  
  User interface state
&lt;/h2&gt;

&lt;p&gt;This represents the user's current interaction with the page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active status filter&lt;/li&gt;
&lt;li&gt;Current page&lt;/li&gt;
&lt;li&gt;Selected shipment ID&lt;/li&gt;
&lt;li&gt;Expanded details&lt;/li&gt;
&lt;li&gt;Open dialogs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A background data refresh generally should not reset these choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request state
&lt;/h2&gt;

&lt;p&gt;This describes what's happening while the application communicates with the server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initial loading&lt;/li&gt;
&lt;li&gt;Background refresh in progress&lt;/li&gt;
&lt;li&gt;Last successful response&lt;/li&gt;
&lt;li&gt;Request failure&lt;/li&gt;
&lt;li&gt;Data that may be stale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This third group matters because a screen can have valid data and an active request at the same time. A user should still be able to inspect the current results while the application retrieves newer information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the user's current view explicit
&lt;/h2&gt;

&lt;p&gt;Suppose a shipment table supports two filters: All and Delayed. The user can also navigate between pages and select a shipment. Those interactions can be represented independently from the returned shipment records.&lt;/p&gt;

&lt;p&gt;Zustand example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zustand&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StatusFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delayed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ShipmentViewState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StatusFilter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;selectedShipmentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StatusFilter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;setPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;selectShipment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useShipmentView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ShipmentViewState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;selectedShipmentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;selectedShipmentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;

    &lt;span class="na"&gt;setPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;

    &lt;span class="na"&gt;selectShipment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;selectedShipmentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing the status filter resets pagination to page 1. The previous page might not exist in the filtered result.&lt;/p&gt;

&lt;p&gt;Selection is stored by shipment ID. If shipment records are refreshed, the application can still identify the selected item. The selected object doesn't need to be stored as a separate copy of the entire record.&lt;/p&gt;

&lt;p&gt;Whether selection should be cleared when filters change depends on the workflow. In this example, we clear it explicitly. In other products, keeping the detail view open may be preferable.&lt;/p&gt;

&lt;p&gt;The important part is making that behavior intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep polling separate from table interactions
&lt;/h2&gt;

&lt;p&gt;In LogiCore, shipment status and ETA information are refreshed through background requests to the REST API.&lt;/p&gt;

&lt;p&gt;Polling is a reasonable approach when the application needs periodic updates and the source data doesn't require a continuous streaming connection. The challenge is managing those requests as the user changes the current view.&lt;/p&gt;

&lt;p&gt;A naive implementation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/shipments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;setShipments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn't account for current filters or pagination. It has no error handling. Requests can overlap if one response takes longer than the interval.&lt;/p&gt;

&lt;p&gt;It also doesn't address what happens when the component unmounts or the user switches to a different query. A more reliable implementation gives the polling process a clear lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  React polling example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Shipment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ShipmentPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Shipment&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FeedState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ShipmentPage&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;syncing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useShipmentFeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFeed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FeedState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;syncing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;setFeed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;syncing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nf"&gt;setFeed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;syncing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;previous&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s2"&gt;`/api/shipments?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ShipmentPage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setFeed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;syncing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aborted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setFeed&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
              &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;syncing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not refresh shipments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;previous&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;syncing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The endpoint, response shape, and 15-second interval are illustrative.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Several common problems here:&lt;/p&gt;

&lt;h2&gt;
  
  
  Requests follow the active query
&lt;/h2&gt;

&lt;p&gt;The effect depends on the current filter and page. When either changes, React cleans up the previous effect and starts a new request process for the new query. &lt;/p&gt;

&lt;h2&gt;
  
  
  Old requests cannot update the new view
&lt;/h2&gt;

&lt;p&gt;The cleanup aborts the previous fetch. The active check also prevents a response from updating component state after its effect has been cleaned up. That matters because network requests do not necessarily finish in the order they started. Imagine the user selects Delayed and immediately switches back to All.&lt;/p&gt;

&lt;p&gt;The Delayed request may finish last. Its response should not replace the current All result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polling requests don't overlap
&lt;/h2&gt;

&lt;p&gt;The next timer is scheduled after the current request settles. That prevents this particular polling loop from starting another request while its previous request is still pending. In a production application, I would also consider an upper limit on request duration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Existing results remain available during a refresh
&lt;/h2&gt;

&lt;p&gt;After the initial load succeeds, setting syncing to true doesn't clear the current response. The interface can continue showing its most recent results while the next request is in progress. This makes a useful distinction between initial loading and refreshing existing data.&lt;/p&gt;

&lt;p&gt;For a real application, I would also consider request deduplication, visibility-aware polling, authentication expiration, retry backoff, and how multiple components share the same data. A dedicated server-state library may be appropriate when those responsibilities grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give every refresh a predictable UI behavior
&lt;/h2&gt;

&lt;p&gt;Fetching current data is only half the implementation. The frontend also needs rules for presenting that data. Consider a shipment with this information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6cz2d9txey9szdky4hk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi6cz2d9txey9szdky4hk.png" alt="A shipment record before and after an update: status, ETA, and the latest carrier timestamp." width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the user is looking at that shipment, the interface should present the newer information in the relevant record. But the application must also decide what happens if the new status changes which rows match the active filter.&lt;/p&gt;

&lt;p&gt;Suppose the current view shows only In transit shipments. After a refresh, a shipment becomes Delayed.&lt;/p&gt;

&lt;p&gt;Should it disappear immediately?&lt;/p&gt;

&lt;p&gt;There isn't one correct answer for every operational product. Immediate removal maintains strict consistency with the filter. It can also make a row disappear while someone is examining it.&lt;/p&gt;

&lt;p&gt;An alternative interaction is to retain the currently displayed set temporarily and indicate that newer results are available.&lt;/p&gt;

&lt;p&gt;The appropriate behavior depends on the task. For a monitoring dashboard, timely changes may take priority. For a table where operators select records and perform multi-step actions, preserving working context may be more important. This is a product decision that frontend and UX teams should agree on before implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat timestamps as part of data reliability
&lt;/h2&gt;

&lt;p&gt;A label such as "Last updated" sounds simple, but developers need to agree on what it represents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the external carrier produced the information&lt;/li&gt;
&lt;li&gt;When the backend received the update&lt;/li&gt;
&lt;li&gt;When the API response was generated&lt;/li&gt;
&lt;li&gt;When the frontend successfully fetched the response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These times can differ. A successful API request at 12:30 doesn't prove that the carrier supplied new data at 12:30.&lt;/p&gt;

&lt;p&gt;In LogiCore, shipment monitoring includes status information, ETA drift indicators, and last-update timestamps associated with refreshed carrier data.&lt;/p&gt;

&lt;p&gt;For similar systems, I would make timestamp ownership explicit in the API contract. The response might distinguish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shipmentId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SHP-2048"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"delayed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-23T16:10:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"carrierUpdatedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-23T12:24:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fetchedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-23T12:30:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can show when the shipment information last changed and separately track when the application last synchronized.&lt;/p&gt;

&lt;p&gt;This distinction becomes useful during outages, integration delays, and incidents where a user needs to know how current the information actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  A failed refresh should be visible
&lt;/h2&gt;

&lt;p&gt;Imagine a dispatcher reviewing 40 shipments. The page initially loads correctly. Five minutes later, the network becomes unstable and the background refresh fails.&lt;/p&gt;

&lt;p&gt;Should the table disappear?&lt;/p&gt;

&lt;p&gt;Usually, the application still has a previously successful response that the user can inspect. The interface can preserve those records and show that the latest refresh failed. A useful set of UI states will include:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fam6v76ts8yx1p7zefbwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fam6v76ts8yx1p7zefbwt.png" alt="How the shipment table handles loading, refreshes, errors, empty results, and expired sessions." width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An expired session needs special handling. The product should not keep issuing the same unauthorized request every few seconds. If the request fails repeatedly, retry logic may need an increasing delay.&lt;/p&gt;

&lt;p&gt;These are recommended patterns for operational software. Their exact implementation should follow the application's security requirements and data sensitivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't let background updates interrupt active work
&lt;/h2&gt;

&lt;p&gt;There is another problem that appears when a product moves beyond read-only dashboards.&lt;/p&gt;

&lt;p&gt;Suppose a user is editing the assigned carrier or reviewing a shipment exception while new information arrives from the backend. A refresh that blindly replaces an entire local object can overwrite in-progress changes.&lt;/p&gt;

&lt;p&gt;For editable records, I would define which fields belong to the current server snapshot and which fields belong to an active local operation. The application also needs a policy for conflicts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Has the record changed since the user opened it?&lt;/li&gt;
&lt;li&gt;Is the user editing the same field that changed on the server?&lt;/li&gt;
&lt;li&gt;Can the operation be submitted against the newer record version?&lt;/li&gt;
&lt;li&gt;Does the user need to review the difference before continuing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on the product, that may require version fields, conditional updates, or a conflict-resolution interaction.&lt;/p&gt;

&lt;p&gt;These decisions are especially important in ERP, CRM, warehouse management, and other B2B applications where multiple users work with related records. This is a general implementation consideration, not a claim about a specific LogiCore editing workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would test before release
&lt;/h2&gt;

&lt;p&gt;For a React interface with changing operational data, I would test scenarios beyond the happy path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rapid filter changes&lt;/strong&gt;. Switch between filters while requests are pending. Confirm that the UI displays the result for the current query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slow responses&lt;/strong&gt;. Simulate a request taking longer than the normal refresh interval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unmounting&lt;/strong&gt;. Navigate away while a request is pending. Confirm that the polling process stops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeated failures&lt;/strong&gt;. Simulate network errors and authentication expiration. Check what remains visible and how the interface recovers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changing records&lt;/strong&gt;. Update a shipment while a user is reviewing its details. Confirm that selection and navigation behave as intended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inactive tabs&lt;/strong&gt;. Check whether background polling continues unnecessarily when the page isn't being used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Different refresh speeds&lt;/strong&gt;. Evaluate whether status changes appear frequently enough for the operational requirements and whether polling creates unnecessary backend traffic.&lt;/p&gt;

&lt;p&gt;I would also review accessibility: refresh indicators should communicate meaningful changes, and frequent updates should not continuously interrupt screen-reader users.&lt;/p&gt;

&lt;h2&gt;
  
  
  What LogiCore taught us about operational frontend architecture
&lt;/h2&gt;

&lt;p&gt;UITOP designed and developed LogiCore as one application connecting shipment monitoring, warehouse structures, inventory, and operational data.&lt;/p&gt;

&lt;p&gt;The shipment interface uses React and TypeScript, with TanStack Table for table behavior and Zustand-managed polling for refreshed shipment information.&lt;/p&gt;

&lt;p&gt;The backend uses Node.js, Express, PostgreSQL, Prisma, and REST APIs.&lt;/p&gt;

&lt;p&gt;Those implementation decisions illustrate an important relationship between the frontend and its data sources. Users control filters, pagination, and navigation. The backend supplies current operational records. The request lifecycle determines when newer data reaches the interface.&lt;/p&gt;

&lt;p&gt;A frontend architecture needs to manage those responsibilities consistently as the product grows. If I were reviewing an existing B2B SaaS product today, I would start with one practical scenario:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when the user changes a filter at the same moment a background request returns newer data?&lt;/strong&gt;&lt;br&gt;
The answer reveals a lot about the quality of the application's state management, request handling, and user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you read this, read also about the project and the team
&lt;/h2&gt;

&lt;p&gt;LogiCore is a logistics and warehouse SaaS designed and developed by UITOP, a product design and software development team specializing in complex B2B applications.&lt;/p&gt;

&lt;p&gt;The project covered product design, frontend and backend development, database architecture, API development, and QA.&lt;/p&gt;

&lt;p&gt;Read the &lt;a href="https://uitop.design/case-studies/logicore/" rel="noopener noreferrer"&gt;LogiCore case study&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>frontend</category>
      <category>saas</category>
    </item>
    <item>
      <title>Building a shipment table for thousands of live records. What I learned on LogiCore?</title>
      <dc:creator>Oleksandr Sainchuk</dc:creator>
      <pubDate>Tue, 13 Oct 2026 15:30:58 +0000</pubDate>
      <link>https://dev.to/oleksandr_sainchuk/building-a-shipment-table-for-thousands-of-live-records-what-i-learned-on-logicore-51np</link>
      <guid>https://dev.to/oleksandr_sainchuk/building-a-shipment-table-for-thousands-of-live-records-what-i-learned-on-logicore-51np</guid>
      <description>&lt;p&gt;A shipment table seems easy, a few columns, some filters, a status badge. Then real data shows up...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;THOUSANDS&lt;/strong&gt; of shipments, statuses that change all day, and operators who need to find the one late truck fast.&lt;/p&gt;

&lt;p&gt;A table that feels fine with 50 test rows can crawl with 5,000.&lt;/p&gt;

&lt;p&gt;I ran into this on LogiCore, a logistics and warehouse management platform UITOP designed and built from scratch, for a UK logistics company. The client ran a multi-million dollar business across three or four separate tools. Data didn’t match between them, updates came late, and managers made big calls without the full picture. They wanted one system for the warehouse floor and the office.&lt;/p&gt;

&lt;p&gt;Our design, project management, development and QA teams worked on it together. Here’s how we built the shipment table, and why we made each choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the table had to do
&lt;/h2&gt;

&lt;p&gt;Operators used the table to find shipments and spot the ones that need attention. And they did it while statuses and ETAs kept changing.&lt;/p&gt;

&lt;p&gt;The frontend runs on &lt;strong&gt;React&lt;/strong&gt;, &lt;strong&gt;TypeScript&lt;/strong&gt; and &lt;strong&gt;Vite&lt;/strong&gt;, with TanStack Table (React Table v8), &lt;strong&gt;Zustand&lt;/strong&gt; and &lt;strong&gt;Tailwind CSS&lt;/strong&gt;. The backend uses &lt;strong&gt;Node.js&lt;/strong&gt;, &lt;strong&gt;Express&lt;/strong&gt;, &lt;strong&gt;PostgreSQL&lt;/strong&gt; and &lt;strong&gt;Prisma ORM&lt;/strong&gt; behind a REST API.&lt;/p&gt;

&lt;p&gt;We had three separate problems to solve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;too much data to load, &lt;/li&gt;
&lt;li&gt;too many rows to draw,&lt;/li&gt;
&lt;li&gt;data that goes stale while the page is open.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Problem 1: the browser doesn’t need every shipment
&lt;/h2&gt;

&lt;p&gt;Why load thousands of records when the operator looks at one screen at a time? Loading everything makes the first view slow and wastes memory.&lt;/p&gt;

&lt;p&gt;So we used server-side pagination. The frontend asks the API for one page. PostgreSQL runs the query with the active filters and returns only that page.&lt;/p&gt;

&lt;p&gt;Details matter more than they look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reset the page when a filter changes. If someone sits on page 12 and the new filter returns three pages, they should land on page 1, not on an empty screen.&lt;/li&gt;
&lt;li&gt;Keep sorting stable. Records change all day, so the API needs a fixed order. Otherwise rows jump between pages and people lose their place.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem 2: one page can still be heavy
&lt;/h2&gt;

&lt;p&gt;Pagination cuts how much data comes back. It doesn’t cut how much the browser has to draw. Each shipment row carries a status badge, an ETA drift indicator and a “Last Updated” timestamp. Draw hundreds of those at once, and React has a lot of work to do.&lt;/p&gt;

&lt;p&gt;So we added row virtualization on top. The table only mounts the rows on screen, plus a small buffer for scrolling. TanStack Table handles the table logic, and the virtualization layer decides which rows exist in the DOM right now.&lt;/p&gt;

&lt;p&gt;The two techniques solve different problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server-side pagination controls how much data travels from the database to the browser.&lt;/li&gt;
&lt;li&gt;Virtualization controls how much the browser renders.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you test both: API response times and rendering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9w76xrdkz5t1tdcywy0o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9w76xrdkz5t1tdcywy0o.png" alt="Left node: a white rounded platform holding a floating frosted-glass UI panel showing a data table with rows, a pagination control and a filter chip, rendered in blue glass. Small clean label floating above: " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: the data changes while someone is looking at it
&lt;/h2&gt;

&lt;p&gt;A shipment moves to a new status. An ETA slips. A carrier sends an update. If the screen shows old data, the operator decides based on a stale picture, and that’s the problem LogiCore was built to fix.&lt;/p&gt;

&lt;p&gt;We handled this with background polling. A Zustand store fetches fresh data from the REST API on a schedule, and the badges, ETA drift indicators and timestamps update from it. “Last Updated” shows the latest carrier update, not a cached value.&lt;/p&gt;

&lt;p&gt;The tricky part here is UX. If a badge changes while someone reads the list, the table shouldn’t reset their filters or scroll position. Polling also raises its own questions: how often to refresh, what to do with failed requests, and whether to keep polling when nobody is at the screen.&lt;/p&gt;

&lt;p&gt;Polling fit LogiCore’s update rhythm. A product that needs second-by-second updates might be better off with WebSockets or server-sent events.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backend decides how fast the table feels
&lt;/h2&gt;

&lt;p&gt;A slow query makes even a well-tuned React table feel broken. So a lot of the speed work happened below the UI.&lt;/p&gt;

&lt;p&gt;On the inventory screen, a multi-location SKU expands with a scoped REST call that fetches only that item’s child records. The first load stays at 100 rows, no matter how deep the data goes. The warehouse location tree loads the same way, one level at a time, from facility down to shelf.&lt;/p&gt;

&lt;p&gt;Filters by client, warehouse or SKU run through Prisma’s dynamic where clauses. That way we avoid raw SQL and still support any filter combination. For the KPI cards, PostgreSQL window functions calculate week-over-week changes inside the database, and the frontend just shows the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design and performance are one conversation
&lt;/h2&gt;

&lt;p&gt;More columns don’t make a table more useful. They make it harder to scan. We gave status and timing a fixed place in each row, because that’s what operators check first.&lt;/p&gt;

&lt;p&gt;Every design choice here has a frontend cost. Columns decide how heavy each cell is. Filters decide what the API has to query. And the design system needs states for loading and errors, not only the happy path.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came out of it
&lt;/h2&gt;

&lt;p&gt;Before launch, our QA team ran stress tests under heavy load, and the platform held at 100% stability. The office and the warehouse now work from the same live data. Managers see transit delays and stock shortages as they happen, so they fix problems before those cost money. Daily shipping decisions got 2x faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start with your own table
&lt;/h2&gt;

&lt;p&gt;If you’re building a B2B SaaS product with big tables, start with three questions. How much data does the screen request? How much does it render? What happens when the records change? The answers usually point to the next engineering decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do you make a React table fast with thousands of records?&lt;/strong&gt;&lt;br&gt;
Combine server-side pagination with row virtualization. Pagination limits what the API sends, and virtualization limits what the browser draws. On LogiCore, we used both with TanStack Table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should a logistics dashboard use polling or WebSockets?&lt;/strong&gt;&lt;br&gt;
It depends on how fast the data changes. Polling worked for LogiCore’s shipment statuses and ETAs. If operators need second-by-second updates, WebSockets or server-sent events fit better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should I look for in a team building custom warehouse management software?&lt;/strong&gt;&lt;br&gt;
Look for a team that covers design, backend and QA together, because table speed depends on the database as much as on the UI. UITOP built LogiCore that way, and the platform held 100% stability under peak-load stress tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  About the project
&lt;/h2&gt;

&lt;p&gt;LogiCore is a logistics and warehouse management platform designed and built by UITOP, a product design and development studio for complex B2B software.&lt;/p&gt;

&lt;p&gt;Case study: &lt;a href="https://uitop.design/case-studies/logicore/" rel="noopener noreferrer"&gt;https://uitop.design/case-studies/logicore/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
