<?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: Oleksii Kyrychenko</title>
    <description>The latest articles on DEV Community by Oleksii Kyrychenko (@alexey79).</description>
    <link>https://dev.to/alexey79</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F685031%2Fdfc2b480-6295-444f-8d65-f68704d6b2b8.jpeg</url>
      <title>DEV Community: Oleksii Kyrychenko</title>
      <link>https://dev.to/alexey79</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexey79"/>
    <language>en</language>
    <item>
      <title>Feature-Based Architecture in React: A Structure That Scales Without Turning Into Chaos</title>
      <dc:creator>Oleksii Kyrychenko</dc:creator>
      <pubDate>Sat, 11 Apr 2026 16:40:30 +0000</pubDate>
      <link>https://dev.to/alexey79/feature-based-architecture-in-react-a-structure-that-scales-without-turning-into-chaos-32mo</link>
      <guid>https://dev.to/alexey79/feature-based-architecture-in-react-a-structure-that-scales-without-turning-into-chaos-32mo</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The classic &lt;code&gt;components/hooks/utils&lt;/code&gt; structure often breaks down as the codebase and team grow&lt;/li&gt;
&lt;li&gt;Feature-based architecture organizes code by &lt;strong&gt;domain responsibility&lt;/strong&gt;, not by technical type&lt;/li&gt;
&lt;li&gt;Each feature should expose a &lt;strong&gt;small public API&lt;/strong&gt;, while keeping implementation details internal&lt;/li&gt;
&lt;li&gt;A lightweight &lt;code&gt;features/core/&lt;/code&gt; layer can hold reusable domain-aware slices shared across features&lt;/li&gt;
&lt;li&gt;Full features should stay isolated from sibling features&lt;/li&gt;
&lt;li&gt;The key rule is simple: &lt;strong&gt;avoid deep imports and keep dependency direction explicit&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Problem With the “Standard” Structure
&lt;/h2&gt;

&lt;p&gt;A lot of React projects start the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  components/
  hooks/
  utils/
  pages/
  store/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, it feels natural. But after a few months, especially in a team environment, the structure starts working against you.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;components/&lt;/code&gt; folder becomes a mix of reusable UI primitives, domain-specific widgets, and half-page composites. &lt;code&gt;hooks/&lt;/code&gt; collects everything from generic helpers to business-critical orchestration. &lt;code&gt;utils/&lt;/code&gt; becomes a dumping ground. Refactoring one feature often means touching files spread across five or six unrelated directories.&lt;/p&gt;

&lt;p&gt;The real issue is not the folder names themselves. The issue is that the codebase is organized by &lt;strong&gt;technical role&lt;/strong&gt; instead of &lt;strong&gt;domain ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That usually creates a few recurring problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feature logic is scattered across the tree&lt;/li&gt;
&lt;li&gt;boundaries between reusable and domain-specific code become blurry&lt;/li&gt;
&lt;li&gt;onboarding gets slower because developers need to learn the whole project structure before changing one feature&lt;/li&gt;
&lt;li&gt;hidden dependencies accumulate over time&lt;/li&gt;
&lt;li&gt;deleting or isolating a feature becomes risky&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A structure like this may be acceptable for a small app or a solo prototype. But once the application grows, it often stops being a source of clarity and starts becoming a source of entropy.&lt;/p&gt;




&lt;h2&gt;
  
  
  How a Codebase Typically Evolves
&lt;/h2&gt;

&lt;p&gt;It helps to see this as a progression rather than a one-time decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1 — The classic structure starts to crack
&lt;/h3&gt;

&lt;p&gt;Imagine a &lt;code&gt;users&lt;/code&gt; domain. In a classic structure, its pieces live like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  components/
    UserAvatar.tsx        ← domain UI buried among generic components
    Button.tsx
    Modal.tsx
  hooks/
    useCurrentUser.ts     ← domain hook mixed with generic helpers
    useDebounce.ts
  store/
    userSlice.ts          ← domain state alongside unrelated slices
    cartSlice.ts
  utils/
    formatUserName.ts     ← domain util in a generic dumping ground
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At some point, the &lt;code&gt;orders&lt;/code&gt; feature needs &lt;code&gt;UserAvatar&lt;/code&gt;. Since there is no clear boundary, it imports it directly:&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;UserAvatar&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="s1"&gt;@/components/UserAvatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That import works today. But now &lt;code&gt;orders&lt;/code&gt; is coupled to the internal path of user-related code. If the file moves, or &lt;code&gt;UserAvatar&lt;/code&gt; gains a domain-specific dependency — &lt;code&gt;orders&lt;/code&gt; breaks too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2 — Extract the first feature
&lt;/h3&gt;

&lt;p&gt;The fix is to move everything belonging to &lt;code&gt;users&lt;/code&gt; into one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features/
└── users/
    ├── components/
    │   └── UserAvatar/
    ├── hooks/
    │   └── useCurrentUser/
    ├── store/
    ├── types/
    └── index.ts          ← exports only what consumers should use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;orders&lt;/code&gt; imports through the public API:&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;UserAvatar&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="s1"&gt;@/features/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The internal layout of &lt;code&gt;users&lt;/code&gt; is now free to change without breaking anything outside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3 — Cross-feature reuse forces a new layer
&lt;/h3&gt;

&lt;p&gt;After a few more features are migrated, a new problem appears.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UserAvatar&lt;/code&gt; is now also needed in &lt;code&gt;catalog&lt;/code&gt;. And &lt;code&gt;PermissionGate&lt;/code&gt; — originally written inside &lt;code&gt;orders&lt;/code&gt; — is needed in both &lt;code&gt;catalog&lt;/code&gt; and &lt;code&gt;users&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The naive fix would be direct sibling imports:&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="c1"&gt;// Inside catalog — avoid this&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserAvatar&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="s1"&gt;@/features/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PermissionGate&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="s1"&gt;@/features/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates hidden coupling between features. &lt;code&gt;catalog&lt;/code&gt; now depends on &lt;code&gt;orders&lt;/code&gt; for a UI component. The dependency graph starts getting tangled.&lt;/p&gt;

&lt;p&gt;The right move is to extract these shared domain pieces into &lt;code&gt;features/core/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features/
  core/
    userAvatar/
      components/
      index.ts
    permissions/
      components/
      hooks/
      index.ts
  users/
  catalog/
  orders/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;catalog&lt;/code&gt; and &lt;code&gt;orders&lt;/code&gt; import from &lt;code&gt;features/core/&lt;/code&gt; instead:&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;UserAvatar&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="s1"&gt;@/features/core/userAvatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PermissionGate&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="s1"&gt;@/features/core/permissions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key insight: &lt;code&gt;features/core/&lt;/code&gt; is not a layer you design on day one. It emerges naturally once real cross-feature reuse appears and you want to avoid sibling feature dependencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Idea
&lt;/h2&gt;

&lt;p&gt;The principle behind feature-based architecture is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group code by domain, not by technical role.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means everything related to a feature lives together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI&lt;/li&gt;
&lt;li&gt;hooks&lt;/li&gt;
&lt;li&gt;state&lt;/li&gt;
&lt;li&gt;types&lt;/li&gt;
&lt;li&gt;API calls&lt;/li&gt;
&lt;li&gt;feature-specific helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this a hook or a component?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you start asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which domain does this belong to?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift changes the way the project evolves. A feature stops being spread across the codebase and becomes a coherent slice with its own internal structure and boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Top-Level Structure
&lt;/h2&gt;

&lt;p&gt;A reasonable React application often ends up with five top-level layers. &lt;/p&gt;

&lt;p&gt;The dependency flow always moves downward — layers can only import from layers below them, never above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    App["app/"] --&amp;gt; Pages["pages/"]
    Pages --&amp;gt; Features["features/"]
    Features -.-&amp;gt; Core["features/core/"]
    Features --&amp;gt; Shared["shared/"]
    Core --&amp;gt; Shared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is how it maps to folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── app/
├── pages/
├── features/
│   ├── core/
│   └── ...
└── shared/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;app/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Application bootstrap and composition only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;providers&lt;/li&gt;
&lt;li&gt;router setup&lt;/li&gt;
&lt;li&gt;theme setup&lt;/li&gt;
&lt;li&gt;global styles&lt;/li&gt;
&lt;li&gt;app-level initialization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layer should stay infrastructural. It is not the right place for domain logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;pages/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Route-level composition.&lt;/p&gt;

&lt;p&gt;This layer acts as a bridge between the router and the feature modules. Its responsibility is to orchestrate multiple features and prepare page-level components that are attached to the routing layer.&lt;/p&gt;

&lt;p&gt;Pages should usually stay thin. They may work with route params, page layout, and feature composition, but they should not become a second business layer containing domain logic that belongs in &lt;code&gt;features/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A typical page can stay this simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useParams&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="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PageLayout&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="s1"&gt;@/shared/ui/PageLayout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ProductDetails&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="s1"&gt;@/features/catalog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Recommendations&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="s1"&gt;@/features/core/recommendations&lt;/span&gt;&lt;span class="dl"&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;ProductPage&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="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;=&lt;/span&gt; &lt;span class="nf"&gt;useParams&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PageLayout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductDetails&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Recommendations&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"product"&lt;/span&gt; &lt;span class="na"&gt;referenceId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;PageLayout&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h3&gt;
  
  
  &lt;code&gt;features/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The main domain layer.&lt;/p&gt;

&lt;p&gt;This is where business capabilities live:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auth&lt;/li&gt;
&lt;li&gt;orders&lt;/li&gt;
&lt;li&gt;catalog&lt;/li&gt;
&lt;li&gt;profile&lt;/li&gt;
&lt;li&gt;reports&lt;/li&gt;
&lt;li&gt;notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each feature owns its own logic and internal implementation details.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;features/core/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Not everything belongs in &lt;code&gt;shared/&lt;/code&gt;, and not everything should live inside a single feature.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;features/core/&lt;/code&gt; is a lightweight domain-oriented layer for reusable business slices that are too specific for &lt;code&gt;shared/&lt;/code&gt;, but still broad enough to be used across multiple features.&lt;/p&gt;

&lt;p&gt;If your team dislikes the name &lt;code&gt;core&lt;/code&gt;, that is fine. Names like &lt;code&gt;domain-shared/&lt;/code&gt; or &lt;code&gt;common-domain/&lt;/code&gt; can express the same idea. The important part is the rule behind the layer, not the label.&lt;/p&gt;

&lt;p&gt;Typical examples might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;domain-aware UI fragments&lt;/li&gt;
&lt;li&gt;permission-related building blocks&lt;/li&gt;
&lt;li&gt;reusable business components&lt;/li&gt;
&lt;li&gt;small domain hooks or selectors&lt;/li&gt;
&lt;li&gt;common feature contracts and lightweight models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layer must stay lightweight. It should not contain page orchestration, route wiring, or dependencies on full feature implementations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;shared/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Cross-cutting building blocks with &lt;strong&gt;no domain ownership&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;design-system primitives&lt;/li&gt;
&lt;li&gt;API client&lt;/li&gt;
&lt;li&gt;low-level utilities&lt;/li&gt;
&lt;li&gt;generic hooks&lt;/li&gt;
&lt;li&gt;global types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good rule of thumb: if the code starts knowing too much about a business domain, it probably does not belong in &lt;code&gt;shared/&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;features/core/&lt;/code&gt; Exists
&lt;/h2&gt;

&lt;p&gt;By this point, the pattern should be familiar: once a domain-aware module needs to be reused by multiple features, but is still too business-specific for &lt;code&gt;shared/&lt;/code&gt;, a separate cross-feature layer becomes useful.&lt;/p&gt;

&lt;p&gt;That is the role of &lt;code&gt;features/core/&lt;/code&gt;: a controlled dependency boundary for reusable business slices that should not stay inside one concrete feature, but should also not leak into &lt;code&gt;shared/&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Feature Looks Like
&lt;/h2&gt;

&lt;p&gt;Inside a feature, you can still organize code by role — but only &lt;strong&gt;inside that feature boundary&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features/
└── users/
    ├── components/
    ├── containers/
    ├── hooks/
    ├── store/
    ├── types/
    └── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you two benefits at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the feature remains self-contained&lt;/li&gt;
&lt;li&gt;the code inside the feature is still easy to navigate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A common interpretation might look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;containers/&lt;/code&gt; — orchestration, data loading, state wiring, side effects&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;components/&lt;/code&gt; — presentational UI with explicit props&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hooks/&lt;/code&gt; — feature-level logic and derived behavior&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;store/&lt;/code&gt; — local feature state or query keys&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;types/&lt;/code&gt; — feature-owned contracts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.ts&lt;/code&gt; — public API of the feature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not the only valid internal layout, but it is a practical one because it keeps the feature cohesive without flattening everything into one giant folder.&lt;/p&gt;




&lt;h2&gt;
  
  
  Public API Matters More Than Folder Names
&lt;/h2&gt;

&lt;p&gt;The most important part of this architecture is not whether you use &lt;code&gt;containers/&lt;/code&gt; or &lt;code&gt;components/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A feature should expose a small, intentional public surface.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is what &lt;code&gt;index.ts&lt;/code&gt; is for.&lt;/p&gt;

&lt;p&gt;Instead of importing internals directly:&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;UserAvatar&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="s1"&gt;@/features/users/components/UserAvatar/UserAvatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you import through the feature’s public API:&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;UserAvatar&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="s1"&gt;@/features/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a real boundary.&lt;/p&gt;

&lt;p&gt;It means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;consumers do not depend on internal file layout&lt;/li&gt;
&lt;li&gt;internals can be refactored more safely&lt;/li&gt;
&lt;li&gt;the feature communicates what is public and what is private&lt;/li&gt;
&lt;li&gt;accidental coupling becomes easier to spot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not “barrel files everywhere” as a stylistic preference.&lt;/p&gt;

&lt;p&gt;The goal is to define &lt;strong&gt;module boundaries&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Structure
&lt;/h2&gt;

&lt;p&gt;Here is a realistic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── app/
│   ├── App.tsx
│   ├── Router.tsx
│   └── providers/
│       ├── QueryProvider.tsx
│       ├── ThemeProvider.tsx
│       └── StoreProvider.tsx
│
├── pages/
│   ├── catalog/
│   │   └── CatalogPage.tsx
│   ├── product/
│   │   └── ProductPage.tsx
│   └── cart/
│       └── CartPage.tsx
│
├── features/
│   ├── core/
│   │   ├── permissions/
│   │   │   ├── components/
│   │   │   ├── hooks/
│   │   │   ├── types/
│   │   │   └── index.ts
│   │   ├── statusBadge/
│   │   │   ├── components/
│   │   │   ├── types/
│   │   │   └── index.ts
│   │   ├── contracts/
│   │   │   ├── types/
│   │   │   └── index.ts
│   │   └── index.ts
│   │
│   ├── users/
│   │   ├── components/
│   │   │   └── UserMenu/
│   │   ├── containers/
│   │   │   └── UserProfile/
│   │   ├── hooks/
│   │   │   └── useCurrentUser/
│   │   ├── types/
│   │   ├── store/
│   │   └── index.ts
│   │
│   ├── catalog/
│   │   ├── components/
│   │   ├── containers/
│   │   ├── hooks/
│   │   ├── types/
│   │   └── index.ts
│   │
│   └── orders/
│       ├── shared/
│       ├── checkout/
│       ├── payment/
│       ├── history/
│       └── index.ts
│
└── shared/
    ├── api/
    ├── config/
    ├── lib/
    ├── types/
    └── ui/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key point is not the exact naming. The key point is that &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;users&lt;/code&gt;, and &lt;code&gt;catalog&lt;/code&gt; are treated as domain slices, not as scattered file fragments, while &lt;code&gt;features/core/&lt;/code&gt; holds reusable domain-aware pieces that are safe to share.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dependency Rules Are What Make This Work
&lt;/h2&gt;

&lt;p&gt;A feature-based structure only scales if dependency direction stays disciplined.&lt;/p&gt;

&lt;p&gt;A useful mental model is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;components&lt;/code&gt; should not know about feature orchestration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hooks&lt;/code&gt; should not import UI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;types&lt;/code&gt; should stay lightweight and dependency-poor&lt;/li&gt;
&lt;li&gt;cross-feature imports should go through public APIs or &lt;code&gt;features/core/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shared/&lt;/code&gt; should not absorb domain code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within a feature, a container importing hooks and components is fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useOrderSummary&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="s1"&gt;../hooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OrderSummaryView&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="s1"&gt;../components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Order&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="s1"&gt;../types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a component reaching into store, router, or data-fetching logic is often a sign that the layer boundary is getting blurry.&lt;/p&gt;

&lt;p&gt;That said, “components must never use hooks” is too absolute.&lt;/p&gt;

&lt;p&gt;A presentational component may still reasonably use UI-level hooks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useMemo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useRef&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;measurement hooks&lt;/li&gt;
&lt;li&gt;accessibility hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A better rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Components should avoid domain side effects, data loading, store access, and cross-feature orchestration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That keeps the architecture pragmatic instead of dogmatic.&lt;/p&gt;

&lt;p&gt;These internal dependency rules are not meant to be absolute laws. They are a practical baseline that teams can adapt depending on the complexity of their UI, state, and data flow model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Isolating Features From Each Other
&lt;/h2&gt;

&lt;p&gt;One of the most important architectural rules is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Full feature modules should not know about sibling features.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice, that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;features/orders/&lt;/code&gt; should not import from &lt;code&gt;features/catalog/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/catalog/&lt;/code&gt; should not import from &lt;code&gt;features/users/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/users/&lt;/code&gt; should not import from &lt;code&gt;features/orders/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Direct feature-to-feature dependencies create hidden coupling and make the import graph harder to control.&lt;/p&gt;

&lt;p&gt;This rule applies to sibling top-level features. Subfeatures inside one bounded feature, such as &lt;code&gt;features/orders/checkout&lt;/code&gt; and &lt;code&gt;features/orders/payment&lt;/code&gt;, are a different case because they still belong to the same domain slice.&lt;/p&gt;

&lt;p&gt;If some reusable domain logic or UI must be shared across multiple features, it should move into &lt;code&gt;features/core/&lt;/code&gt;, not into a sibling feature.&lt;/p&gt;

&lt;p&gt;That gives you a cleaner dependency direction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;features/xxx&lt;/code&gt; may import from &lt;code&gt;shared/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/xxx&lt;/code&gt; may import from &lt;code&gt;features/core/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/core/*&lt;/code&gt; may import from &lt;code&gt;shared/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/core/*&lt;/code&gt; must not depend on full features&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/xxx&lt;/code&gt; must not import sibling features directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, full features stay isolated, and cross-feature reuse goes through a lightweight core domain layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Guardrails for Keeping &lt;code&gt;features/core/&lt;/code&gt; Clean
&lt;/h2&gt;

&lt;p&gt;This is the most sensitive part of the model.&lt;/p&gt;

&lt;p&gt;If left unmanaged, &lt;code&gt;features/core/&lt;/code&gt; can easily turn into a second &lt;code&gt;shared/&lt;/code&gt; or a new dumping ground for everything that feels “kind of reusable.”&lt;/p&gt;

&lt;p&gt;That is exactly what should be avoided.&lt;/p&gt;

&lt;p&gt;A few guardrails help keep the layer healthy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only move code into &lt;code&gt;features/core/&lt;/code&gt; when it is genuinely reusable across multiple features&lt;/li&gt;
&lt;li&gt;keep it lightweight and domain-aware, but not orchestration-heavy&lt;/li&gt;
&lt;li&gt;do not place page assembly, route wiring, or full feature flows there&lt;/li&gt;
&lt;li&gt;do not let &lt;code&gt;features/core/&lt;/code&gt; depend on concrete feature implementations&lt;/li&gt;
&lt;li&gt;if a module still carries too much feature-specific behavior, keep it inside the feature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful test is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If a module cannot be reused by at least two features without dragging feature-specific orchestration with it, it probably does not belong in &lt;code&gt;features/core/&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This layer should solve cross-feature reuse, not hide poor boundaries.&lt;/p&gt;

&lt;p&gt;For example, a price formatter that only makes sense inside &lt;code&gt;catalog&lt;/code&gt; should usually stay in &lt;code&gt;features/catalog/&lt;/code&gt;, even if another team is tempted to reuse it once. A single reuse request is not enough reason to turn a feature detail into a cross-feature dependency.&lt;/p&gt;

&lt;p&gt;In practice, this layer survives only when teams protect it with clear ownership and strict code review. Without that discipline, &lt;code&gt;features/core/&lt;/code&gt; can degrade into an unstructured catch-all faster than almost any other part of the codebase.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;features/core/&lt;/code&gt;, it is perfectly fine to reuse the same internal folder anatomy as in regular features, such as &lt;code&gt;components/&lt;/code&gt;, &lt;code&gt;hooks/&lt;/code&gt;, &lt;code&gt;store&lt;/code&gt;, or &lt;code&gt;types&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The important distinction is that &lt;code&gt;core/&lt;/code&gt; should not behave like one large feature-level container. Instead, it should consist of small, focused, reusable domain slices, each with its own local structure and public API.&lt;/p&gt;

&lt;p&gt;For example, this is usually a good direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features/
  core/
    permissions/
      components/
      hooks/
      types/
      index.ts
    statusBadge/
      components/
      types/
      index.ts
    contracts/
      types/
      index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps &lt;code&gt;core/&lt;/code&gt; modular and explicit. The problem is not repeating folder names like &lt;code&gt;components/&lt;/code&gt; or &lt;code&gt;hooks/&lt;/code&gt;. The real risk is turning &lt;code&gt;core/&lt;/code&gt; into a monolithic catch-all such as &lt;code&gt;core/components&lt;/code&gt;, &lt;code&gt;core/hooks&lt;/code&gt;, or &lt;code&gt;core/types&lt;/code&gt; filled with unrelated code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Large Features and Subfeatures
&lt;/h2&gt;

&lt;p&gt;Not every feature needs subfeatures.&lt;/p&gt;

&lt;p&gt;In fact, most features should start flat.&lt;/p&gt;

&lt;p&gt;But once a feature becomes large enough to contain several distinct areas, splitting it into subfeatures helps keep the import graph clearer.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features/
└── orders/
    ├── shared/
    ├── checkout/
    ├── payment/
    ├── history/
    └── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when those areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;have different flows&lt;/li&gt;
&lt;li&gt;own different UI and hooks&lt;/li&gt;
&lt;li&gt;evolve independently&lt;/li&gt;
&lt;li&gt;share some internal primitives, but not everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A structure like this can reduce the chance of circular dependencies and boundary leakage, especially if the root feature API stays small and the subfeatures do not import each other’s internals.&lt;/p&gt;

&lt;p&gt;I would avoid claiming that this makes cycles “physically impossible.” It does not. But it &lt;strong&gt;does&lt;/strong&gt; make the dependency graph easier to reason about and significantly reduces the likelihood of accidental coupling when paired with import restrictions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Import Rules That Age Well
&lt;/h2&gt;

&lt;p&gt;A practical rule set looks like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Good
&lt;/h3&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;UserAvatar&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="s1"&gt;@/features/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CheckoutStepper&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="s1"&gt;@/features/orders/checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PermissionGate&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="s1"&gt;@/features/core/permissions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&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="s1"&gt;@/shared/ui/Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Avoid
&lt;/h3&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;UserAvatar&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="s1"&gt;@/features/users/components/UserAvatar/UserAvatar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PaymentBadge&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="s1"&gt;@/features/orders/payment/components/PaymentBadge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ProductCard&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="s1"&gt;@/features/catalog/components/ProductCard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal layout can change without breaking consumers&lt;/li&gt;
&lt;li&gt;refactors stay local&lt;/li&gt;
&lt;li&gt;ownership is clearer&lt;/li&gt;
&lt;li&gt;dependency review becomes easier in PRs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the rare rules that is both simple and genuinely high-value:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not deep-import into another feature, and do not depend on sibling features directly.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Architecture Improves in Practice
&lt;/h2&gt;

&lt;p&gt;When implemented with discipline, feature-based architecture tends to improve several day-to-day problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Locality
&lt;/h3&gt;

&lt;p&gt;Everything related to one domain capability lives in one place.&lt;/p&gt;

&lt;p&gt;A developer working on checkout should not need to jump across six top-level folders to understand one flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Encapsulation
&lt;/h3&gt;

&lt;p&gt;Feature internals stay private unless intentionally exported.&lt;/p&gt;

&lt;p&gt;That makes refactors less fragile.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Onboarding
&lt;/h3&gt;

&lt;p&gt;New developers can reason about one domain slice at a time instead of learning the entire technical tree upfront.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. PR Scope
&lt;/h3&gt;

&lt;p&gt;Changes are more likely to stay inside one bounded area.&lt;/p&gt;

&lt;p&gt;That makes reviews easier and safer.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Parallel Work
&lt;/h3&gt;

&lt;p&gt;Teams working across different features collide less often when boundaries are real.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Safer Cross-Feature Reuse
&lt;/h3&gt;

&lt;p&gt;Reusable business pieces move into &lt;code&gt;features/core/&lt;/code&gt; instead of leaking through sibling feature dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Deletion and Extraction
&lt;/h3&gt;

&lt;p&gt;A well-isolated feature is easier to remove, replace, or extract into a separate package or app section.&lt;/p&gt;

&lt;p&gt;Not trivial. Not always one command. But definitely easier than in a scattered structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Trade-Offs
&lt;/h2&gt;

&lt;p&gt;This is the part many architecture articles skip.&lt;/p&gt;

&lt;p&gt;Feature-based architecture is useful, but it is not free.&lt;/p&gt;

&lt;h3&gt;
  
  
  More structure upfront
&lt;/h3&gt;

&lt;p&gt;You need to define boundaries deliberately instead of letting the project grow organically.&lt;/p&gt;

&lt;p&gt;That requires judgment.&lt;/p&gt;

&lt;h3&gt;
  
  
  More layers to keep clean
&lt;/h3&gt;

&lt;p&gt;Once &lt;code&gt;features/core/&lt;/code&gt; exists, somebody needs to protect it from becoming a second &lt;code&gt;shared/&lt;/code&gt; dumping ground.&lt;/p&gt;

&lt;h3&gt;
  
  
  More files
&lt;/h3&gt;

&lt;p&gt;If every small piece gets its own folder, &lt;code&gt;index.ts&lt;/code&gt;, &lt;code&gt;types.ts&lt;/code&gt;, &lt;code&gt;utils.ts&lt;/code&gt;, and so on, the structure can become too granular.&lt;/p&gt;

&lt;p&gt;What was meant to improve clarity can become ceremony.&lt;/p&gt;

&lt;h3&gt;
  
  
  More conventions to enforce
&lt;/h3&gt;

&lt;p&gt;Without lint rules and review discipline, people eventually start bypassing the boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deep imports&lt;/li&gt;
&lt;li&gt;sibling feature dependencies&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shared/&lt;/code&gt; becoming a junk drawer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features/core/&lt;/code&gt; becoming an unbounded catch-all&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Public APIs require maintenance
&lt;/h3&gt;

&lt;p&gt;Once a feature has an explicit public surface, somebody has to own it.&lt;/p&gt;

&lt;p&gt;That is a good thing, but it still has a cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not every project needs it
&lt;/h3&gt;

&lt;p&gt;For a small product, internal tool, or early MVP, this architecture may be more formal than the project actually needs.&lt;/p&gt;

&lt;p&gt;So the right question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is feature-based architecture good?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The right question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the complexity of this codebase high enough to justify stronger boundaries?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  When This Approach Fits Best
&lt;/h2&gt;

&lt;p&gt;This architecture tends to work especially well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the app has multiple business domains&lt;/li&gt;
&lt;li&gt;several developers work in parallel&lt;/li&gt;
&lt;li&gt;the project will live for a long time&lt;/li&gt;
&lt;li&gt;the team cares about ownership and maintainability&lt;/li&gt;
&lt;li&gt;feature boundaries are more important than technical grouping&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is particularly effective in products where business workflows keep growing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;admin platforms&lt;/li&gt;
&lt;li&gt;dashboards&lt;/li&gt;
&lt;li&gt;e-commerce apps&lt;/li&gt;
&lt;li&gt;SaaS products&lt;/li&gt;
&lt;li&gt;enterprise frontends&lt;/li&gt;
&lt;li&gt;large internal tools&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When I Would Not Reach for It First
&lt;/h2&gt;

&lt;p&gt;I would be more cautious in cases like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a small solo project&lt;/li&gt;
&lt;li&gt;an MVP still searching for product shape&lt;/li&gt;
&lt;li&gt;a tiny marketing app&lt;/li&gt;
&lt;li&gt;a codebase with very little domain depth&lt;/li&gt;
&lt;li&gt;a design-system-heavy app where feature boundaries are minimal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those cases, a lighter structure may be enough.&lt;/p&gt;

&lt;p&gt;Architecture should reduce friction, not introduce it prematurely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migrating an Existing Project
&lt;/h2&gt;

&lt;p&gt;You do not need a big-bang rewrite.&lt;/p&gt;

&lt;p&gt;A gradual migration is usually the better choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Define aliases
&lt;/h3&gt;

&lt;p&gt;Set up imports like:&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;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"@/features/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/features/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@/shared/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/shared/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@/pages/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/pages/*"&lt;/span&gt;&lt;span class="p"&gt;]&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;span class="p"&gt;}&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;This makes future boundaries clearer from the start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Create &lt;code&gt;shared/&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Move only truly generic pieces first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI primitives&lt;/li&gt;
&lt;li&gt;API client&lt;/li&gt;
&lt;li&gt;cross-app utilities&lt;/li&gt;
&lt;li&gt;generic hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is usually the safest extraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Migrate one small feature
&lt;/h3&gt;

&lt;p&gt;Choose an isolated domain and move it completely.&lt;/p&gt;

&lt;p&gt;Do not redesign the whole project at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — Add public APIs
&lt;/h3&gt;

&lt;p&gt;Introduce &lt;code&gt;index.ts&lt;/code&gt; at the feature boundary and stop deep imports from outside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 — Introduce &lt;code&gt;features/core/&lt;/code&gt; when cross-feature reuse appears
&lt;/h3&gt;

&lt;p&gt;At this point, if two or more features need the same domain piece, move it to &lt;code&gt;features/core/&lt;/code&gt; rather than creating a sibling feature dependency.&lt;/p&gt;

&lt;p&gt;Extract only those business-oriented pieces that are lightweight, reusable, and needed across multiple features.&lt;/p&gt;

&lt;p&gt;Do not move orchestration there. Do not move full feature flows there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 — Enforce rules
&lt;/h3&gt;

&lt;p&gt;Use ESLint to restrict cross-feature deep imports and sibling feature dependencies.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://github.com/javieraviles/eslint-plugin-boundaries" rel="noopener noreferrer"&gt;&lt;code&gt;eslint-plugin-boundaries&lt;/code&gt;&lt;/a&gt; or linters from the Feature-Sliced Design ecosystem are excellent choices for this. You can configure them to strictly forbid a feature from importing another sibling feature, or to block deep paths.&lt;/p&gt;

&lt;p&gt;Without enforcement, the architecture becomes a suggestion instead of a boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rules Worth Keeping
&lt;/h2&gt;

&lt;p&gt;If I had to reduce the whole approach to a few durable rules, I would keep these:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Organize by domain ownership
&lt;/h3&gt;

&lt;p&gt;A feature should own its UI, hooks, state, types, and logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Keep public APIs small
&lt;/h3&gt;

&lt;p&gt;Do not expose everything by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Avoid deep imports across feature boundaries
&lt;/h3&gt;

&lt;p&gt;Import from the feature, not from its internals.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Keep full features isolated
&lt;/h3&gt;

&lt;p&gt;Feature modules should not know about sibling features.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Use &lt;code&gt;features/core/&lt;/code&gt; for lightweight reusable domain slices
&lt;/h3&gt;

&lt;p&gt;If cross-feature reuse is needed, route it through a controlled core domain layer rather than direct feature-to-feature imports.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Keep &lt;code&gt;shared/&lt;/code&gt; free from business logic
&lt;/h3&gt;

&lt;p&gt;Infrastructure belongs in &lt;code&gt;shared/&lt;/code&gt;. Domain behavior belongs in &lt;code&gt;features/&lt;/code&gt; or &lt;code&gt;features/core/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Start simple, split later
&lt;/h3&gt;

&lt;p&gt;Do not introduce subfeatures too early. Add them when the feature actually becomes too large or too mixed.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Important Caveat
&lt;/h2&gt;

&lt;p&gt;Folder structure alone does not solve architecture.&lt;/p&gt;

&lt;p&gt;A codebase can still become chaotic even with beautiful feature folders if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ownership is unclear&lt;/li&gt;
&lt;li&gt;review discipline is weak&lt;/li&gt;
&lt;li&gt;shared boundaries are ignored&lt;/li&gt;
&lt;li&gt;abstractions are introduced too early&lt;/li&gt;
&lt;li&gt;business logic leaks through “helper” files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The structure helps, but the real value comes from the &lt;strong&gt;dependency discipline&lt;/strong&gt; behind it.&lt;/p&gt;

&lt;p&gt;That is what makes the difference between a folder convention and an actual architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Relation to Feature-Sliced Design (FSD)
&lt;/h2&gt;

&lt;p&gt;If this structure feels familiar, it is because it shares its philosophy with &lt;strong&gt;Feature-Sliced Design (FSD)&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;What it keeps from FSD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit boundaries between slices&lt;/li&gt;
&lt;li&gt;public APIs instead of deep imports&lt;/li&gt;
&lt;li&gt;strong dependency direction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it simplifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer top-level layers to explain&lt;/li&gt;
&lt;li&gt;no separate &lt;code&gt;entities&lt;/code&gt; and &lt;code&gt;widgets&lt;/code&gt; layers&lt;/li&gt;
&lt;li&gt;a more pragmatic path for teams that want the benefits without the full model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In that sense, this is not an alternative to FSD as much as a lighter interpretation of the same core instinct: make boundaries visible, and make dependency direction intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Feature-based architecture is not a magic formula, and it is not the only valid way to structure a React codebase.&lt;/p&gt;

&lt;p&gt;But for growing applications, it solves a real problem: it aligns the codebase with the shape of the business domain and makes dependency direction more explicit.&lt;/p&gt;

&lt;p&gt;That gives you something more valuable than neat folders.&lt;/p&gt;

&lt;p&gt;It gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clearer ownership&lt;/li&gt;
&lt;li&gt;safer refactoring&lt;/li&gt;
&lt;li&gt;smaller dependency surfaces&lt;/li&gt;
&lt;li&gt;better onboarding&lt;/li&gt;
&lt;li&gt;less structural drift over time&lt;/li&gt;
&lt;li&gt;more controlled cross-feature reuse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to create the “perfect” folder tree.&lt;/p&gt;

&lt;p&gt;The goal is to make the codebase easier to understand, change, and scale.&lt;/p&gt;

&lt;p&gt;If your current structure is already making feature work feel scattered, this approach is worth serious consideration.&lt;/p&gt;

&lt;p&gt;Start with one feature. Keep the boundaries real. Let the structure prove itself in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Acknowledgment
&lt;/h2&gt;

&lt;p&gt;I’d like to thank my team and my team lead for the inspiration behind this article. Many of the ideas in it were shaped through engineering discussions, collaboration, and practical experience.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Discussion&lt;/strong&gt;&lt;br&gt;
How do you handle feature boundaries in React projects? Do you prefer a flatter feature model, or do you introduce a lightweight core domain layer for safe cross-feature reuse?&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>architecture</category>
      <category>typescript</category>
      <category>featurebased</category>
    </item>
    <item>
      <title>Stop Fighting Zustand Context: Practical Store Scoping Patterns for React</title>
      <dc:creator>Oleksii Kyrychenko</dc:creator>
      <pubDate>Sun, 29 Mar 2026 17:04:16 +0000</pubDate>
      <link>https://dev.to/alexey79/stop-fighting-zustand-context-practical-store-scoping-patterns-for-react-3c71</link>
      <guid>https://dev.to/alexey79/stop-fighting-zustand-context-practical-store-scoping-patterns-for-react-3c71</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/pmndrs/zustand" rel="noopener noreferrer"&gt;Zustand&lt;/a&gt; is one of the rare state management libraries that feels good almost immediately. It is small, fast, and does not try to force a framework-sized architecture onto your app.&lt;/p&gt;

&lt;p&gt;That simplicity is exactly why many teams adopt it quickly.&lt;/p&gt;

&lt;p&gt;Then the app grows, and a different problem shows up: &lt;strong&gt;scoped state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What happens when your app needs multiple, isolated instances of the same store? Imagine a dashboard where each complex "widget" needs its own independent state or a multi-step "wizard" where simultaneous tabs shouldn't overwrite each other's data.&lt;/p&gt;

&lt;p&gt;The official Zustand documentation recommends using React Context for this, but doing it manually is a grind. You have to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a React Context.&lt;/li&gt;
&lt;li&gt;Create a factory function for the store instance.&lt;/li&gt;
&lt;li&gt;Build a wrapper Provider component.&lt;/li&gt;
&lt;li&gt;Manually rebuild strongly-typed selector hooks (&lt;code&gt;useStore&lt;/code&gt;, &lt;code&gt;useStoreApi&lt;/code&gt;) for consumers.&lt;/li&gt;
&lt;li&gt;Pepper your codebase with &lt;code&gt;useShallow&lt;/code&gt; to prevent unnecessary re-renders when returning objects or arrays.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At that point, plain Zustand is still capable, but the implementation starts getting repetitive.&lt;/p&gt;

&lt;p&gt;To reduce that boilerplate, I built &lt;strong&gt;&lt;code&gt;@okyrychenko-dev/react-zustand-toolkit&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It gives you a few composable helpers around Zustand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generated context providers&lt;/li&gt;
&lt;li&gt;shallow-first selectors by default&lt;/li&gt;
&lt;li&gt;"resolved" hooks that can read from either a scoped or global store&lt;/li&gt;
&lt;li&gt;a small set of React 19 utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal of this article is not to oversell that toolkit. It is to show the real architectural cases where it helps, where it does not, and how its three main factory functions map to actual React state ownership patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before We Start: The Real Problem
&lt;/h2&gt;

&lt;p&gt;Zustand itself is not the problem. In many apps, plain Zustand is already enough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one global store&lt;/li&gt;
&lt;li&gt;a few focused selectors&lt;/li&gt;
&lt;li&gt;occasional middleware&lt;/li&gt;
&lt;li&gt;no need for isolated store instances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pain starts when your architecture stops being purely global.&lt;/p&gt;

&lt;p&gt;That usually happens in one of these situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you render the same complex widget multiple times and each instance needs separate state&lt;/li&gt;
&lt;li&gt;you build reusable modules that should work standalone and also inside a larger application&lt;/li&gt;
&lt;li&gt;you want most of the app to read from one global store, but a subtree should temporarily override it&lt;/li&gt;
&lt;li&gt;you are tired of repeating provider + context + hook wiring for every isolated Zustand use case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the exact gap this toolkit is trying to cover.&lt;/p&gt;

&lt;p&gt;So while this article shows the library API, the more important takeaway is architectural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use a plain global store when isolation is not needed&lt;/li&gt;
&lt;li&gt;use scoped providers when identity and lifetime matter per subtree&lt;/li&gt;
&lt;li&gt;use resolved hooks when consumers should not care where the state comes from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that framing in place, the API makes much more sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Global Singleton: &lt;code&gt;createShallowStore&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s start with the simplest layer.&lt;/p&gt;

&lt;p&gt;If you are just building a standard global store, the main reason to use this layer is &lt;strong&gt;shallow-first selectors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In standard Zustand, if your selector returns a new object or array, your component will re-render every single time the store updates, even if the selected values haven't changed. To fix this, you have to manually wrap your selectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Standard Zustand requires boilerplate for shallow picks&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useShallow&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="s1"&gt;zustand/react/shallow&lt;/span&gt;&lt;span class="dl"&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUserStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;useShallow&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;With &lt;code&gt;createShallowStore&lt;/code&gt;, your generated hooks use &lt;code&gt;zustand/shallow&lt;/code&gt; by default. You can pick objects and arrays freely without the boilerplate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;createShallowStore&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;@okyrychenko-dev/react-zustand-toolkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SessionStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;token&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;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&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;role&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="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;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&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;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;name&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;role&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="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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useStorePlain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useStoreApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createShallowStore&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SessionStore&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;token&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;user&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;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Object picks use shallow comparison by default.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProfileInfo&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&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;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you ever need standard, strict-equality behavior, the toolkit always provides explicit &lt;code&gt;useStorePlain&lt;/code&gt; alternatives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters in practice
&lt;/h3&gt;

&lt;p&gt;The shallow-first approach is especially useful when components naturally want to read small object bundles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isLoading&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="nx"&gt;reload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&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;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&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="nx"&gt;state&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="na"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reload&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;In plain Zustand, patterns like this often push teams into one of two habits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wrapping selectors in &lt;code&gt;useShallow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;splitting every field into its own selector call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both work. They are just noisy when repeated across a large codebase.&lt;/p&gt;

&lt;p&gt;This helper does not replace selector discipline. It simply makes the common "pick a few fields" case less repetitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does not do
&lt;/h3&gt;

&lt;p&gt;It is still important to be precise about the limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it does not make every selector free&lt;/li&gt;
&lt;li&gt;it does not replace good store design&lt;/li&gt;
&lt;li&gt;it does not solve deep comparison problems&lt;/li&gt;
&lt;li&gt;it does not remove the need to think about derived data and subscription granularity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It mainly improves the ergonomics of shallow object and array picks.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Isolated Store Contexts: &lt;code&gt;createStoreProvider&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The next layer is where Zustand usually becomes a little more manual.&lt;/p&gt;

&lt;p&gt;When you need true isolation, where every instance of a component must own a separate store, &lt;code&gt;createStoreProvider&lt;/code&gt; removes most of the repetitive setup.&lt;/p&gt;

&lt;p&gt;It generates the Context, the Provider component, and the typed consumer hooks in a single call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;createStoreProvider&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;@okyrychenko-dev/react-zustand-toolkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WizardStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;step&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;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forward&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="s1"&gt;backward&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&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="c1"&gt;// 1. Generate the provider and hooks&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="na"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WizardProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;useContextStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useContextStoreApi&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createStoreProvider&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WizardStore&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;step&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;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forward&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&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;state&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;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&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;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;forward&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wizard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Consume safely within the isolated tree&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;WizardControls&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;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContextStore&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;step&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;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContextStore&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Current Step: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Next Step&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h3&gt;
  
  
  Why provider-scoped Zustand is useful
&lt;/h3&gt;

&lt;p&gt;Context-scoped stores are not just an implementation detail. They model a different ownership pattern.&lt;/p&gt;

&lt;p&gt;With a global singleton store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the store exists once&lt;/li&gt;
&lt;li&gt;every consumer shares the same data&lt;/li&gt;
&lt;li&gt;state lifetime usually matches the application lifetime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a provider-scoped store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each provider instance owns one store&lt;/li&gt;
&lt;li&gt;sibling subtrees can hold completely different values&lt;/li&gt;
&lt;li&gt;state lifetime follows the mounted subtree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes provider-scoped stores a good fit for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wizards&lt;/li&gt;
&lt;li&gt;modals with complex internal state&lt;/li&gt;
&lt;li&gt;embeddable widgets&lt;/li&gt;
&lt;li&gt;repeated dashboard panels&lt;/li&gt;
&lt;li&gt;request or test isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Provider Lifecycle Hooks
&lt;/h3&gt;

&lt;p&gt;Sometimes you need to initialize your isolated store with data from outside (like props) &lt;em&gt;before&lt;/em&gt; the component renders, or run a side effect right after it mounts.&lt;/p&gt;

&lt;p&gt;The generated &lt;code&gt;Provider&lt;/code&gt; component supports two lifecycle stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onStoreInit&lt;/code&gt;: Synchronous initialization during store creation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onStoreReady&lt;/code&gt;: Post-commit side effects.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;WizardShell&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;initialStep&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;initialStep&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WizardProvider&lt;/span&gt;
      &lt;span class="na"&gt;onStoreInit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&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="c1"&gt;// Initialize the store synchronously before first render&lt;/span&gt;
        &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initialStep&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onStoreReady&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&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="c1"&gt;// Run side effects like analytics tracking after mount&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wizard instance mounted at step&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WizardControls&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;WizardProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;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;That split is small, but useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onStoreInit&lt;/code&gt; is for deterministic setup before consumers read the store&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onStoreReady&lt;/code&gt; is for effects that should happen after mount&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a better mental model than mixing initialization and side effects in the same callback.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Best of Both Worlds: &lt;code&gt;createStoreToolkit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the layer that makes the package feel more like a toolkit and less like a single helper.&lt;/p&gt;

&lt;p&gt;What if you have &lt;strong&gt;global state&lt;/strong&gt;, but &lt;em&gt;certain parts&lt;/em&gt; of the UI need to &lt;strong&gt;override it locally&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;This is where &lt;code&gt;createStoreToolkit&lt;/code&gt; becomes useful.&lt;/p&gt;

&lt;p&gt;It creates both a global singleton store and an optional context provider. It also gives you &lt;strong&gt;resolved hooks&lt;/strong&gt; such as &lt;code&gt;useResolvedValue&lt;/code&gt; and &lt;code&gt;useResolvedStoreApi&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These hooks dynamically check the React Component tree:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Are we inside a Provider for this store? If yes, use the &lt;strong&gt;scoped context store&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;No Provider found? Fall back to the &lt;strong&gt;global singleton store&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Take a look at this Theme example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;createStoreToolkit&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;@okyrychenko-dev/react-zustand-toolkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ThemeStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&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="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;setMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&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="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Generates both global store AND provider&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;themeToolkit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createStoreToolkit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ThemeStore&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;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Global default&lt;/span&gt;
  &lt;span class="na"&gt;setMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&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;mode&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Theme&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;useResolvedValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;useTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;themeToolkit&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ThemeProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;themeToolkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now consuming components do not need to care whether they are reading from the global store or a scoped provider instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ThemedCard&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;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTheme&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`card-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Smart Card&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* 🌍 1. Uses the global 'light' theme */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemedCard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; 

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* 🏠 2. Overrides the state to 'dark' for this specific tree ONLY */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt; &lt;span class="na"&gt;onStoreInit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&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;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;setMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dark-zone"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemedCard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;This hybrid pattern is useful for reusable UI modules, nested widgets, or apps where most of the UI can share one store, but a subtree sometimes needs an isolated instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why resolved hooks are interesting
&lt;/h3&gt;

&lt;p&gt;This is probably the most opinionated part of the library.&lt;/p&gt;

&lt;p&gt;Normally, when a component can run in two modes, you end up with one of these designs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate hooks for global and scoped usage&lt;/li&gt;
&lt;li&gt;props that inject the store&lt;/li&gt;
&lt;li&gt;branching logic scattered across the component tree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resolved hooks collapse that decision into one place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inside the matching provider, read the scoped store&lt;/li&gt;
&lt;li&gt;outside it, read the global store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That can simplify component APIs a lot, especially in shared UI packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  A good mental model
&lt;/h3&gt;

&lt;p&gt;Think of &lt;code&gt;createStoreToolkit&lt;/code&gt; as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a normal global Zustand store&lt;/li&gt;
&lt;li&gt;plus an optional scoped override mechanism&lt;/li&gt;
&lt;li&gt;plus consumer hooks that pick the nearest valid source&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That framing is more accurate than thinking of it as "magic context Zustand".&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to be careful
&lt;/h3&gt;

&lt;p&gt;Resolved hooks are convenient, but they are also a design choice. I would avoid them when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the distinction between global and local state should be explicit in the component API&lt;/li&gt;
&lt;li&gt;debugging would become ambiguous because a component may silently switch data sources&lt;/li&gt;
&lt;li&gt;different teams own global and scoped behavior separately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, resolved hooks are best when the fallback behavior is intentional, not surprising.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Middleware Without Losing Types
&lt;/h2&gt;

&lt;p&gt;So far the value has been architectural. This section is more about preserving the normal Zustand experience.&lt;/p&gt;

&lt;p&gt;Zustand middleware such as Redux DevTools, Persist, or SubscribeWithSelector still belongs in the store creator.&lt;/p&gt;

&lt;p&gt;The useful part here is that the toolkit preserves the resulting store API types, so helpers like &lt;code&gt;persist.rehydrate&lt;/code&gt; or selector-aware &lt;code&gt;subscribe&lt;/code&gt; remain available on &lt;code&gt;useStoreApi&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;createShallowStore&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;@okyrychenko-dev/react-zustand-toolkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;devtools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;persist&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/middleware&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CartStore&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;items&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;addItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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="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="c1"&gt;// Middleware types are preserved on the store API.&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;useStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useStoreApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createShallowStore&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;CartStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zustand/persist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CartStore&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zustand/devtools&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;never&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="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;devtools&lt;/span&gt;&lt;span class="p"&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;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
        &lt;span class="na"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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;state&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;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GlobalCartStore&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cart-storage&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="c1"&gt;// Mutator APIs stay typed.&lt;/span&gt;
&lt;span class="nx"&gt;useStoreApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rehydrate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;useStoreApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devtools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanUp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; mean the toolkit adds a custom DevTools layer for provider stores. If you want Redux DevTools, apply Zustand middleware in the creator itself. Dynamic provider instances are not auto-connected for you.&lt;/p&gt;

&lt;p&gt;This is a subtle point, but an important one.&lt;/p&gt;

&lt;p&gt;The library is not trying to compete with Zustand middleware. It is trying to stay out of the way while preserving the resulting types.&lt;/p&gt;

&lt;p&gt;That is the right design choice. Middleware remains a Zustand concern, not a toolkit-specific abstraction.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Ready for React 19 ⚛️
&lt;/h2&gt;

&lt;p&gt;This part is useful, but it should be read with the right expectations.&lt;/p&gt;

&lt;p&gt;React 19 introduces hooks and rendering primitives such as Transitions, Action State, and Optimistic Updates.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@okyrychenko-dev/react-zustand-toolkit&lt;/code&gt; includes a few small utilities around those APIs. They are wrappers, not a new state model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping Actions in Transitions
&lt;/h3&gt;

&lt;p&gt;If you have an update that may trigger expensive rendering, you can wrap the action in a transition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;createTransitionAction&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;@okyrychenko-dev/react-zustand-toolkit&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;incrementInTransition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTransitionAction&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="c1"&gt;// This update runs inside React.startTransition&lt;/span&gt;
  &lt;span class="nx"&gt;counterToolkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useStoreApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;increment&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;h3&gt;
  
  
  Action State Adapters
&lt;/h3&gt;

&lt;p&gt;If you want a thin adapter over &lt;code&gt;useActionState&lt;/code&gt; for store-related async actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useActionStateAdapter&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;@okyrychenko-dev/react-zustand-toolkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SaveForm&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="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;submitForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useActionStateAdapter&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="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;myApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;myStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;markSaved&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;saved&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitForm&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Saving...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;saved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Saved successfully!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h3&gt;
  
  
  Optimistic UI Updates
&lt;/h3&gt;

&lt;p&gt;If you want an optimistic layer on top of committed Zustand state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useOptimisticReducer&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;@okyrychenko-dev/react-zustand-toolkit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TodoList&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;serverTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTodos&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&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;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&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;optimisticTodos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addOptimisticTodo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useOptimisticReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;serverTodos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextTodo&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;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextTodo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// ... render optimisticTodos instead of serverTodos&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I would treat these helpers as convenience utilities, not the center of the package.&lt;/p&gt;

&lt;p&gt;They are nice because they keep React 19-oriented code close to the same toolkit, but the core value of the library is still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;store scoping&lt;/li&gt;
&lt;li&gt;shallow-first selectors&lt;/li&gt;
&lt;li&gt;resolved hooks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where the architectural leverage really is.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Which Factory Should You Reach For?
&lt;/h2&gt;

&lt;p&gt;If you only remember one section from this article, make it this one.&lt;/p&gt;

&lt;p&gt;If you are evaluating the library quickly, this is the practical decision tree:&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;createShallowStore&lt;/code&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;you want one global singleton store&lt;/li&gt;
&lt;li&gt;your main annoyance is repeated &lt;code&gt;useShallow&lt;/code&gt; usage&lt;/li&gt;
&lt;li&gt;you do not need isolated instances&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;createStoreProvider&lt;/code&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;every mounted subtree should own its own store&lt;/li&gt;
&lt;li&gt;the state lifetime should end when that subtree unmounts&lt;/li&gt;
&lt;li&gt;store isolation should be explicit&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;createStoreToolkit&lt;/code&gt; when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;you want a global store by default&lt;/li&gt;
&lt;li&gt;some subtrees should be able to override it with local instances&lt;/li&gt;
&lt;li&gt;your consumers should work in both environments with the same hook API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation is one of the better aspects of the package. The API is not trying to force one pattern onto every use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factory&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Store lifetime&lt;/th&gt;
&lt;th&gt;Main benefit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;createShallowStore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One global store&lt;/td&gt;
&lt;td&gt;App-wide&lt;/td&gt;
&lt;td&gt;Shallow-first selectors with low boilerplate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;createStoreProvider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Isolated subtree state&lt;/td&gt;
&lt;td&gt;Per provider instance&lt;/td&gt;
&lt;td&gt;Explicit store ownership and lifecycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;createStoreToolkit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mixed global + local override scenarios&lt;/td&gt;
&lt;td&gt;Global plus optional scoped instances&lt;/td&gt;
&lt;td&gt;Shared consumer API through resolved hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  7. When You Probably Do Not Need This Library
&lt;/h2&gt;

&lt;p&gt;This section matters because a good abstraction should come with a clear boundary.&lt;/p&gt;

&lt;p&gt;It is also worth being explicit about the non-use-cases.&lt;/p&gt;

&lt;p&gt;You probably do &lt;strong&gt;not&lt;/strong&gt; need this toolkit if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your app already works well with a single global Zustand store&lt;/li&gt;
&lt;li&gt;you rarely select object or array bundles&lt;/li&gt;
&lt;li&gt;you do not use scoped providers at all&lt;/li&gt;
&lt;li&gt;you prefer explicit store injection over fallback resolution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no benefit in adding an abstraction layer just because it exists.&lt;/p&gt;

&lt;p&gt;Good Zustand architecture is still mostly about picking the right ownership model for state. This toolkit simply makes a few of those models easier to implement consistently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The strongest part of &lt;strong&gt;&lt;code&gt;react-zustand-toolkit&lt;/code&gt;&lt;/strong&gt; is not that it reinvents Zustand. It does not.&lt;/p&gt;

&lt;p&gt;Its value is that it packages a few repeatable patterns into a small API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generated providers and hooks for isolated store instances&lt;/li&gt;
&lt;li&gt;shallow-first selector hooks with explicit plain alternatives&lt;/li&gt;
&lt;li&gt;resolved hooks for code that should work both inside and outside a provider&lt;/li&gt;
&lt;li&gt;typed passthrough for Zustand middleware&lt;/li&gt;
&lt;li&gt;a few optional React 19 wrappers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those are problems you keep solving by hand, the library is worth a look.&lt;/p&gt;

&lt;p&gt;If your app only needs a single global store, plain Zustand may still be enough, and that is completely fine.&lt;/p&gt;

&lt;p&gt;But if your real problem is no longer "how do I store state?" and has become "who owns this state, how many instances of it exist, and how should components resolve it?", then this toolkit starts to become much more interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;Install it today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @okyrychenko-dev/react-zustand-toolkit zustand
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the full API reference, examples, and source code in the &lt;a href="https://github.com/okyrychenko-dev/react-zustand-toolkit" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;If you have run into the "global store everywhere, until one subtree needs isolation" problem, this is the part of Zustand architecture the toolkit is trying to simplify.&lt;/p&gt;

</description>
      <category>react</category>
      <category>zustand</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A Cleaner Way to Write Conditional useEffect in React</title>
      <dc:creator>Oleksii Kyrychenko</dc:creator>
      <pubDate>Fri, 27 Mar 2026 19:44:45 +0000</pubDate>
      <link>https://dev.to/alexey79/a-cleaner-way-to-write-conditional-useeffect-in-react-5gi9</link>
      <guid>https://dev.to/alexey79/a-cleaner-way-to-write-conditional-useeffect-in-react-5gi9</guid>
      <description>&lt;p&gt;If you've built anything in React 18+, you've likely felt the pain of &lt;strong&gt;Strict Mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You add a simple event listener, fire an analytics event, or connect a WebSocket. You boot up your dev server, and—&lt;em&gt;boom&lt;/em&gt;—it fires twice. &lt;/p&gt;

&lt;p&gt;React intentionally mounts your component, unmounts it, and remounts it in development to help you catch bugs like stale closures, missed cleanups, and memory leaks. It's an incredibly useful feature, but it can make your development console extremely noisy and trigger unexpected side-effects.&lt;/p&gt;

&lt;p&gt;Faced with this, developers often end up doing one of three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Disable Strict Mode&lt;/strong&gt; entirely (and lose out on finding actual bugs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;if (!user || !socket) return&lt;/code&gt;&lt;/strong&gt; guard clauses everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stuff a &lt;code&gt;useRef(false)&lt;/code&gt;&lt;/strong&gt; flag inside the component to prevent double execution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What if you could keep Strict Mode on, but express your effect timing declaratively?&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://www.npmjs.com/package/@okyrychenko-dev/react-effect-when" rel="noopener noreferrer"&gt;&lt;code&gt;@okyrychenko-dev/react-effect-when&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛑 The Problem with Manual Guards
&lt;/h2&gt;

&lt;p&gt;Let's say you want to connect to a WebSocket, but you need both a &lt;code&gt;userId&lt;/code&gt; and an &lt;code&gt;authToken&lt;/code&gt; to be ready. &lt;/p&gt;

&lt;p&gt;Normally, you'd write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RealtimeConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authToken&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="c1"&gt;// 1. Guard against empty state or loading moments&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;userId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Do the actual work&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;connectSocket&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Clean up&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="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authToken&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;This works, but as your app grows, this pattern repeats everywhere. The real trigger condition is hidden inside the effect body. And if you want to reduce duplicate development-time noise for a specific effect, a common workaround is adding a &lt;code&gt;useRef&lt;/code&gt; flag alongside those conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✨ The Solution: Declarative Effect Hooks
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;react-effect-when&lt;/code&gt; gives you specialized tools to lift conditions right into the hook's signature. That removes a lot of boilerplate and also provides solid TypeScript narrowing.&lt;/p&gt;

&lt;p&gt;Let's take a look at the three main hooks the package provides.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;useEffectWhenReady&lt;/code&gt;: For asynchronous data and services
&lt;/h3&gt;

&lt;p&gt;When you are waiting for data, selectors, or services to load, you typically want to ensure none of them are &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useEffectWhenReady&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;@okyrychenko-dev/react-effect-when&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffectWhenReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;readyUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readyToken&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="c1"&gt;// 🚀 TypeScript knows these are non-null and non-undefined!&lt;/span&gt;
      &lt;span class="nf"&gt;trackProfileView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readyUser&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="nx"&gt;readyToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// The hook naturally tracks these dependencies:&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&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;strong&gt;Why it's better:&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No early returns.&lt;/strong&gt; The effect only fires when all dependencies are non-null and non-undefined.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in TypeScript narrowing.&lt;/strong&gt; &lt;code&gt;readyUser&lt;/code&gt; and &lt;code&gt;readyToken&lt;/code&gt; are strictly typed, eliminating the need for &lt;code&gt;user?.id&lt;/code&gt; checking inside the effect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;useEffectWhenTruthy&lt;/code&gt;: Perfect for boolean flags and UI states
&lt;/h3&gt;

&lt;p&gt;Sometimes your conditions are simple truthy flags. For instance, you want to show a toast only when a modal &lt;em&gt;actually opens&lt;/em&gt;, or connect a UI banner when a user is marked &lt;code&gt;isOnline&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useEffectWhenTruthy&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;@okyrychenko-dev/react-effect-when&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SessionBanner&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOnline&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffectWhenTruthy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;readyToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;online&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="nf"&gt;connectBannerChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readyToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;online&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;once&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="c1"&gt;// Re-run whenever it becomes truthy again!&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;Here, the effect runs when all dependencies resolve to a truthy value. If &lt;code&gt;isOnline&lt;/code&gt; toggles off and then on again, the cleanup runs and the effect reconnects.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;useEffectWhen&lt;/code&gt;: Full Control with a Custom Predicate
&lt;/h3&gt;

&lt;p&gt;This is the most flexible option. It works well for cases like analytics events in development or effects that should run only when several conditions match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;useEffectWhen&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;@okyrychenko-dev/react-effect-when&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isReady&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffectWhen&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="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_view&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="na"&gt;productId&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;([,&lt;/span&gt; &lt;span class="nx"&gt;ready&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;ready&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="c1"&gt;// Custom predicate&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;once&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By passing &lt;code&gt;{ once: true }&lt;/code&gt;, the effect runs once per mount lifecycle after the predicate first matches. That can reduce local Strict Mode noise for effects like analytics, while keeping the condition explicit at the call site.&lt;/p&gt;

&lt;p&gt;You can also use custom conditions for re-synchronization. For example, syncing an item list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffectWhen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;itemList&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="nf"&gt;syncToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemList&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;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOnline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasPermission&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// Custom logic right in the condition signature&lt;/span&gt;
  &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;itemList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;online&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;permission&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;online&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;permission&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;itemList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔍 Bonus: Debugging with &lt;code&gt;onSkip&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Ever wonder &lt;em&gt;why&lt;/em&gt; an effect isn't firing? Did a socket disconnect, or did the user drop to &lt;code&gt;null&lt;/code&gt;? The library provides an &lt;code&gt;onSkip&lt;/code&gt; callback for logging the dependency state that failed your predicate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;predicates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffectWhen&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;@okyrychenko-dev/react-effect-when&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;useEffectWhen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentToken&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="nf"&gt;initializeDashboard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentToken&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;predicates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onSkip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;pendingUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pendingToken&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Still waiting for dependencies to load:&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="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pendingUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pendingToken&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, you can inspect why an effect was skipped when the predicate does not match, instead of manually scattering debug logs through the component.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 The Takeaway
&lt;/h2&gt;

&lt;p&gt;We should not have to fight React's primitives. Ad-hoc &lt;code&gt;useRef(false)&lt;/code&gt; flags and &lt;code&gt;if (!data) return&lt;/code&gt; conditions get messy fast.&lt;/p&gt;

&lt;p&gt;Instead, a small abstraction over &lt;code&gt;useEffect&lt;/code&gt; can make intent clearer, reduce repeated guard boilerplate, and preserve the guardrails that React 18 gives us in development.&lt;/p&gt;

&lt;p&gt;This is not a replacement for plain &lt;code&gt;useEffect&lt;/code&gt;. But if your codebase has a lot of guard-heavy effects, it can be a cleaner way to express them.&lt;/p&gt;

&lt;p&gt;If that sounds familiar, give &lt;a href="https://github.com/okyrychenko-dev/react-effect-when" rel="noopener noreferrer"&gt;&lt;code&gt;react-effect-when&lt;/code&gt;&lt;/a&gt; a try.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it out:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @okyrychenko-dev/react-effect-when
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you like the approach, &lt;a href="https://github.com/okyrychenko-dev/react-effect-when" rel="noopener noreferrer"&gt;drop a ⭐️ on the GitHub repo&lt;/a&gt; and let me know what you think in the comments! 👇&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
