[{"data":1,"prerenderedAt":487},["ShallowReactive",2],{"bootstrap":3,"blog-frontend-components-refactoring-core-and-hooks":277},{"navigation":4,"globalConfig":196,"forms":218},{"headerSections":5,"headerFooterLinks":67,"footer":76,"legalLinks":153,"socialLinks":176},[6,39,44,50,57,62],{"hasSubMenu":7,"title":8,"links":9,"columns":38},true,"Product",[10,20,26,32],{"showTag":11,"_modelApiKey":12,"text":13,"subtitle":14,"icon":15,"isExternal":11,"link":16,"openForm":11},false,"navigation_link","Universal checkout","Sell anywhere","globe-simple",{"_modelApiKey":17,"slug":18,"title":19},"page","universal-checkout","Universal Checkout",{"showTag":11,"_modelApiKey":12,"text":21,"subtitle":22,"icon":23,"isExternal":11,"link":24,"openForm":11},"Distributed OMS","Manage orders","package",{"_modelApiKey":17,"slug":25,"title":21},"distributed-oms",{"showTag":11,"_modelApiKey":12,"text":27,"subtitle":28,"icon":29,"isExternal":11,"link":30,"openForm":11},"Promotion engine","Automated discounts","seal-percent",{"_modelApiKey":17,"slug":31,"title":27},"promotion-engine",{"showTag":11,"_modelApiKey":12,"text":33,"subtitle":34,"icon":35,"isExternal":11,"link":36,"openForm":11},"POS system","Operate stores","devices",{"_modelApiKey":17,"slug":37,"title":33},"pos-system",2,{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":40,"isExternal":11,"link":41,"openForm":11,"title":40},"Pricing",{"_modelApiKey":42,"slug":43},"pricing_page","pricing",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":45,"isExternal":11,"link":46,"openForm":11,"title":45},"Customers",{"_modelApiKey":47,"slug":48,"title":49},"customer_landing_page","customers","Customers page",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":51,"isExternal":11,"link":52,"openForm":11,"form":54,"title":51},"Partners",{"_modelApiKey":17,"slug":53,"title":51},"partners",{"_modelApiKey":55,"slug":53,"name":56},"form","PartnersForm",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":58,"label":59,"isExternal":7,"url":60,"openForm":11,"title":61},"Docs Hub","Learn more","https:\u002F\u002Fdocs.commercelayer.io\u002F","Docs",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":63,"isExternal":11,"link":64,"openForm":11,"title":63},"Blog",{"_modelApiKey":65,"slug":66},"blog_landing_page","blog",[68,72],{"text":69,"link":70},"Sign in",{"_modelApiKey":12,"text":69,"isExternal":7,"url":71,"openForm":11},"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_in",{"text":73,"link":74},"Sign up for free",{"_modelApiKey":12,"text":73,"isExternal":7,"url":75,"openForm":11},"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_up",[77,89,109,132],{"hasSubMenu":7,"title":8,"links":78},[79,81,83,85,87],{"showTag":11,"_modelApiKey":12,"text":13,"subtitle":14,"icon":15,"isExternal":11,"link":80,"openForm":11},{"_modelApiKey":17,"slug":18,"title":19},{"showTag":11,"_modelApiKey":12,"text":21,"subtitle":22,"icon":23,"isExternal":11,"link":82,"openForm":11},{"_modelApiKey":17,"slug":25,"title":21},{"showTag":11,"_modelApiKey":12,"text":27,"subtitle":28,"icon":29,"isExternal":11,"link":84,"openForm":11},{"_modelApiKey":17,"slug":31,"title":27},{"showTag":11,"_modelApiKey":12,"text":33,"subtitle":34,"icon":35,"isExternal":11,"link":86,"openForm":11},{"_modelApiKey":17,"slug":37,"title":33},{"showTag":11,"_modelApiKey":12,"text":40,"isExternal":11,"link":88,"openForm":11},{"_modelApiKey":42,"slug":43},{"hasSubMenu":7,"title":90,"links":91},"Resources",[92,94,99,104,107],{"showTag":11,"_modelApiKey":12,"text":93,"isExternal":7,"url":60,"openForm":11},"Documentation",{"showTag":11,"_modelApiKey":12,"text":95,"label":95,"isExternal":11,"link":96,"openForm":11},"Agentic Commerce",{"_modelApiKey":17,"slug":97,"title":98},"agentic-commerce-whitepaper","Agentic Commerce Whitepaper",{"showTag":11,"_modelApiKey":12,"text":100,"isExternal":11,"link":101,"openForm":11},"Core concepts",{"_modelApiKey":17,"slug":102,"title":103},"headless-commerce","Headless commerce",{"showTag":11,"_modelApiKey":12,"text":105,"isExternal":7,"url":106,"openForm":11},"Changelog","https:\u002F\u002Fdocs.commercelayer.io\u002Fchangelog\u002F",{"showTag":11,"_modelApiKey":12,"text":63,"isExternal":11,"link":108,"openForm":11},{"_modelApiKey":65,"slug":66},{"hasSubMenu":7,"title":110,"links":111},"Company",[112,116,118,121,126],{"showTag":11,"_modelApiKey":12,"text":113,"isExternal":11,"link":114,"openForm":11},"Vision",{"_modelApiKey":17,"slug":115,"title":113},"vision",{"showTag":11,"_modelApiKey":12,"text":45,"isExternal":11,"link":117,"openForm":11},{"_modelApiKey":47,"slug":48,"title":49},{"showTag":11,"_modelApiKey":12,"text":51,"isExternal":11,"link":119,"openForm":11,"form":120},{"_modelApiKey":17,"slug":53,"title":51},{"_modelApiKey":55,"slug":53,"name":56},{"showTag":11,"_modelApiKey":12,"text":122,"isExternal":11,"link":123,"openForm":11},"Security",{"_modelApiKey":17,"slug":124,"title":125},"security","Security at Commerce Layer",{"showTag":11,"_modelApiKey":12,"text":127,"label":128,"isExternal":11,"openForm":7,"form":129},"Giveback","Giveback program",{"_modelApiKey":55,"slug":130,"name":131},"giveback","GivebackForm",{"hasSubMenu":7,"title":133,"links":134},"Get in touch",[135,140,145,149],{"showTag":11,"_modelApiKey":12,"text":136,"isExternal":11,"openForm":7,"form":137},"Contact",{"_modelApiKey":55,"slug":138,"name":139},"contact","ContactUsForm",{"showTag":11,"_modelApiKey":12,"text":141,"isExternal":11,"openForm":7,"form":142},"Book a demo",{"_modelApiKey":55,"slug":143,"name":144},"demo","RequestDemoForm",{"showTag":11,"_modelApiKey":12,"text":146,"label":147,"isExternal":7,"url":148,"openForm":11},"Support","Email support","mailto:support@commercelayer.io",{"showTag":11,"_modelApiKey":12,"text":150,"label":151,"isExternal":7,"url":152,"openForm":11},"Community","Discord community","https:\u002F\u002Fdiscord.gg\u002Fcommercelayer",{"hasSubMenu":7,"title":154,"links":155},"Legal",[156,161,165,169,173],{"showTag":11,"_modelApiKey":12,"text":157,"label":158,"isExternal":7,"url":159,"openForm":11,"htmlClassNames":160},"Terms","Terms and conditions","\u002Flegal-embed\u002Fterms-of-service","iubenda-nostyle no-brand iubenda-noiframe iubenda-embed",{"showTag":11,"_modelApiKey":12,"text":162,"label":163,"isExternal":7,"url":164,"openForm":11,"htmlClassNames":160},"Privacy","Privacy policy","\u002Flegal-embed\u002Fprivacy-policy",{"showTag":11,"_modelApiKey":12,"text":166,"label":167,"isExternal":7,"url":168,"openForm":11,"htmlClassNames":160},"Cookies","Cookie policy","\u002Flegal-embed\u002Fcookie-policy",{"showTag":11,"_modelApiKey":12,"text":170,"isExternal":7,"url":171,"openForm":11,"htmlClassNames":172},"Notice at collection","#","iubenda-cs-uspr-link",{"showTag":11,"_modelApiKey":12,"text":174,"isExternal":7,"url":171,"openForm":11,"htmlClassNames":175},"Privacy preferences","iubenda-cs-preferences-link",[177,180,184,188,192],{"title":178,"type":179,"url":152,"isExternal":7},"Discord","discord-logo",{"title":181,"type":182,"url":183,"isExternal":7},"Github","github","https:\u002F\u002Fgithub.com\u002Fcommercelayer",{"title":185,"type":186,"url":187,"isExternal":7},"Bluesky","bluesky-logo","https:\u002F\u002Fbsky.app\u002Fprofile\u002Fcommercelayer.io",{"title":189,"type":190,"url":191,"isExternal":7},"X","x-logo","https:\u002F\u002Fx.com\u002Fcommercelayer",{"title":193,"type":194,"url":195,"isExternal":7},"LinkedIn","linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fcommerce-layer",{"pagesWithoutCtaPanel":197,"ctaPanel":198,"notification":208,"showApiResponseTime":11,"newsletterSignup":216},[],{"title":199,"subtitle":200,"ctas":201},"Ready to get started?","Discover how we can help your business.",[202,205],{"_modelApiKey":203,"text":204,"isExternal":7,"url":75,"openForm":11},"link","Get started for free",{"_modelApiKey":203,"text":206,"isExternal":11,"openForm":7,"form":207},"Contact us",{"_modelApiKey":55,"slug":138,"name":139},{"showNotification":7,"link":209,"linkText":210,"text":215},{"_modelApiKey":203,"text":210,"isExternal":11,"link":211,"openForm":11},"Read article",{"_modelApiKey":212,"slug":213,"title":214},"blog_post","the-new-payments-api-built-for-how-people-pay-now","The new Payments API. Built for how people pay now.","\u003Cp>The new Payments API. Built for how people pay now.\u003C\u002Fp>\n",{"ctaText":217},"Subscribe",[219,225,229,236,240,246,249,253,261,262,267,272,274],{"_modelApiKey":55,"slug":220,"pardotFormEmbedCode":221,"formEmbedMinHeight":222,"successTitle":223,"successCopy":224},"agentic-commerce","https:\u002F\u002Fcommercelayer.fillout.com\u002Ft\u002F5w4Khwgxr6us?whitepaper=agentic",520,"Thank you for your request!","We’ll be in touch soon to arrange a date and time for your personalized demo.",{"_modelApiKey":55,"slug":226,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"successTitle":223,"successCopy":224},"shopify","https:\u002F\u002Fform.fillout.com\u002Ft\u002FgVnLSmKZwRus",850,{"_modelApiKey":55,"slug":130,"pardotFormEmbedCode":230,"formEmbedMinHeight":231,"displayTitle":232,"copy":233,"successTitle":234,"successCopy":235},"https:\u002F\u002Fform.fillout.com\u002Ft\u002FmVDF8aAiofus",500,"We give back.","\u003Cp>Part of Commerce Layer&#39;s company ethos is to empathize and support one another: co-workers, customers, and community. We want to do our part to help nonprofit organizations and small businesses who may feel adversely affected by the current global economic climate, and as a result, need in-kind support to reach their customers.\u003C\u002Fp>\n\n\u003Cp>If this general description matches your organization’s needs, please tell us your story by completing the following form so we can consider helping your cause.\u003C\u002Fp>\n","Thank you!","We will contact you soon to learn more about your story.",{"_modelApiKey":55,"slug":237,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":238,"copy":239},"book-an-onboarding-session","Book an onboarding session","\u003Cp>Schedule some time and our devs will help you set up a Commerce Layer + Sanity store.\u003C\u002Fp>\n",{"_modelApiKey":55,"slug":241,"pardotFormEmbedCode":242,"formEmbedMinHeight":243,"displayTitle":244,"successTitle":234,"successCopy":245},"download-our-whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=ComposableCommerce",450,"Download our whitepaper.","Check your inbox. You should receive a download link soon.",{"_modelApiKey":55,"slug":247,"pardotFormEmbedCode":248,"formEmbedMinHeight":243,"displayTitle":244,"successTitle":234,"successCopy":245},"download-the-content-and-commerce-whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=ContentAndCommerce",{"_modelApiKey":55,"slug":250,"pardotFormEmbedCode":251,"formEmbedMinHeight":243,"displayTitle":244,"copy":252,"successTitle":234,"successCopy":245},"whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=Jamstack","\u003Cp>Commerce Layer is the perfect platform for building commerce on the Jamstack. Convert any design into pixel perfect HTML pages. Connect a best-of-breed headless CMS. Go serverless and forget the hassle of managing complex infrastructures.\u003C\u002Fp>\n",{"_modelApiKey":55,"slug":254,"pardotFormEmbedCode":255,"formEmbedMinHeight":256,"displayTitle":257,"copy":258,"successTitle":259,"successCopy":260},"contact-links","https:\u002F\u002Fform.fillout.com\u002Ft\u002FeWPyhHGHT7us",750,"Contact us.","\u003Cp>Tell us about your project. Our team will reach out to you for a personalized offer.\u003C\u002Fp>\n","Thanks for your message!","We’ll be in touch soon to discuss your project details.",{"_modelApiKey":55,"slug":138,"pardotFormEmbedCode":255,"formEmbedMinHeight":256,"displayTitle":257,"copy":258,"successTitle":259,"successCopy":260},{"_modelApiKey":55,"slug":263,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":264,"successTitle":265,"successCopy":266},"demo-embed","Want to see it in action? Request a demo.","Thanks for your request!","We’ll be in touch soon to arrange a date and time for your free live demo.",{"_modelApiKey":55,"slug":53,"pardotFormEmbedCode":268,"formEmbedMinHeight":228,"displayTitle":269,"copy":270,"successTitle":234,"successCopy":271},"https:\u002F\u002Fform.fillout.com\u002Ft\u002F9MTkDWMUSqus","Become our partner.","\u003Cp>Join us and get our guidance in helping your clients succeed. Our partners are our ambassadors. We are committed to your success as much as you are.\u003C\u002Fp>\n","We will contact you soon to discuss the details of our partnership.",{"_modelApiKey":55,"slug":273,"pardotFormEmbedCode":268,"formEmbedMinHeight":228,"displayTitle":269,"copy":270,"successTitle":234,"successCopy":271},"partners-embed",{"_modelApiKey":55,"slug":143,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":275,"copy":276,"successTitle":265,"successCopy":266},"Get a free live demo.","\u003Cp>Get a personalized demo and learn more about what Commerce Layer can do for your company.\u003C\u002Fp>\n",{"_modelApiKey":212,"link":278,"subtitle":283,"date":283,"author":284,"additionalAuthors":294,"icon":295,"abstract":296,"tag":297,"displayTitle":279,"cardRatio":299,"featured":11,"seoMetaTags":300,"layout":364,"blogPostCarousel":302,"_seoMetaTags":448,"authors":482},{"text":279,"isExternal":11,"url":280,"link":281},"Frontend components refactoring #1: core and hooks","",{"_modelApiKey":212,"slug":282},"frontend-components-refactoring-core-and-hooks","August 31, 2026",{"name":285,"picture":286},"Alessandro Casazza",{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":291,"customData":293},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1787929051-alessandro-cas.jpg","Alessandro Casazza avatar",1280,"jpg",{"x":292,"y":292},0.5,{},[],"atom","The first step in a multi-part series on rebuilding our React library by extracting framework-independent business logic into reusable TypeScript packages.",{"color":298,"name":8},"gray","portrait",[301,305,309,312,316,319,322,326,330,334,337,341,345,349,353,356,360],{"attributes":302,"tag":303,"content":304},null,"title","Refactoring Commerce Layer's React Library: Core and Hooks",{"attributes":306,"tag":308,"content":302},{"property":307,"content":304},"og:title","meta",{"attributes":310,"tag":308,"content":302},{"name":311,"content":304},"twitter:title",{"attributes":313,"tag":308,"content":302},{"name":314,"content":315},"description","Discover how Commerce Layer refactored its React library by separating framework-independent business logic into reusable TypeScript packages.",{"attributes":317,"tag":308,"content":302},{"property":318,"content":315},"og:description",{"attributes":320,"tag":308,"content":302},{"name":321,"content":315},"twitter:description",{"attributes":323,"tag":308,"content":302},{"property":324,"content":325},"og:image","https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1788178579-fe-refactor-react-components-1.jpg?auto=format&fit=max&w=1200",{"attributes":327,"tag":308,"content":302},{"property":328,"content":329},"og:image:width","1200",{"attributes":331,"tag":308,"content":302},{"property":332,"content":333},"og:image:height","630",{"attributes":335,"tag":308,"content":302},{"name":336,"content":325},"twitter:image",{"attributes":338,"tag":308,"content":302},{"property":339,"content":340},"og:locale","en",{"attributes":342,"tag":308,"content":302},{"property":343,"content":344},"og:type","article",{"attributes":346,"tag":308,"content":302},{"property":347,"content":348},"og:site_name","Commerce Layer",{"attributes":350,"tag":308,"content":302},{"property":351,"content":352},"article:modified_time","2026-08-31T14:05:09Z",{"attributes":354,"tag":308,"content":302},{"property":355,"content":280},"article:publisher",{"attributes":357,"tag":308,"content":302},{"name":358,"content":359},"twitter:card","summary_large_image",{"attributes":361,"tag":308,"content":302},{"name":362,"content":363},"twitter:site","@commercelayer",[365,370,376,381,384,387,404,407,410,414,417,419,422,425,427,430,433,436,439,442,445],{"_modelApiKey":366,"icon":367,"copy":368,"color":369},"banner","code-simple","\u003Cp>This article is written for frontend developers and marks the beginning of a new chapter for the Commerce Layer React ecosystem: the launch of a deeply refactored architecture for our \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fcommercelayer-react-components\">React Components library\u003C\u002Fa>. It is the first episode of a series where we’ll progressively unpack the technical decisions behind the refactoring, explain the architectural changes introduced, and explore the concrete benefits they bring in terms of maintainability, scalability, performance, and developer experience.\u003C\u002Fp>\n","violet",{"_modelApiKey":371,"title":372,"preTitle":93,"linkText":373,"darkTheme":11,"link":374},"link_block","React components","Try it on Storybook",{"_modelApiKey":203,"text":373,"label":280,"subtitle":280,"icon":280,"isExternal":7,"url":375,"link":302,"anchor":280,"openForm":11,"form":302,"htmlClassNames":280},"https:\u002F\u002Fcommercelayer.github.io\u002Fcommercelayer-react-components\u002F",{"_modelApiKey":377,"copy":378,"listStyle":379,"darkTheme":11,"buttons":380,"color":369,"indent":7},"wysiwyg","\u003Ch3>The starting point\u003C\u002Fh3>\n\n\u003Cp>It all begins with an 805-line file.\u003C\u002Fp>\n\n\u003Cp>\u003Ccode>OrderReducer.ts\u003C\u002Fcode> is the beating heart of order management in the library. Inside it live API calls, error handling, cart business logic, order creation, updates, coupon management, payment source operations, local state serialization, and action dispatching to the reducer. Everything in a single file. Everything coupled to React.\u003C\u002Fp>\n\n\u003Cp>It&#39;s the perfect symbol of an architecture that worked well for a long time, but started creaking under the weight of its own complexity.\u003C\u002Fp>\n\n\u003Cp>The \u003Ccode>commercelayer-react-components\u003C\u002Fcode> library was born as a monolith — a single npm package exporting ready-to-use React components for building ecommerce experiences powered by the Commerce Layer API. It was a sensible choice at the beginning: a single entry point, one dependency to install, everything under control. But over time, the code grew. And with it, the problems.\u003C\u002Fp>\n","checkbox",[],{"_modelApiKey":377,"copy":382,"listStyle":379,"darkTheme":11,"buttons":383,"color":369,"indent":11},"\u003Ch3>The architecture that was\u003C\u002Fh3>\n\n\u003Cp>The dominant pattern was \u003Cstrong>Container + Reducer + Context\u003C\u002Fstrong>. Let&#39;s take orders as an example.\u003C\u002Fp>\n\n\u003Cp>\u003Ccode>OrderContainer\u003C\u002Fcode> was a React component that fetched the order from the API on mount, stored it in state managed by \u003Ccode>useReducer\u003C\u002Fcode>, and made it available to its children through an \u003Ccode>OrderContext\u003C\u002Fcode>. Every operation — creating an order, adding to cart, applying a coupon — lived inside \u003Ccode>OrderReducer.ts\u003C\u002Fcode>, one function after another, each accepting dispatch, config, state as parameters, mixing pure business logic with React state machinery.\u003C\u002Fp>\n\n\u003Cp>The same pattern repeated everywhere: \u003Ccode>PricesContainer\u003C\u002Fcode> with its own context, \u003Ccode>SkusContainer\u003C\u002Fcode> with its own, \u003Ccode>AvailabilityContainer\u003C\u002Fcode> with its own. Each domain had its own container, its own reducer, its own context. In total, \u003Cstrong>36 context files\u003C\u002Fstrong> and \u003Cstrong>11 reducers\u003C\u002Fstrong> — a maze of distributed global state where every child component had to be nested inside the right container to work.\u003C\u002Fp>\n\n\u003Cp>This approach brought with it several concrete problems.\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>\u003Cstrong>\u003Cem>Total coupling to React\u003C\u002Fem>\u003C\u002Fstrong>---Every function that talked to the Commerce Layer APIs depended on \u003Ccode>Dispatch&lt;OrderActions&gt;\u003C\u002Fcode>, on \u003Ccode>CommerceLayerConfig\u003C\u002Fcode>, on types tied to the React context. Wanted to use the order creation logic in a server-side Node.js script? In a unit test without mounting components? In a Vue or Svelte app? In a React Server Component? Impossible. The business logic was held hostage by the framework.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Cem>The difficulty of testing\u003C\u002Fem>\u003C\u002Fstrong>---Testing \u003Ccode>createOrder\u003C\u002Fcode> meant mocking dispatch, building a compatible config object, simulating state. You weren&#39;t testing business logic — you were testing the integration with React&#39;s state system. Tests became fragile and verbose.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Cem>The &quot;invisible prop drilling&quot; problem\u003C\u002Fem>\u003C\u002Fstrong>---Every child component had to be inside the right container. \u003Ccode>&lt;TotalAmount&gt;\u003C\u002Fcode> only worked inside \u003Ccode>&lt;OrderContainer&gt;\u003C\u002Fcode>. \u003Ccode>&lt;PriceAmount&gt;\u003C\u002Fcode> only inside \u003Ccode>&lt;PricesContainer&gt;\u003C\u002Fcode>. Forgot a wrapper? Runtime error. The TypeScript compiler couldn&#39;t help you — the contract was implicit, hidden in internal \u003Ccode>useContext\u003C\u002Fcode> calls.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Cem>Scattered batching logic\u003C\u002Fem>\u003C\u002Fstrong>---The duplicate request problem was already solved, but the solution was fragmented: each container implemented its own version of the collection and deduplication logic. PricesContainer had its own way of accumulating SKU codes, SkusContainer another. The same idea — collect codes, make a single call, distribute the results — was reimplemented case by case, with different nuances, different bugs, and no possibility of reuse across domains.\u003C\u002Fli>\n\u003C\u002Ful>\n",[],{"_modelApiKey":377,"copy":385,"listStyle":379,"darkTheme":11,"buttons":386,"color":369,"indent":11},"\u003Ch3>The decision\u003C\u002Fh3>\n\n\u003Cp>In April 2026, the time came to change. The idea is simple in its formulation, radical in its execution: \u003Cstrong>separate pure business logic from the React framework, organizing it into independent layers.\u003C\u002Fstrong>\u003C\u002Fp>\n\n\u003Cp>This is how the three-package structure was born:\u003C\u002Fp>\n",[],{"_modelApiKey":388,"darkTheme":11,"table":389,"size":403},"table_block",{"columns":390,"data":393},[391,392],"Package","Description",[394,397,400],{"Package":395,"Description":396},"[Core components](https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fcommercelayer-react-components\u002Ftree\u002Fmain\u002Fpackages\u002Fcore-components)","Pure TypeScript, zero React dependencies.",{"Package":398,"Description":399},"[React hooks components](https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fcommercelayer-react-components\u002Ftree\u002Fmain\u002Fpackages\u002Freact-hooks-components)","React hooks + SWR, depends only on core.",{"Package":401,"Description":402},"[React components](https:\u002F\u002Fgithub.com\u002Fcommercelayer\u002Fcommercelayer-react-components\u002Ftree\u002Fmain\u002Fpackages\u002Freact-components)","UI components, depends on core + hooks.","Wide",{"_modelApiKey":377,"copy":405,"listStyle":379,"darkTheme":11,"buttons":406,"color":369,"indent":11},"\u003Cp>It&#39;s not just a folder reorganization. It&#39;s a change in philosophy.\u003C\u002Fp>\n",[],{"_modelApiKey":377,"copy":408,"listStyle":379,"darkTheme":11,"buttons":409,"color":369,"indent":11},"\u003Ch3>Core — The truth without opinions\u003C\u002Fh3>\n\n\u003Cp>The core package is the foundation of everything. It&#39;s pure TypeScript — no React imports, no \u003Ccode>useState\u003C\u002Fcode>, no \u003Ccode>useEffect\u003C\u002Fcode>, no dispatch. Just functions that do one thing and do it well.\u003C\u002Fp>\n\n\u003Cp>Take order creation. It used to live in \u003Ccode>OrderReducer.ts\u003C\u002Fcode>, among dozens of other functions, coupled to React&#39;s dispatch. Now it looks like this:\u003C\u002Fp>\n",[],{"_modelApiKey":411,"code":412,"showLineNumbers":11,"highlightLines":280,"language":413,"size":403},"code_block","export async function createOrder({\n  accessToken,\n\tinterceptors,\n\tmetadata,\n\tattributes = {},\n  }: CreateOrderParams): Promise\u003COrder> {\n    getSdk({ accessToken, interceptors })\n\t  return await orders.create({ metadata, ...attributes })\n})","javascript",{"_modelApiKey":377,"copy":415,"listStyle":379,"darkTheme":11,"buttons":416,"color":369,"indent":11},"\u003Cp>Ten lines. No React dependency. No dispatch. No try\u002Fcatch that dispatches errors into global state. It takes parameters, calls the SDK, returns a Promise with the created order. Error handling? That&#39;s the caller&#39;s responsibility, not the function&#39;s.\u003C\u002Fp>\n\n\u003Cp>The same goes for \u003Ccode>retrieveOrder\u003C\u002Fcode>, \u003Ccode>updateOrder\u003C\u002Fcode>, \u003Ccode>getPrices\u003C\u002Fcode>, \u003Ccode>getSkus\u003C\u002Fcode>, \u003Ccode>getAvailability\u003C\u002Fcode>. Each function is a self-contained unit that can be imported and used anywhere — in a React component, in a Node.js script, in a test, in a worker, in an app of any framework.\u003C\u002Fp>\n\n\u003Cp>But core doesn&#39;t just wrap the SDK. It also contains \u003Ccode>createBatchStore\u003C\u002Fcode>, an elegant piece of engineering that solves the duplicate request problem. It&#39;s a factory that creates a module-level store where multiple hook instances can register the SKU codes they need. A 50-millisecond debounce collects all codes, produces an immutable snapshot, and notifies subscribers — who then trigger a single fetch. SWR deduplicates further. The result? Ten \u003Ccode>&lt;Price&gt;\u003C\u002Fcode> components on the same page generate a single API call.\u003C\u002Fp>\n",[],{"_modelApiKey":411,"code":418,"showLineNumbers":11,"highlightLines":280,"language":413,"size":403},"export function createBatchStore() {\n  const store = new Map\u003Cstring, StoreEntry>()\n\tfunction registerCode(accessToken: string, code: string): void {\n\t  const entry = getOrCreate(accessToken)\n\t\tif (entry.skuCodes.has(code)) return entry.skuCodes.add(code)\n\t\tscheduleFlush(entry)  \u002F\u002F 50ms debounce → immutable snapshot → notify\n  }\n  return { subscribe, getSnapshot, registerCode, unregisterCode }\n}",{"_modelApiKey":377,"copy":420,"listStyle":379,"darkTheme":11,"buttons":421,"color":369,"indent":11},"\u003Cp>\u003Ccode>createBatchStore\u003C\u002Fcode> is also pure TypeScript. It uses \u003Ccode>Map\u003C\u002Fcode>, \u003Ccode>Set\u003C\u002Fcode>, \u003Ccode>setTimeout\u003C\u002Fcode>. No React. Yet it integrates seamlessly with \u003Ccode>useSyncExternalStore\u003C\u002Fcode> in the layer above.\u003C\u002Fp>\n",[],{"_modelApiKey":377,"copy":423,"listStyle":379,"darkTheme":11,"buttons":424,"color":369,"indent":11},"\u003Ch3>Hooks — The bridge to React\u003C\u002Fh3>\n\n\u003Cp>The hooks package is the translator. It takes the pure functions from core and dresses them with React primitives: \u003Ccode>useState\u003C\u002Fcode> for local state, SWR for data fetching with cache and revalidation, useCallback for referential stability.ap\u003C\u002Fp>\n\n\u003Cp>\u003Ccode>useOrder\u003C\u002Fcode> is the perfect example of this translation:\u003C\u002Fp>\n",[],{"_modelApiKey":411,"code":426,"showLineNumbers":11,"highlightLines":280,"language":413,"size":403},"export function useOrder({\n  accessToken,\n\torderId,\n\tinterceptors\n  }: UseOrderParams): UseOrderReturn {\n    const { data, error, isLoading, mutate } = useSWR\u003COrder>(\n\t    accessToken && orderId ? [\"order\", \"retrieve\", accessToken, orderId] : null,\n\t    async () => retrieveOrder({ accessToken, interceptors, id: orderId! }),\n      { revalidateOnFocus: false }\n  )\n  const createOrder = useCallback(\n    async (params?) => coreCreateOrder({ accessToken, interceptors, ...params }),\n    [accessToken, interceptors]\n\t  )\n  return { order: data, isLoading, error: error?.message ?? null, createOrder, ... }\n}",{"_modelApiKey":377,"copy":428,"listStyle":379,"darkTheme":11,"buttons":429,"color":369,"indent":11},"\u003Cp>No reducer. No dispatch. No context provider. The hook takes an accessToken and an orderId, returns the order and functions to manipulate it. SWR handles caching, deduplication, and revalidation. The component using this hook doesn&#39;t need to be wrapped in any container.\u003C\u002Fp>\n\n\u003Cp>The same pattern repeats for \u003Ccode>usePrices\u003C\u002Fcode>, \u003Ccode>useSkus\u003C\u002Fcode>, \u003Ccode>useSkuList\u003C\u002Fcode>, \u003Ccode>useAvailability\u003C\u002Fcode> — each a self-sufficient hook that composes core functions with React primitives.\u003C\u002Fp>\n",[],{"_modelApiKey":377,"copy":431,"listStyle":379,"darkTheme":11,"buttons":432,"color":369,"indent":11},"\u003Ch3>The concrete improvements\u003C\u002Fh3>\n\n\u003Cp>This separation isn&#39;t cosmetic. It brings measurable benefits.\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>\u003Cstrong>\u003Cem>Radical testability\u003C\u002Fem>\u003C\u002Fstrong>---Testing \u003Ccode>createOrder\u003C\u002Fcode> from core means calling an async function and verifying the result. No \u003Ccode>renderHook\u003C\u002Fcode>, no \u003Ccode>act()\u003C\u002Fcode>, no provider wrappers. Tests are fast, deterministic, easy to write and read.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Cem>Reusability beyond React\u003C\u002Fem>\u003C\u002Fstrong>---A team using Vue, Svelte, or vanilla JavaScript can import \u003Ccode>@commercelayer\u002Fcore-components\u003C\u002Fcode> and have access to all the business logic without dragging React along as a dependency. The core package has zero peer dependencies — just the Commerce Layer SDK and the authentication library.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Cem>Centralized and reusable batching\u003C\u002Fem>\u003C\u002Fstrong>---The request collection and deduplication logic, previously scattered and reimplemented in each container, now lives in a single place: \u003Ccode>createBatchStore\u003C\u002Fcode> in the core package. It&#39;s a generic factory — create it once for prices, once for SKUs, and you get the same guaranteed behavior: 50ms debounce, immutable snapshot, automatic cleanup on unmount. No more parallel implementations with different bugs. One solution, tested once, used everywhere.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Cem>Intelligent caching with SWR\u003C\u002Fem>\u003C\u002Fstrong>---SWR brings request deduplication, in-memory caching, configurable revalidation, and optimistic updates for free (as seen in \u003Ccode>updateOrder\u003C\u002Fcode>, where \u003Ccode>mutate(updated, { revalidate: false })\u003C\u002Fcode> updates the cache immediately without waiting for a new fetch). Before, all of this had to be reimplemented by hand in the reducers.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Cem>Gradual deprecation\u003C\u002Fem>\u003C\u002Fstrong>---The old \u003Ccode>OrderContainer\u003C\u002Fcode> wasn&#39;t deleted. It became a thin wrapper that delegates to the new \u003Ccode>Order\u003C\u002Fcode> component, printing a warning in development. Library consumers can migrate at their own pace, component by component. No immediate breaking changes.\u003C\u002Fli>\n\u003C\u002Ful>\n",[],{"_modelApiKey":377,"copy":434,"listStyle":379,"darkTheme":11,"buttons":435,"color":369,"indent":11},"\u003Ch3>The pattern that emerges\u003C\u002Fh3>\n\n\u003Cp>Each domain of the library now follows the same refactoring path:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>Pure functions are extracted into core — no React, no state, just input\u002Foutput.\u003C\u002Fli>\n\u003Cli>A hook in hooks composes those functions with SWR and React primitives.\u003C\u002Fli>\n\u003Cli>A standalone component in react-components uses the hook internally.\u003C\u002Fli>\n\u003Cli>The old container is deprecated and turned into a proxy to the new component.\u003C\u002Fli>\n\u003C\u002Fol>\n\n\u003Cp>It&#39;s a repeatable, predictable, and — most importantly — reversible pattern. If a choice turns out to be wrong, the abstraction layer allows changing it without rewriting the world.\u003C\u002Fp>\n",[],{"_modelApiKey":377,"copy":437,"listStyle":379,"darkTheme":11,"buttons":438,"color":369,"indent":11},"\u003Ch3>What comes next\u003C\u002Fh3>\n\n\u003Cp>This is just the beginning. The separation into core and hooks laid the foundation, but refactoring is a journey, not a destination. In the next episodes, we&#39;ll see how this pattern was applied domain by domain — prices, SKUs, availability, orders — and what specific challenges emerged along the way.\u003C\u002Fp>\n\n\u003Cp>But the lesson from this first episode is already clear:\u003C\u002Fp>\n",[],{"_modelApiKey":440,"copy":441,"color":369},"highlight_block","The best architecture isn't one that anticipates every possible future, but one that makes change cheap.",{"_modelApiKey":377,"copy":443,"listStyle":379,"darkTheme":11,"buttons":444,"color":369,"indent":11},"\u003Cp>Separating pure logic from the framework is the first step toward making a codebase resilient — not because it will never change, but because it can change without fear.\u003C\u002Fp>\n",[],{"_modelApiKey":366,"icon":446,"copy":447,"color":369},"arrow-fat-right","\u003Cp>Next episode: \u003Cem>The batch store — How a Map, a Set, and a setTimeout eliminated hundreds of API calls.\u003C\u002Fem>\u003C\u002Fp>\n",[449,450,452,454,456,458,460,462,464,466,468,470,472,474,476,478,480],{"attributes":302,"tag":303,"content":304},{"attributes":451,"tag":308,"content":302},{"property":307,"content":304},{"attributes":453,"tag":308,"content":302},{"name":311,"content":304},{"attributes":455,"tag":308,"content":302},{"name":314,"content":315},{"attributes":457,"tag":308,"content":302},{"property":318,"content":315},{"attributes":459,"tag":308,"content":302},{"name":321,"content":315},{"attributes":461,"tag":308,"content":302},{"property":324,"content":325},{"attributes":463,"tag":308,"content":302},{"property":328,"content":329},{"attributes":465,"tag":308,"content":302},{"property":332,"content":333},{"attributes":467,"tag":308,"content":302},{"name":336,"content":325},{"attributes":469,"tag":308,"content":302},{"property":339,"content":340},{"attributes":471,"tag":308,"content":302},{"property":343,"content":344},{"attributes":473,"tag":308,"content":302},{"property":347,"content":348},{"attributes":475,"tag":308,"content":302},{"property":351,"content":352},{"attributes":477,"tag":308,"content":302},{"property":355,"content":280},{"attributes":479,"tag":308,"content":302},{"name":358,"content":359},{"attributes":481,"tag":308,"content":302},{"name":362,"content":363},[483],{"name":285,"picture":484},{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":485,"customData":486},{"x":292,"y":292},{},1791534732177]