erock's bloghttps://erock.prose.sh2022-06-29T00:10:06ZTechnical writings by Eric BowererockRedux-saga style-guide2022-07-05T13:45:07Zhttps://erock.prose.sh/redux-saga-style-guide<p>Redux wrote a <a href="https://redux.js.org/style-guide/style-guide" rel="nofollow">style guide</a> that
attempts to set some standard practices on how to organize state management
using redux. I think this is an important step to create a platform for further
discussions. I found myself agreeing with most of the recommendations, but there
are a few that I disagree with. I think the primary reason why I disagree with
the style-guide is because virtually every project I use is built with
<code>redux-saga</code>. From my perspective, <code>redux-thunk</code> is rarely the right choice
except for very small react applications. Neither arguments building a <code>redux</code>
app are wrong, they are looking at redux from different ways to manage
side-effects.</p>
<p>As a software engineer that builds a lot of front-end apps with other engineers,
it's vitally important that <em>when</em> we build an app, we make it readable and
maintainable. Official recommendations are the north star for software engineers
because it is the culmination of years of experience and it sets a baseline for
how to build an app successfully. Any <code>strong recommendations</code> from the official
style-guide requires an equally strong reason for going against it.</p>
<p>In this article I will go through the recommendations that I think are
contentious and I want to put forth some recommendations when using
<code>redux-saga</code>.</p>
<h2 id="critique"><a class="anchor" href="#critique" rel="nofollow">#</a> Critique</h2>
<h3 id="put-as-much-logic-as-possible-in-reducers"><a class="anchor" href="#put-as-much-logic-as-possible-in-reducers" rel="nofollow">#</a> Put as much logic as possible in reducers</h3>
<ul>
<li><a href="https://redux.js.org/style-guide/style-guide#put-as-much-logic-as-possible-in-reducers" rel="nofollow">https://redux.js.org/style-guide/style-guide#put-as-much-logic-as-possible-in-reducers</a></li>
</ul>
<p>I built a large app that originally placed a lot of logic inside reducers. This
resulted in spaghetti code that was difficult to maintain:</p>
<ul>
<li>In order to understand what an action is doing, the developer needs to grep
for all occurrences of the action type and then piece together the logic.</li>
<li>Reducers would listen to actions that were similar, but because the payloads
were slightly different they couldn't easily be abstracted into the same
functionality, causing duplicated code.</li>
<li>I have regularly run into situations where a reducer needed access to other
slice data that is not easily available inside of a reducer slice.</li>
</ul>
<p>In general, I think having reducers hold a lot of logic makes that logic easy to
test but difficult to maintain, generalize, and refactor over time.</p>
<h3 id="model-actions-as-events-not-setters"><a class="anchor" href="#model-actions-as-events-not-setters" rel="nofollow">#</a> Model actions as events not setters</h3>
<ul>
<li><a href="https://redux.js.org/style-guide/style-guide#model-actions-as-events-not-setters" rel="nofollow">https://redux.js.org/style-guide/style-guide#model-actions-as-events-not-setters</a></li>
</ul>
<p>Theoretically I agree with this one because it does make the event log easier to
read. It will also make time travel debugging -- something I don't find useful
-- easier to perform because there would be fewer actions dispatched and they
are more traceable to find what triggered them.</p>
<p>However, in practice, I take a hybrid approach. When actions are being
dispatched from react, use events. When actions are dispatched from sagas
(effects), use setters. This will make an action traceable (find the source of
where the action was called) and reducers become generic containers of data that
are maintainable, composable. This is how I view reducers: they don't hold
logic, they are a database table.</p>
<p>Thinking of reducer slices as database tables provides clarity and consistency
to our state management. This doesn't diminish the utility of <code>redux</code> it's just
that there are better layers/tools to manage business logic -- <em>hint where we
manage side-effects</em>.</p>
<h3 id="allow-many-reducers-to-respond-to-the-same-action"><a class="anchor" href="#allow-many-reducers-to-respond-to-the-same-action" rel="nofollow">#</a> Allow many reducers to respond to the same action</h3>
<ul>
<li><a href="https://redux.js.org/style-guide/style-guide#allow-many-reducers-to-respond-to-the-same-action" rel="nofollow">https://redux.js.org/style-guide/style-guide#allow-many-reducers-to-respond-to-the-same-action</a></li>
</ul>
<p>My hot take is that I think there should be a 1:1 mapping between actions and
reducers. Reducers should own the actions (via <code>createSlice</code>) and only under
rare exceptions should a reducer listen to outside actions.</p>
<p>Employing this method, <code>redux</code> becomes a very thin layer of setters to hold data
and tell <code>react</code> when state has changed. I know this point of view is
<a href="https://twitter.com/dan_abramov/status/800310164792414208" rel="nofollow">controversial</a> and
normally when it comes to building apps as part of a team I don't like to go
against the standards, but this really is being driven by my experience building
large scale web applications with a team of engineers.</p>
<p>To me, the real value of redux is:</p>
<ul>
<li>A single global state object that is easily accessible through entire codebase</li>
<li>One structured way to update state</li>
<li>A single source of truth that our UI <em>reacts</em> to</li>
<li>With <code>react-redux</code> synchronizing UI with state is handled automatically</li>
</ul>
<h3 id="avoid-dispatching-many-actions-sequentially"><a class="anchor" href="#avoid-dispatching-many-actions-sequentially" rel="nofollow">#</a> Avoid dispatching many actions sequentially</h3>
<ul>
<li><a href="https://redux.js.org/style-guide/style-guide#avoid-dispatching-many-actions-sequentially" rel="nofollow">https://redux.js.org/style-guide/style-guide#avoid-dispatching-many-actions-sequentially</a></li>
</ul>
<p>I want a list of steps that demonstrate how redux is being updated ideally in
the same function. What I don't want is to grep all the reducers for an action
type to see how my state is being updated. This is especially annoying when
employing a modular, feature-based folder structure. We have replaced a single
function that centralizes business logic into a composition of functions that
are scattered throughout the codebase. The logic is broken up which makes the
flow of what is happening harder to understand. What compounds this even worse,
with libraries like <code>redux-saga</code>, sagas can also listen for those actions and
activate even more side-effects.</p>
<p><em>Aside: I try to only let sagas listen for events (react-side), not my setters
to avoid errant infinite loops.</em></p>
<p>The counter-argument regularly cited is that sequential dispatches could trigger
multiple react re-renders. This is because each action sequentially hits the
root reducer and could return a new version of the state, triggering an update
event. <code>redux</code> could have allowed for an array of actions to be dispatched, but
that was ultimately <a href="https://github.com/reduxjs/redux/pull/1813" rel="nofollow">rejected</a>.
Because of this, developers are now required to use
<a href="https://github.com/tshelburne/redux-batched-actions" rel="nofollow">redux-batched-actions</a>. I
think it should have been part of the API and if I were rebuilding <code>redux</code> from
scratch, it would be an included feature, but I also agree with their
perspective: it could make a lot of people unhappy and there's a user-land
library that makes it work all the same. Regardless, this suggestion and
argument revolving around performance is really because the redux API does not
support dispatching an array of actions. If I could add one thing to the redux
API it would probably be that.</p>
<h2 id="saga-style-guide"><a class="anchor" href="#saga-style-guide" rel="nofollow">#</a> Saga style-guide</h2>
<p>Take the <code>redux</code> style-guide, remove the ones listed above, and add these for my
unofficial <code>redux-saga</code> style-guide.</p>
<h3 id="effects-as-the-central-processing-unit"><a class="anchor" href="#effects-as-the-central-processing-unit" rel="nofollow">#</a> Effects as the central processing unit</h3>
<p>Most of my arguments revolve around using effects as the primary location for
business logic. Whenever I build a react/redux app, beyond the simplest of them,
I need something more powerful and maintainable than <code>redux-thunk</code>.
<code>redux-toolkit</code> endorses using <code>redux-thunk</code> and only under special
circumstances should we reach for something more powerful like <code>redux-saga</code>.
Personally, I think this should be the opposite. I understand that <code>redux-thunk</code>
is a simple addition (you could inline it easily) with only
<a href="https://github.com/reduxjs/redux-thunk/blob/master/src/index.js" rel="nofollow">14 lines of code</a>
but that's kind of my point. Redux has always struggled with one of the most
important parts of building a web app: side-effects. To be honest I actually
think this is a positive for <code>redux</code> because it manages state, not side-effects.
Use another tool to solve side-effects. Even Dan
<a href="https://twitter.com/dan_abramov/status/800105879290912768" rel="nofollow">admits</a> that he was
hoping that <code>redux-thunk</code> would be replaced by something built by the community.</p>
<p>To me, there's no real debate: use <code>redux-saga</code>. I understand why it cannot be
officially sanctioned: because for simple todo apps -- something the js
community uses as a litmus test to compare implementations -- it is unnecessary.
I get it, but beyond anything simple, you <em>need</em> something more powerful.</p>
<p>Yes, there's a learning curve, the same can be said for <code>redux</code> and yet we all
still recommend it. <code>redux-saga</code> uses ES6 generators, they are not that
difficult to grok, and are part of the language. If you are an engineer, it is
your responsibility to learn all language features.</p>
<h3 id="reducers-as-setters"><a class="anchor" href="#reducers-as-setters" rel="nofollow">#</a> Reducers as setters</h3>
<p>Redux is an object that should be thought of like a database. Reducer slices are
database tables. We should reduce boilerplate with slice helpers
(<a href="https://github.com/neurosnap/robodux#slice-helpers" rel="nofollow">robodux</a>) by leveraging
new <em>officially</em> sanctioned helpers like <code>createSlice</code>.</p>
<p>We don't even need to test our reducers anymore because these libraries already
did that for us.</p>
<p>This makes reducers predictable, isn't that one of the taglines for redux? A
<code>predictable</code> state container? Reducers are simplified, and slice helpers cover
90% of our use cases, because we are treating them like database tables.</p>
<h3 id="ui-dispatches-events-effects-dispatch-events-and-setters"><a class="anchor" href="#ui-dispatches-events-effects-dispatch-events-and-setters" rel="nofollow">#</a> UI dispatches events, effects dispatch events and setters</h3>
<p>When <code>react</code> dispatches actions, it should dispatch events, like the <code>redux</code>
style-guide recommends.</p>
<p>When effects like sagas dispatch actions, it can dispatch events and setters.
This still provides some traceability and helps centralize business logic into
one layer.</p>
<h3 id="avoid-listening-to-setters"><a class="anchor" href="#avoid-listening-to-setters" rel="nofollow">#</a> Avoid listening to setters</h3>
<p>When you ever have the urge to listen to a setter action in <code>redux-saga</code>, think
again. This invariably will have the unintended consequence of creating infinite
loops where a saga (A) will keep dispatching setters and saga (B) will listen
for those setters and dispatch actions that trigger saga (A).</p>
<h3 id="build-indexes-for-your-db-tables"><a class="anchor" href="#build-indexes-for-your-db-tables" rel="nofollow">#</a> Build indexes for your db tables</h3>
<p>Need to have a sorted list of entities that come from an API? Need to group a
subset of entities? First try to create a selector for it. If we need to
preserve the order the API sent us then we can create a reducer <code>EntityId[]</code>
that acts like an index.</p>
<p>Yes, it feels like we are rebuilding a database, but it's not that much work and
the manual process allows for performance tweaking which is desirable when
building a large application. You will also have to maintain both reducers
together. This might sound tedious or prone to errors but in reality these two
reducers are coupled by our effects, so it's not that difficult.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">call</span><span class="p">,</span> <span class="nx">put</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'redux-saga/effects'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createTable</span><span class="p">,</span> <span class="nx">createAssign</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'robodux'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">batchActions</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'redux-batched-actions'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kr">interface</span> <span class="nx">Article</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="nx">title</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">post</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">author</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kr">interface</span> <span class="nx">ArticleMap</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="p">[</span><span class="nx">key</span>: <span class="kt">string</span><span class="p">]</span><span class="o">:</span> <span class="nx">Article</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// hashmap to store all articles for easy id lookup
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">articles</span> <span class="o">=</span> <span class="nx">createTable</span><span class="p"><</span><span class="nt">ArticleMap</span><span class="p">>({</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">'articles'</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="kr">set</span><span class="o">:</span> <span class="nx">setArticles</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">articles</span><span class="p">.</span><span class="nx">actions</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1">// sorted array of article ids that we receive from the API
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">articleOrder</span> <span class="o">=</span> <span class="nx">createAssign</span><span class="p"><</span><span class="nt">string</span><span class="err">[]</span><span class="p">>({</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s1">'articleOrder'</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="kr">set</span><span class="o">:</span> <span class="nx">setArticleOrder</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">articleOrder</span><span class="p">.</span><span class="nx">actions</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="kd">function</span><span class="o">*</span> <span class="nx">onFetchArticles() {</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"> <span class="kr">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">yield</span> <span class="nx">call</span><span class="p">(</span><span class="nx">fetch</span><span class="p">,</span> <span class="s1">'/articles'</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="kr">const</span> <span class="nx">articles</span>: <span class="kt">Article</span><span class="p">[]</span> <span class="o">=</span> <span class="k">yield</span> <span class="nx">call</span><span class="p">([</span><span class="nx">response</span><span class="p">,</span> <span class="s1">'json'</span><span class="p">]);</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"> <span class="c1">// preserve order from API
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">articleOrder</span> <span class="o">=</span> <span class="nx">articles</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">article</span><span class="p">)</span> <span class="o">=></span> <span class="nx">article</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"> <span class="c1">// build hashmap of articles (normalize) for easier
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="c1"></span> <span class="c1">// lookup and update
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">articleMap</span> <span class="o">=</span> <span class="nx">articles</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"> <span class="p">.</span><span class="nx">reduce</span><span class="p"><</span><span class="nt">ArticleMap</span><span class="p">>((</span><span class="nx">acc</span><span class="p">,</span> <span class="nx">article</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"> <span class="nx">acc</span><span class="p">[</span><span class="nx">article</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span> <span class="o">=</span> <span class="nx">article</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"> <span class="k">return</span> <span class="nx">acc</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"> <span class="p">},</span> <span class="p">{});</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"> <span class="k">yield</span> <span class="nx">put</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"> <span class="nx">batchActions</span><span class="p">([</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"> <span class="nx">setArticles</span><span class="p">(</span><span class="nx">articleMap</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"> <span class="nx">setArticleOrder</span><span class="p">(</span><span class="nx">articleOrder</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"> <span class="p">]),</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><h2 id="conclusion"><a class="anchor" href="#conclusion" rel="nofollow">#</a> Conclusion</h2>
<p>These are all ideas I use when building large scale web applications and it has
worked extremely well for us. These recommendations are subtle differences
between the official style guide and using <code>redux-saga</code>.</p>
<blockquote>
<p>Congrats! you made it to the end of this article. Do you love <code>redux-saga</code>?
Try out <a href="https://github.com/neurosnap/saga-query" rel="nofollow">saga-query</a> which is our
<code>rtk-query</code>/<code>react-query</code> equivalent. Seriously, it is awesome.</p>
</blockquote>
<p>I'd love to read your thoughts so feel free to email me (blog [at] erock.io)
about this style-guide.</p>
<h2 id="links"><a class="anchor" href="#links" rel="nofollow">#</a> Links</h2>
<ul>
<li><a href="https://redux.js.org/style-guide/style-guide" rel="nofollow">https://redux.js.org/style-guide/style-guide</a></li>
<li><a href="https://redux.js.org/faq/code-structure#how-should-i-split-my-logic-between-reducers-and-action-creators-where-should-my-business-logic-go" rel="nofollow">https://redux.js.org/faq/code-structure#how-should-i-split-my-logic-between-reducers-and-action-creators-where-should-my-business-logic-go</a></li>
<li><a href="https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao-of-redux-part-2/#thick-and-thin-reducers" rel="nofollow">https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao-of-redux-part-2/#thick-and-thin-reducers</a></li>
<li><a href="https://twitter.com/dan_abramov/status/800310164792414208" rel="nofollow">https://twitter.com/dan_abramov/status/800310164792414208</a></li>
<li><a href="https://github.com/reduxjs/redux-toolkit/issues/91#issuecomment-456827660" rel="nofollow">https://github.com/reduxjs/redux-toolkit/issues/91#issuecomment-456827660</a></li>
<li><a href="https://github.com/reduxjs/redux-toolkit/issues/17#issuecomment-414543588" rel="nofollow">https://github.com/reduxjs/redux-toolkit/issues/17#issuecomment-414543588</a></li>
<li><a href="https://github.com/neurosnap/saga-query" rel="nofollow">https://github.com/neurosnap/saga-query</a></li>
</ul>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
Recommended rules for building web apps with redux-sagaWhat is a selector?2022-07-05T13:45:09Zhttps://erock.prose.sh/what-is-a-selector<p>If <code>redux</code> is our front-end database, selectors are reusable functions that let
us query our database. There are some rules that are required for selectors to
be useful:</p>
<ul>
<li>Pure functions (deterministic: given the same inputs they will always return
the same outputs)</li>
<li>Function signature: <code>(state: State, props?: { [key: string]: any }) => any</code></li>
</ul>
<p>Selectors ought to keep to the function signature above. We should also try to
avoid using any and instead type exactly when the function requires and returns.</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">selectControlsByLineage</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nx">state</span>: <span class="kt">State</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"> <span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">lineage</span>: <span class="kt">string</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">)</span><span class="o">:</span> <span class="nx">Control</span><span class="p">[]</span> <span class="o">=></span> <span class="p">{};</span>
</span></span></code></pre><h2 id="what-is-reselect"><a class="anchor" href="#what-is-reselect" rel="nofollow">#</a> What is reselect?</h2>
<p><code>reselect</code> is a third-party library that helps us build composable selectors as
well as dramatically improve the performance of our queries with memoization.
Memoization is a technique to cache the result of a function. Since selectors
must be pure functions, if the inputs are the same, then the output must also be
the same from the previous computation. This is one of the main mechanisms we
use to improve performance of our application. We use selectors on every single
page within our front-end application and some of the queries we make to our
database can be complex. Determining the time complexity of a selector is a
crucial part of improving the performance of our application. By leveraging
reselect, we sacrifice memory for CPU cycles.</p>
<p>I ask everyone reading this to please spend 10 minutes reading the reselect
docs. They do a great job explaining the API with plenty of examples on how to
use it.</p>
<h2 id="when-should-i-use-reselect"><a class="anchor" href="#when-should-i-use-reselect" rel="nofollow">#</a> When should I use reselect?</h2>
<p>When to use reselect is very context dependent. Since these are reusable
queries, they have the opportunity to save other developers a lot of time
building their own selectors. When it comes to using reselect for performance
reasons, it’s always recommended to analyze the performance of a query before
and after using reselect. Having said that, I use a very simple heuristic for
when to use reselect:</p>
<blockquote>
<p>If the time complexity for the query is equal to or worse than linear time
O(n) then we should probably build the selector using createSelector.</p>
</blockquote>
<h3 id="setup"><a class="anchor" href="#setup" rel="nofollow">#</a> Setup</h3>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">interface</span> <span class="nx">ToDo</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">text</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">boolean</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kr">interface</span> <span class="nx">State</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">todos</span><span class="o">:</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span>: <span class="kt">string</span><span class="p">]</span><span class="o">:</span> <span class="nx">ToDo</span> <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kr">const</span> <span class="nx">state</span>: <span class="kt">State</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">todos</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="mi">1</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">1</span><span class="p">,</span> <span class="nx">text</span><span class="o">:</span> <span class="s2">"learn what a selector is"</span><span class="p">,</span> <span class="nx">completed</span>: <span class="kt">true</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="mi">2</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">2</span><span class="p">,</span> <span class="nx">text</span><span class="o">:</span> <span class="s2">"learn what reselect is"</span><span class="p">,</span> <span class="nx">completed</span>: <span class="kt">true</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="mi">3</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">3</span><span class="p">,</span> <span class="nx">text</span><span class="o">:</span> <span class="s2">"learn when I should use reselect"</span><span class="p">,</span> <span class="nx">completed</span>: <span class="kt">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="mi">4</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">4</span><span class="p">,</span> <span class="nx">text</span><span class="o">:</span> <span class="s2">"learn how to write selectors"</span><span class="p">,</span> <span class="nx">completed</span>: <span class="kt">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><h3 id="example-1"><a class="anchor" href="#example-1" rel="nofollow">#</a> Example 1</h3>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">selectTodos</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">)</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">todos</span><span class="p">;</span>
</span></span></code></pre><p>Should we use reselect for selectTodos? To answer this question we need to
understand the time complexity of this function. Accessing a property on an
object is O(1) which is faster than linear time. Therefore, we do not need to
use reselect.</p>
<h3 id="example-2"><a class="anchor" href="#example-2" rel="nofollow">#</a> Example 2</h3>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">selectTodoById</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">,</span> <span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="kr">const</span> <span class="nx">todos</span> <span class="o">=</span> <span class="nx">selectTodos</span><span class="p">(</span><span class="nx">state</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"> <span class="k">return</span> <span class="nx">todos</span><span class="p">[</span><span class="nx">id</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><p>Should we use reselect for <code>selectTodoById</code>? A hash lookup is O(1), so no we
should not use reselect in this case.</p>
<h3 id="example-3"><a class="anchor" href="#example-3" rel="nofollow">#</a> Example 3</h3>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">selectCompletedTodos</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="kr">const</span> <span class="nx">todos</span> <span class="o">=</span> <span class="nx">selectTodos</span><span class="p">(</span><span class="nx">state</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"> <span class="k">return</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">values</span><span class="p">(</span><span class="nx">todos</span><span class="p">).</span><span class="nx">filter</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todo</span><span class="p">.</span><span class="nx">completed</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><p>Should we use reselect for <code>selectCompletedTodos</code>?
<a href="https://stackoverflow.com/questions/7716812/object-keys-complexity/7716858#7716858" rel="nofollow">Object.values for v8 appears to be O(n)</a>
and the filter operation on the lists of todos is also O(n). This operation
should be memoized since it requires linear time to complete.</p>
<p>How would we convert the above function to use <code>createSelector</code>?</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createSelector</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"reselect"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">const</span> <span class="nx">selectTodosAsList</span> <span class="o">=</span> <span class="nx">createSelector</span><span class="p">((</span><span class="nx">todos</span><span class="p">)</span> <span class="o">=></span> <span class="nb">Object</span><span class="p">.</span><span class="nx">values</span><span class="p">(</span><span class="nx">todos</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kr">const</span> <span class="nx">selectCompletedTodos</span> <span class="o">=</span> <span class="nx">createSelector</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"> <span class="nx">selectTodosAsList</span><span class="p">,</span> <span class="c1">// selector composition is critical!
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span> <span class="p">(</span><span class="nx">todoList</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todoList</span><span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todo</span><span class="p">.</span><span class="nx">completed</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">);</span>
</span></span></code></pre><h2 id="createselector-limitation"><a class="anchor" href="#createselector-limitation" rel="nofollow">#</a> createSelector limitation</h2>
<p>It’s important to note that createSelector will only cache the last result. So
if the inputs keep changing then it will constantly recompute the query.</p>
<h3 id="example"><a class="anchor" href="#example" rel="nofollow">#</a> Example</h3>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createSelector</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"reselect"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">const</span> <span class="nx">selectTodosByText</span> <span class="o">=</span> <span class="nx">createSelector</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">selectTodos</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">,</span> <span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">search</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span> <span class="nx">props</span><span class="p">.</span><span class="nx">search</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="p">(</span><span class="nx">todos</span><span class="p">,</span> <span class="nx">search</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todos</span><span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todo</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">search</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">selectTodosByText</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">search</span><span class="o">:</span> <span class="s2">"what"</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// returns a cached result!
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="nx">selectTodosByText</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">search</span><span class="o">:</span> <span class="s2">"what"</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">// recomputes because the input changed
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span><span class="nx">selectTodosByText</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">search</span><span class="o">:</span> <span class="s2">"when"</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// recomputes beacuse the input changed again!
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span><span class="nx">selectTodosByText</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">search</span><span class="o">:</span> <span class="s2">"what"</span> <span class="p">});</span>
</span></span></code></pre><p>It does not matter if at one point in time we called the selector with the same
props, if the last function execution does not match the same inputs as the
current function execution then it will recompute the query.</p>
<h2 id="when-should-i-build-a-selector-creator"><a class="anchor" href="#when-should-i-build-a-selector-creator" rel="nofollow">#</a> When should I build a selector creator?</h2>
<p>A selector creator is a function that creates selectors. This allows us to get
around the last result cache limitation of createSelector that was described
previously. A selector creator is particularly useful when we use the same
selector in multiple places on the same page.</p>
<h3 id="example-4"><a class="anchor" href="#example-4" rel="nofollow">#</a> Example</h3>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">"react"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useSelector</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"react-redux"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kr">const</span> <span class="nx">Page</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="kr">const</span> <span class="nx">whenTodos</span> <span class="o">=</span> <span class="nx">useSelector</span><span class="p">((</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">)</span> <span class="o">=></span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="nx">selectTodosByText</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">search</span><span class="o">:</span> <span class="s2">"when"</span> <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="kr">const</span> <span class="nx">whereTodos</span> <span class="o">=</span> <span class="nx">useSelector</span><span class="p">((</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">)</span> <span class="o">=></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="nx">selectTodosByText</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">search</span><span class="o">:</span> <span class="s2">"where"</span> <span class="p">})</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="p"><</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="p"><</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="p">{</span><span class="nx">whenTodos</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=></span> <span class="p"><</span><span class="nt">div</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">todo</span><span class="p">.</span><span class="nx">id</span><span class="p">}>{</span><span class="nx">todo</span><span class="p">.</span><span class="nx">text</span><span class="p">}</</span><span class="nt">div</span><span class="p">>)}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="p"><</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"> <span class="p">{</span><span class="nx">whereTodos</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=></span> <span class="p"><</span><span class="nt">div</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">todo</span><span class="p">.</span><span class="nx">id</span><span class="p">}>{</span><span class="nx">todo</span><span class="p">.</span><span class="nx">text</span><span class="p">}</</span><span class="nt">div</span><span class="p">>)}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"> <span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"> <span class="p"></</span><span class="nt">div</span><span class="p">></span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><p>In this case, <code>createSelector</code> is rendered useless because we are constantly
changing the inputs being supplied to our selector <code>selectTodosByText</code>.</p>
<p>However, if we build a function that creates selectors for us, then we can build
as many createSelector for our one query as many times as we want.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">const</span> <span class="nx">createSelectorTodosByText</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="nx">createSelector</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">selectTodos</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">,</span> <span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">search</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span> <span class="nx">props</span><span class="p">.</span><span class="nx">search</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="p">(</span><span class="nx">todos</span><span class="p">,</span> <span class="nx">search</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todos</span><span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todo</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">search</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">"react"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useSelector</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"react-redux"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// do NOT create these variables inside the react component without
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">// `useCallback` or `useMemo` because everytime these are called they
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">// create a new selector with a blank cache.
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1">// It's safer to come up with a way to define these outside the
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// react component.
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">selectWhenTodos</span> <span class="o">=</span> <span class="nx">createSelectorTodosByText</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="kr">const</span> <span class="nx">selectWhereTodos</span> <span class="o">=</span> <span class="nx">createSelectorTodosByText</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="kr">const</span> <span class="nx">Page</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"> <span class="kr">const</span> <span class="nx">whenTodos</span> <span class="o">=</span> <span class="nx">useSelector</span><span class="p">((</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">)</span> <span class="o">=></span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="nx">selectWhenTodos</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">search</span><span class="o">:</span> <span class="s2">"when"</span> <span class="p">})</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="kr">const</span> <span class="nx">whereTodos</span> <span class="o">=</span> <span class="nx">useSelector</span><span class="p">((</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">)</span> <span class="o">=></span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="nx">selectWhereTodos</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">search</span><span class="o">:</span> <span class="s2">"where"</span> <span class="p">})</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="c1">// rendering both todos on the page
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"></span><span class="p">};</span>
</span></span></code></pre><p>This is great because now we have two separate memoized selectors that we can
use in this react component without popping their cache.</p>
<h2 id="avoid-calling-createselector-inside-a-react-component"><a class="anchor" href="#avoid-calling-createselector-inside-a-react-component" rel="nofollow">#</a> Avoid calling <code>createSelector</code> inside a react component</h2>
<p>Calling <code>createSelector</code> within a react component creates a new memoized
selector on every single run of the component. This defeats the purpose of using
reselect.</p>
<h3 id="example-5"><a class="anchor" href="#example-5" rel="nofollow">#</a> Example</h3>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">makeSelectTodosById</span> <span class="o">=</span> <span class="p">(</span><span class="nx">id</span>: <span class="kt">string</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="k">return</span> <span class="nx">createSelector</span><span class="p">(</span><span class="nx">selectTodos</span><span class="p">,</span> <span class="p">(</span><span class="nx">todos</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todos</span><span class="p">[</span><span class="nx">id</span><span class="p">]);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kr">const</span> <span class="nx">ToDoPage</span> <span class="o">=</span> <span class="p">(</span><span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"> <span class="c1">// this **creates** a new memoized selector everytime the react component
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"></span> <span class="c1">// re-renders, which wipes the cache for the selector!
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">todo</span> <span class="o">=</span> <span class="nx">useSelector</span><span class="p">(</span><span class="nx">makeSelectTodosById</span><span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">id</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><p>Selector builders are not a good way to pass props into the selector.</p>
<h3 id="passing-props-to-a-selector"><a class="anchor" href="#passing-props-to-a-selector" rel="nofollow">#</a> Passing props to a selector</h3>
<p>If we want to pass props into a selector, build a selector like this:</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">const</span> <span class="nx">selectPropId</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span> <span class="nx">id</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">const</span> <span class="nx">selectTodoById</span> <span class="o">=</span> <span class="nx">createSelector</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">selectTodos</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">selectPropId</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="p">(</span><span class="nx">todos</span><span class="p">,</span> <span class="nx">id</span><span class="p">)</span> <span class="o">=></span> <span class="nx">todos</span><span class="p">[</span><span class="nx">id</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kr">const</span> <span class="nx">ToDoPage</span> <span class="o">=</span> <span class="p">(</span><span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="kr">const</span> <span class="nx">todo</span> <span class="o">=</span> <span class="nx">useSelector</span><span class="p">((</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">)</span> <span class="o">=></span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">selectTodoById</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">prop.id</span> <span class="p">})</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><h2 id="when-to-use-createselector-or-usememo"><a class="anchor" href="#when-to-use-createselector-or-usememo" rel="nofollow">#</a> When to use createSelector or useMemo</h2>
<p>With the rapid adoption of react hooks and t he introduction of <code>useMemo</code>, one
might ask:
<a href="https://github.com/reduxjs/reselect/issues/386" rel="nofollow">do we need createSelector anymore?</a>
I think this topic warrants its own post but I will briefly discuss my thoughts
on the topic.</p>
<p>Both <code>createSelector</code> and <code>useMemo</code> cache the result of some computation. With
<code>createSelector</code> the function is created once in memory and then used throughout
the entire application. As we have seen, when we need to memoize more than one
call to <code>createSelector</code> then we need to create a selector factory. With
<code>useMemo</code>, on the other hand, the memoization function is created within the
main component render function. <code>react</code> has some magic to make this work
correctly that I won't go into, but feel free to read Dan's
<a href="https://overreacted.io/why-do-hooks-rely-on-call-order/" rel="nofollow">Why do hooks rely on call order?</a></p>
<p><a href="https://overreacted.io/before-you-memo/" rel="nofollow">There's a cost to using <code>useMemo</code></a> and
<a href="https://medium.com/swlh/should-you-use-usememo-in-react-a-benchmarked-analysis-159faf6609b7" rel="nofollow">recent benchmarks suggest that <code>useMemo</code> should be used sparringly</a>.</p>
<p>Because of <code>react</code>'s magic in order to get hooks to work with their current API,
there's a cost to using them.</p>
<p>Basically, the decision tree for using <code>createSelector</code> vs <code>useMemo</code> should look
something like this:</p>
<p><a href="https://erock.imgs.sh/selector-flow-chart.jpg" rel="nofollow"><img src="/selector-flow-chart.jpg" alt="picture describing how to decide whether to use createSelector or useMemo"></a></p>
<p>The simplest heuristic I can come up with:</p>
<blockquote>
<p>If you can find a way to use <code>createSelector</code> instead of <code>useMemo</code> then that
is preferred.</p>
</blockquote>
<p>I plan on writing a follow-up article on this topic that goes deeper into the
performance differences between <code>createSelector</code> and <code>useMemo</code>.</p>
<h2 id="how-to-use-selectors-inside-redux-saga"><a class="anchor" href="#how-to-use-selectors-inside-redux-saga" rel="nofollow">#</a> How to use selectors inside redux-saga</h2>
<p>Using selectors inside a saga is pretty simple. redux-saga provides a helper
function called select which will automatically pass the state to the variable.</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">select</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"redux-saga/effects"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">const</span> <span class="nx">selectToken</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">)</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">token</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kr">const</span> <span class="nx">selectUserById</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">,</span> <span class="nx">props</span><span class="o">:</span> <span class="p">{</span> <span class="nx">id</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">id</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="kd">function</span><span class="o">*</span> <span class="nx">onLogin() {</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"> <span class="kr">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">yield</span> <span class="nx">select</span><span class="p">(</span><span class="nx">selectToken</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"> <span class="kr">const</span> <span class="nx">userId</span> <span class="o">=</span> <span class="k">yield</span> <span class="nx">select</span><span class="p">(</span><span class="nx">selectUserbyId</span><span class="p">,</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
A quick introduction to using selectors in a redux applicationRecreating redux-toolkit's createSlice2022-07-05T13:45:07Zhttps://erock.prose.sh/recreating-redux-toolkits-create-slice<h2 id="what-is-a-slice"><a class="anchor" href="#what-is-a-slice" rel="nofollow">#</a> What is a slice?</h2>
<p>In redux, a slice is a "slice" of your redux state object.</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="cm">{
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="cm"> token: '', // this is a slice
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="cm"> users: {}, // this is a slice
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="cm"> todos: {}, // this is a slice
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="cm">}
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="cm">*/</span>
</span></span></code></pre><h2 id="actions-and-reducers-per-slice"><a class="anchor" href="#actions-and-reducers-per-slice" rel="nofollow">#</a> Actions and reducers per slice</h2>
<p><a href="https://redux.js.org/recipes/structuring-reducers/splitting-reducer-logic" rel="nofollow">Splitting up reducer logic</a>
is an import concept in redux where we compose multiple reducers into one big
reducer using <a href="https://redux.js.org/api/combinereducers" rel="nofollow">combineReducers</a>. For
every slice, there is a single corresponding reducer. When building store data
inside redux, it is very common to build a set of actions and a reducer that
responds to those actions.</p>
<h2 id="what-is-createslice"><a class="anchor" href="#what-is-createslice" rel="nofollow">#</a> What is <code>createSlice</code>?</h2>
<p><code>createSlice</code> is a higher-order function that accepts the slice name (e.g.
<code>token</code>, <code>user</code>, <code>todos</code>), a set of reducers, and returns a single reducer along
with the action creators for that reducer. The goal of <code>createSlice</code> is to
reduce the boilerplate required to add data to redux the canonical way.</p>
<p>The <a href="https://redux-toolkit.js.org/api/createSlice" rel="nofollow">createSlice</a> we know of today
from <code>redux-toolkit</code> was inspired by
<a href="https://github.com/ericelliott/autodux" rel="nofollow">autodux</a>.
<a href="https://github.com/reduxjs/redux-toolkit/issues/17#issuecomment-414543588" rel="nofollow">I also helped build the original implementation in redux-toolkit</a>
and have been using it for every redux project since. It is a powerful helper
function that has gained a ton of popularity in the redux community.</p>
<p>However, it is common for engineers learning redux for the first time to be
completely overwhelmed by the terms and phrases used by the redux community.
This is exacerbated by the fact that every reducer is now wrapped by
<code>createSlice</code>.</p>
<p>In this post, I want to demystify <code>createSlice</code> by building our own stripped
down version of it for new engineers to use as a reference guide when learning
redux.</p>
<h2 id="byo-createslice"><a class="anchor" href="#byo-createslice" rel="nofollow">#</a> BYO <code>createSlice</code></h2>
<p>In order to build our own <code>createSlice</code> we need to build a couple of other
helper functions first.</p>
<p>Note: all of these implementations are simplified versions of the official ones
to be used as a learning guide. If you dig into the <code>redux-toolkit</code> source code,
you'll see that most of the code are typings and embellishments on top of the
code written in this article.</p>
<p>For our example usage we will be recreating redux's
<a href="https://redux.js.org/basics/example#example-todo-list" rel="nofollow">example todo list</a></p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">type</span> <span class="nx">ToDo</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nx">id</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"> <span class="nx">text</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">boolean</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><h3 id="byo-createaction"><a class="anchor" href="#byo-createaction" rel="nofollow">#</a> BYO <code>createAction</code></h3>
<p><code>createAction</code> is a simple helper function that accepts a string and returns an
action creator.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">function</span> <span class="nx">createAction</span><span class="p"><</span><span class="nt">P</span> <span class="err">=</span> <span class="na">any</span><span class="p">>(</span><span class="kr">type</span><span class="o">:</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> <span class="kr">const</span> <span class="nx">actionCreator</span> <span class="o">=</span> <span class="p">(</span><span class="nx">payload?</span>: <span class="kt">P</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="kr">type</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">payload</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="c1">// this overrides the default stringification
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span> <span class="c1">// method so when we stringify the action creator
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span> <span class="c1">// we get the action type
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span> <span class="nx">actionCreator</span><span class="p">.</span><span class="nx">toString</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="sb">`</span><span class="si">${</span><span class="kr">type</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="k">return</span> <span class="nx">actionCreator</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><h4 id="example-usage-for-createaction"><a class="anchor" href="#example-usage-for-createaction" rel="nofollow">#</a> Example usage for <code>createAction</code></h4>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">const</span> <span class="nx">addTodo</span> <span class="o">=</span> <span class="nx">createAction</span><span class="p"><</span><span class="nt">ToDo</span><span class="p">>(</span><span class="s2">"ADD_TODO"</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">addTodo</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">"1"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">text</span><span class="o">:</span> <span class="s2">"build my own createAction"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="cm">{
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="cm"> type: 'ADD_TODO',
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="cm"> payload: {
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="cm"> id: '1',
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="cm"> text: 'build my own createAction',
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="cm"> completed: true
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="cm"> },
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="cm">}
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="cm">*/</span>
</span></span></code></pre><h3 id="byo-createreducer"><a class="anchor" href="#byo-createreducer" rel="nofollow">#</a> BYO <code>createReducer</code></h3>
<p><code>createReducer</code> is a function that accepts an object where the keys are the
action type and the values are the reducer.</p>
<p>The <code>redux-toolkit</code> version of <code>createReducer</code> leverages
<a href="https://github.com/immerjs/immer" rel="nofollow">immer</a> to handle state updates. I won't go
into the details of how <code>immer</code> works but just know that it is a clever way for
the end-developer to appear to mutate their state object while under-the-hood
<code>immer</code> actually handles updates to the state in an immutable, redux-friendly
manner.</p>
<p>For the purposes of our demonstration, we will <em>not</em> be using <code>immer</code>.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// for the purposes of this demonstration I'm removing
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// types because otherwise it would dramatically increase
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// the complexity of this code.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span><span class="kd">function</span> <span class="nx">createReducer</span><span class="p">(</span><span class="nx">initialState</span><span class="p">,</span> <span class="nx">reducers</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="cm">/*
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="cm"> This is a reducer function that selects one of the
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="cm"> other reducer functions based on the action type (key).
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="cm"> When we call this reducer, we do a lookup on our
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="cm"> `reducers` object by the key which, in this case,
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="cm"> is the `action.type`. If there's a match we call that
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="cm"> reducer function with the `action.payload`.
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="cm">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="cm"> If our `reducers` object was
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="cm"> { increment: (state, payload) => state += 1 }
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="cm"> and the reducer function received:
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="cm"> state = 0, action = { type: 'increment' }
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="cm"> we match the action type with the reducers key
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="cm"> 'increment',call that reducer function, and the new
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="cm"> state value would be `1`.
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"> <span class="kr">const</span> <span class="nx">reducer</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">initialState</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"> <span class="kr">const</span> <span class="nx">caseReducer</span> <span class="o">=</span> <span class="nx">reducers</span><span class="p">[</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">caseReducer</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="k">return</span> <span class="nx">state</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"> <span class="c1">// note that I am not passing the entire action
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"></span> <span class="c1">// object to each reducer, simply the payload
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"></span> <span class="k">return</span> <span class="nx">caseReducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"> <span class="k">return</span> <span class="nx">reducer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><h4 id="example-usage-for-createreducer"><a class="anchor" href="#example-usage-for-createreducer" rel="nofollow">#</a> Example usage for <code>createReducer</code></h4>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createStore</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"redux"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">type</span> <span class="nx">State</span> <span class="o">=</span> <span class="nx">ToDo</span><span class="p">[];</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kr">const</span> <span class="nx">addTodo</span> <span class="o">=</span> <span class="nx">createAction</span><span class="p"><</span><span class="nt">ToDo</span><span class="p">>(</span><span class="s2">"ADD_TODO"</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kr">const</span> <span class="nx">toggleTodo</span> <span class="o">=</span> <span class="nx">createAction</span><span class="p"><</span><span class="nt">string</span><span class="p">>(</span><span class="s2">"TOGGLE_TODO"</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kr">const</span> <span class="nx">reducer</span> <span class="o">=</span> <span class="nx">createReducer</span><span class="p">([],</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">addTodo</span><span class="o">:</span> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">,</span> <span class="nx">payload</span>: <span class="kt">ToDo</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="k">return</span> <span class="p">[...</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">];</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">toggleTodo</span><span class="o">:</span> <span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">payload</span>: <span class="kt">string</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="k">return</span> <span class="nx">state</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="c1">// when we find the todo id that
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span> <span class="c1">// matches the payload we toggle the completed state
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span> <span class="k">if</span> <span class="p">(</span><span class="nx">todo</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="k">return</span> <span class="p">{</span> <span class="p">...</span><span class="nx">todo</span><span class="p">,</span> <span class="nx">completed</span><span class="o">:</span> <span class="o">!</span><span class="nx">todo</span><span class="p">.</span><span class="nx">completed</span> <span class="p">};</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="k">return</span> <span class="nx">todo</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="kr">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createStore</span><span class="p">(</span><span class="nx">reducer</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="nx">addTodo</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">"1"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"> <span class="nx">text</span><span class="o">:</span> <span class="s2">"byo createAction"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"> <span class="p">}),</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"> <span class="nx">addTodo</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">"2"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"> <span class="nx">text</span><span class="o">:</span> <span class="s2">"byo createReducer"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">false</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"> <span class="p">}),</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"> <span class="nx">addTodo</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">"3"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"> <span class="nx">text</span><span class="o">:</span> <span class="s2">"byo createSlice"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">false</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"> <span class="p">}),</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="cm"> [
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="cm"> { id: '1', text: 'byo createAction', completed: true }
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="cm"> { id: '2', text: 'byo createReducer', completed: false }
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="cm"> { id: '3', text: 'byo createSlice', completed: false }
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="cm"> ]
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">toggleTodo</span><span class="p">(</span><span class="s2">"2"</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="cm"> [
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="cm"> { id: '1', text: 'byo createAction', completed: true }
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="cm"> { id: '2', text: 'byo createReducer', completed: true }
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="cm"> { id: '3', text: 'byo createSlice', completed: false }
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="cm"> ]
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="cm">*/</span>
</span></span></code></pre><h2 id="createslice-implementation"><a class="anchor" href="#createslice-implementation" rel="nofollow">#</a> <code>createSlice</code> implementation</h2>
<p>Okay, now that we have our implementation for <code>createAction</code> and <code>createReducer</code>
built, we can move onto building our <code>createSlice</code>.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// helper to build action types scoped to the
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// slice name to avoid naming conflicts
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">actionTypeBuilder</span> <span class="o">=</span> <span class="p">(</span><span class="nx">slice</span><span class="p">)</span> <span class="p">=></span> <span class="p">(</span><span class="nx">action</span><span class="p">)</span> <span class="p">=></span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">slice</span> <span class="o">?</span> <span class="sb">`</span><span class="si">${</span><span class="nx">slice</span><span class="si">}</span><span class="sb">/</span><span class="si">${</span><span class="nx">action</span><span class="si">}</span><span class="sb">`</span> <span class="o">:</span> <span class="nx">action</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">createSlice</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">initialState</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="nx">reducers</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">extraReducers</span> <span class="o">=</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="kr">const</span> <span class="nx">actionKeys</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">reducers</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="kr">const</span> <span class="nx">createActionType</span> <span class="o">=</span> <span class="nx">actionTypeBuilder</span><span class="p">(</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="cm">/*
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="cm"> `createSlice` will create an action for each key:value
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="cm"> pair inside the main `reducers` property.
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="cm"> `extraReducers` does not create an action for the key:value
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="cm"> pair which allows outside actions to map to a
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="cm"> reducer inside our slice.
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"> <span class="kr">const</span> <span class="nx">reducerMap</span> <span class="o">=</span> <span class="nx">actionKeys</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">map</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="nx">map</span><span class="p">[</span><span class="nx">createActionType</span><span class="p">(</span><span class="nx">action</span><span class="p">)]</span> <span class="o">=</span> <span class="nx">reducers</span><span class="p">[</span><span class="nx">action</span><span class="p">];</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="k">return</span> <span class="nx">map</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="p">},</span> <span class="nx">extraReducers</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"> <span class="c1">// using our `createReducer` :tada:
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">reducer</span> <span class="o">=</span> <span class="nx">createReducer</span><span class="p">(</span><span class="nx">initialState</span><span class="p">,</span> <span class="nx">reducerMap</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"> <span class="c1">// this builds an object where the key is the
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"></span> <span class="c1">// actionType and the value is the corresponding
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"></span> <span class="c1">// actionCreator
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="c1"></span> <span class="kr">const</span> <span class="nx">actionMap</span> <span class="o">=</span> <span class="nx">actionKeys</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">map</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl"> <span class="kr">const</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">createActionType</span><span class="p">(</span><span class="nx">action</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"> <span class="c1">// using our `createAction` :tada:
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"></span> <span class="nx">map</span><span class="p">[</span><span class="nx">action</span><span class="p">]</span> <span class="o">=</span> <span class="nx">createAction</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"> <span class="k">return</span> <span class="nx">map</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"> <span class="p">},</span> <span class="p">{});</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"> <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"> <span class="nx">actions</span><span class="o">:</span> <span class="nx">actionMap</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"> <span class="nx">reducer</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"> <span class="nx">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><h4 id="example-usage-for-createslice"><a class="anchor" href="#example-usage-for-createslice" rel="nofollow">#</a> Example usage for <code>createSlice</code></h4>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createStore</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">"redux"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">reducer</span><span class="p">,</span> <span class="nx">actions</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">createSlice</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="nx">name</span><span class="o">:</span> <span class="s2">"todos"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> <span class="nx">initialState</span><span class="o">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="nx">reducers</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">addTodo</span><span class="o">:</span> <span class="p">(</span><span class="nx">state</span>: <span class="kt">State</span><span class="p">,</span> <span class="nx">payload</span>: <span class="kt">ToDo</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="k">return</span> <span class="p">[...</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">];</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">toggleTodo</span><span class="o">:</span> <span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">payload</span>: <span class="kt">string</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="k">return</span> <span class="nx">state</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">todo</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="nx">todo</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="k">return</span> <span class="p">{</span> <span class="p">...</span><span class="nx">todo</span><span class="p">,</span> <span class="nx">completed</span><span class="o">:</span> <span class="o">!</span><span class="nx">todo</span><span class="p">.</span><span class="nx">completed</span> <span class="p">};</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="k">return</span> <span class="nx">todo</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"> <span class="p">});</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">addTodo</span><span class="p">,</span> <span class="nx">toggleTodo</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">actions</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"> <span class="nx">addTodo</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">"1"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> <span class="nx">text</span><span class="o">:</span> <span class="s2">"build my own createAction"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"> <span class="p">}),</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="cm">{
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="cm"> type: 'todos/ADD_TODO',
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="cm"> payload: {
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="cm"> id: '1',
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="cm"> text: 'build my own createAction',
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="cm"> completed: true
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="cm"> },
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="cm">}
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1">// after this point everything works exactly
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="c1">// the same as our previous example
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createStore</span><span class="p">(</span><span class="nx">reducer</span><span class="p">,</span> <span class="p">[]);</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"> <span class="nx">addTodo</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">"1"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"> <span class="nx">text</span><span class="o">:</span> <span class="s2">"byo createAction"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"> <span class="p">}),</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl"> <span class="nx">addTodo</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">"2"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl"> <span class="nx">text</span><span class="o">:</span> <span class="s2">"byo createReducer"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">false</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl"> <span class="p">}),</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl"> <span class="nx">addTodo</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl"> <span class="nx">id</span><span class="o">:</span> <span class="s2">"3"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl"> <span class="nx">text</span><span class="o">:</span> <span class="s2">"byo createSlice"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"> <span class="nx">completed</span>: <span class="kt">false</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl"> <span class="p">}),</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="cm"> [
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="cm"> { id: '1', text: 'byo createAction', completed: true }
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="cm"> { id: '2', text: 'byo createReducer', completed: false }
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="cm"> { id: '3', text: 'byo createSlice', completed: false }
</span></span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="cm"> ]
</span></span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">toggleTodo</span><span class="p">(</span><span class="s2">"2"</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="cm"> [
</span></span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="cm"> { id: '1', text: 'byo createAction', completed: true }
</span></span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="cm"> { id: '2', text: 'byo createReducer', completed: true }
</span></span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="cm"> { id: '3', text: 'byo createSlice', completed: false }
</span></span></span><span class="line"><span class="ln">77</span><span class="cl"><span class="cm"> ]
</span></span></span><span class="line"><span class="ln">78</span><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="c1">// all of our todos are done!
</span></span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="c1"></span><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">toggleTodo</span><span class="p">(</span><span class="s2">"3"</span><span class="p">));</span>
</span></span></code></pre><h2 id="conclusion"><a class="anchor" href="#conclusion" rel="nofollow">#</a> Conclusion</h2>
<p>This article demonstrates how leveraging a few simple helper functions
significantly reduces the amount of boilerplate code required to add state and
reducer logic to your redux app. All three of these functions can be used
independently of each other. I also hope this article demystifies <code>createSlice</code>,
which is now considered the canonical way to use redux.</p>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
Learn how to use createSlice by implementing it yourselfScaling a react/redux codebase for multiple platforms2022-07-05T13:45:08Zhttps://erock.prose.sh/scaling-js-codebase-multiple-platforms<p>In the world of react and redux, there is no shortage of tutorials, to-do apps,
and how-to guides for small web applications. There's a rather steep learning
curve when trying to deploy a modern web application and when researching how to
scale and maintain a large one, I found very little discussion on the subject.</p>
<p>Contrary to what people think, react is not a framework; it's a view library.
That is its strength and also its weakness. For people looking for a
batteries-included web framework to build a single-page application, react only
satisfies the V in MVC. For small, contained applications this is an incredible
ally. React and redux don't make any assumptions about how a codebase is
organized.</p>
<p>There is no standard for how to organize a react redux application.
<a href="https://medium.com/magnetis-backstage/redux-side-effects-and-me-89c104a4b149" rel="nofollow">We cannot even settle on a side-effects middleware for it</a>.
This has left the react redux ecosystem fragmented. From
<a href="https://github.com/erikras/ducks-modular-redux" rel="nofollow">ducks</a> to rails-style layer
organization, there is no official recommendation. This lack of standardization
is not because the problem has been ignored, in fact, the official redux site
states that
<a href="https://redux.js.org/faq/code-structure" rel="nofollow">it ultimately doesn't matter how you lay out your code on disk</a>.
In this article is to show how I like to build large accplications using react
and redux.</p>
<hr>
<h2 id="inspiration"><a class="anchor" href="#inspiration" rel="nofollow">#</a> Inspiration</h2>
<p>There really are not a lot of large and open codebases to gain inspiration from.
The most notable examples I have found are
<a href="https://github.com/Automattic/wp-calypso" rel="nofollow">Automattic's calypso</a> and most
recently <a href="https://github.com/keybase/client" rel="nofollow">Keybase's client</a>.</p>
<p><a href="https://vimeo.com/43612849" rel="nofollow">Uncle Bob's Clean Architecture</a> argues that
architecture should describe intent and not implementation. The top-level source
code of a project should not look the same for every project.
<a href="https://jaysoo.ca/2016/02/28/organizing-redux-application/" rel="nofollow">Jaysoo's Organizing Redux Application</a>
goes into the details of how to implement a react/redux application using a
feature-based folder organization.</p>
<hr>
<h2 id="code-organization"><a class="anchor" href="#code-organization" rel="nofollow">#</a> Code Organization</h2>
<h3 id="monorepo"><a class="anchor" href="#monorepo" rel="nofollow">#</a> Monorepo</h3>
<p>On a recent project I was responsible for multiple platforms which include but
are not limited to: web (all major browsers), desktop (windows, mac, linux),
outlook plugin, chrome extension, and a salesforce app.</p>
<p>We decided that all that code should live under one repository. The most
important reason was for code sharing. I also felt it unnecessary and
unmaintainable to build seven separate repositories.</p>
<h3 id="a-quick-overview"><a class="anchor" href="#a-quick-overview" rel="nofollow">#</a> A quick overview</h3>
<p>I leveraged <a href="https://yarnpkg.com/lang/en/docs/workspaces/" rel="nofollow">yarn workspaces</a> to
do all the installation. Every package was located under the <code>packages</code> folder.
Each platform had its own folder for customization under the <code>platform</code> folder.
Platform specific packages would also be located under the <code>packages</code> folder.
Although if desired it would be easy to move platform specific packages under
each platform folder respectively. This made initial setup easier to handle
because all packages lived in one place.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl">plaforms/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> web/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> webpack/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> index.js
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> store.js
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> packages.js
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> cli/ <span class="c1"># same structure as web</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> salesforce/ <span class="c1"># same structure as web</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> desktop/ <span class="c1"># same structure as web</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> chrome/ <span class="c1"># same structure as web</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> outlook/ <span class="c1"># same structure as web</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">packages/
</span></span><span class="line"><span class="ln">13</span><span class="cl"> login/
</span></span><span class="line"><span class="ln">14</span><span class="cl"> packages.json
</span></span><span class="line"><span class="ln">15</span><span class="cl"> index.js
</span></span><span class="line"><span class="ln">16</span><span class="cl"> action-creators.js
</span></span><span class="line"><span class="ln">17</span><span class="cl"> action-types.js
</span></span><span class="line"><span class="ln">18</span><span class="cl"> effects.js
</span></span><span class="line"><span class="ln">19</span><span class="cl"> sagas.js
</span></span><span class="line"><span class="ln">20</span><span class="cl"> reducers.js
</span></span><span class="line"><span class="ln">21</span><span class="cl"> selectors.js
</span></span><span class="line"><span class="ln">22</span><span class="cl"> logout/ <span class="c1"># same structure as login</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"> messages/ <span class="c1"># same structure as login</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"> web-login/ <span class="c1"># same structure as login</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"> cli-login/ <span class="c1"># same structure as login</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">packages.json
</span></span></code></pre><h3 id="feature-based-folder-organization"><a class="anchor" href="#feature-based-folder-organization" rel="nofollow">#</a> Feature-based folder organization</h3>
<p>There are two predominate ways to organize code: layer-based and feature-based
folder organization. When building an application, the top level source code
should not look the same for every single application. The rails-style MVC
folder structure (layer-based) muddles each feature together into one
application instead of treating them as their own entities. Building a new
feature in isolation is more difficult when each component of a feature needs to
join the other features. Using a feature-based approach the new feature can be
built in isolation, away from everything else and then "hooked up" later when
it's finished.</p>
<p>Layer-based</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl">src/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"> models/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> login.js
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> logout.js
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"> views/
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> login.js
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> logout.js
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> controllers/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> login.js
</span></span><span class="line"><span class="ln">10</span><span class="cl"> logout.js
</span></span></code></pre><p>Feature-based</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl">src/
</span></span><span class="line"><span class="ln">2</span><span class="cl"> login/
</span></span><span class="line"><span class="ln">3</span><span class="cl"> model.js
</span></span><span class="line"><span class="ln">4</span><span class="cl"> view.js
</span></span><span class="line"><span class="ln">5</span><span class="cl"> controller.js
</span></span><span class="line"><span class="ln">6</span><span class="cl"> logout/
</span></span><span class="line"><span class="ln">7</span><span class="cl"> model.js
</span></span><span class="line"><span class="ln">8</span><span class="cl"> view.js
</span></span><span class="line"><span class="ln">9</span><span class="cl"> controller.js
</span></span></code></pre><h3 id="every-feature-is-an-npm-package"><a class="anchor" href="#every-feature-is-an-npm-package" rel="nofollow">#</a> Every feature is an npm package</h3>
<p>This was a recent development that has been successful for us. We leveraged
<a href="https://yarnpkg.com/blog/2017/08/02/introducing-workspaces/" rel="nofollow">yarn workspaces</a>
to manage dependencies between features. By developing each feature as a
package, it allowed us to think of each feature as its own individual unit. It
really helps decouple a feature from a particular application or platform. Using
a layer-based approach, it's really easy to lose site that these features are
discrete contributions to an application.</p>
<h4 id="absolute-imports"><a class="anchor" href="#absolute-imports" rel="nofollow">#</a> Absolute imports</h4>
<p>It was a nightmare moving code around when using relative imports for all of our
internal dependencies. The weight of each file being moved multiplies by the
number of thing depending on it. Absolute imports were a really great feature to
leverage. The larger the app, the more common it is to see absolute imports.</p>
<h4 id="lint-rules-around-inter-dependencies"><a class="anchor" href="#lint-rules-around-inter-dependencies" rel="nofollow">#</a> Lint rules around inter-dependencies</h4>
<p>One of the best things about absolute imports was the lint tooling that could be
built. We used a namespace <code>@company/<package></code> for our imports so it was
relatively easy to build lint rules around that consistent naming.</p>
<h4 id="strict-package-boundaries"><a class="anchor" href="#strict-package-boundaries" rel="nofollow">#</a> Strict package boundaries</h4>
<p>This was another key to scaling a codebase. Each package had to subscribe to a
consistent API structure. It forces the developer to think about how packages
are interacting with each other and creates an environment where there is only
one API that each package is required to maintain.</p>
<p>For example, if we allowed any package to import another package, it's difficult
to understand what happens when a developer decides to move files, folders
around. For example when building a package, let's say we want to change the
file <code>utils</code> to <code>helpers</code>. By allowing a package to import <code>utils</code> directly, we
inadvertantly broke the API. Another example is when a package is really simple
and could be encapsulated inside one file. As long as the package has an
<code>index.js</code> file and it exports all of the components that another package needs,
it doesn't matter how the package is actually organized. It's important for a
large codebase to have some sort of internal consistency, however, I found
having some flexbility allows to fit an organization that matches the needs of
the feature.</p>
<p>Another reason why strict module boundaries is important is to simplify the
dependency tree. When reaching into a package to grab a submodule, the
dependency graph treats that submodule as a full-blown package. When creating
module boundaries and a pacakge imports another package, it imports the entire
package. This simplifies the dependency graph and makes it easier to understand.
<a href="http://engineering.khanacademy.org/posts/python-refactor-3.htm" rel="nofollow">Here's an article on the important of dependency graph</a>.</p>
<p>Each package exports the following:</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nx">reducers</span><span class="o">:</span> <span class="nb">Object</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"> <span class="nx">sagas</span><span class="o">:</span> <span class="nb">Object</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"> <span class="nx">actionCreators</span><span class="o">:</span> <span class="nb">Object</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"> <span class="nx">actionTypes</span><span class="o">:</span> <span class="nb">Object</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"> <span class="nx">selectors</span><span class="o">:</span> <span class="nb">Object</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"> <span class="nx">utils</span><span class="o">:</span> <span class="nb">Object</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">}</span>
</span></span></code></pre><p>Creating this consistent API provided opportunities ripe for tooling.</p>
<p>One of the most important rules was the <code>module-boundary</code> lint rule. This
prohibited any package from importing a sibling package's submodules directly.
They must always use the <code>index.js</code> file to get what they want.</p>
<p>For example:</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// bad and a lint rule will prevent this
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">fetchNewsArticle</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">"@company/news/action-creators"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// good
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">actionCreators</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">"@company/news"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">fetchNewsArticle</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">actionCreators</span><span class="p">;</span>
</span></span></code></pre><p>This setup came at a cost. Import statements became more verbose as a result of
this change.</p>
<p>Probably one of the greatest benefits to this structure was circular
dependencies. I know that sounds insane, who would actually want circular
dependencies in their codebase? Especially since every circular dependency that
was introduced caused an ominous runtime error: <code>cannot find X of undefined</code>.
I'll go into more details about why these errors were favorable later.</p>
<h4 id="a-package-is-a-package-is-a-package"><a class="anchor" href="#a-package-is-a-package-is-a-package" rel="nofollow">#</a> A package is a package is a package</h4>
<p>Another huge benefit to our "feature-based, everything is an npm package" setup
was the fact that every package was setup the same way. When I onboard new
developers, I usually ask them to add a new feature. What this means is they get
to build their own package that does something new. This made them understand
exactly how a package works and they have plenty of examples on how to build
them. It really reduced the barrier to entry into a massive codebase and was a
great ally when trying to introduce people into a large codebase. With this
architecture, I created a scalable system that anyone can understand.</p>
<h3 id="support-tools"><a class="anchor" href="#support-tools" rel="nofollow">#</a> Support tools</h3>
<p>Because of how tedious it can be to maintain a list of internal dependencies for
each package, not to mention creating <code>package.json</code> files for each feature, I
outsourced it to tooling. This was a lot easier than I originally thought.</p>
<p>I leveraged a javascript AST to detect all import statements that matched
<code>@company/<package></code>. This built the list I needed for each package. Then all I
did was hook that script up to our test runner and it would fail a) anytime a
dependency was not inside the package.json or b) whenever there was a dependency
inside the package.json that was no longer detected in the code. I then built an
automatic fixer to update those package.json files that have changed.</p>
<p>Another huge benefit to having internal dependencies within each package was the
ability to quickly look at a <code>package.json</code> file and see all of its
dependencies. This allowed us to reflect on the dependency graph on a
per-package basis.</p>
<p>Making our packages npm install-able was easy after this and I don't have to do
anything to maintain those package.json files. Easy!</p>
<p>I wrote the support tools into a CLI
<a href="https://github.com/neurosnap/lint-workspaces" rel="nofollow">lint-workspaces</a></p>
<h3 id="package-loader"><a class="anchor" href="#package-loader" rel="nofollow">#</a> Package loader</h3>
<p>Since I had a consistent API for all of our packages, each platform was able to
load whatever dependencies it needed upfront. Each package exported a <code>reducers</code>
object and a <code>sagas</code> object. Each platform then simply had to use one of our
helper functions to automatically load our reducers and sagas.</p>
<p>So inside each platform was a <code>packages.js</code> file which loaded all reducers and
sagas that were required by the platform and the packages it wanted to use.</p>
<p>By registering the packages, it made it very clear in each platform what kind of
state shape they required and what kind of sagas would be triggered.</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// packages.js
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="nx">use</span> <span class="nx">from</span> <span class="s2">"redux-package-loader"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">import</span> <span class="nx">sagaCreator</span> <span class="nx">from</span> <span class="s2">"redux-saga-creator"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kr">const</span> <span class="nx">packages</span> <span class="o">=</span> <span class="nx">use</span><span class="p">([</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="nx">require</span><span class="p">(</span><span class="s2">"@company/auth"</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="nx">require</span><span class="p">(</span><span class="s2">"@company/news"</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">require</span><span class="p">(</span><span class="s2">"@company/payment"</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">]);</span> <span class="c1">// `use` simply combines all package objects into one large object
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kr">const</span> <span class="nx">rootReducer</span> <span class="o">=</span> <span class="nx">combineReducers</span><span class="p">(</span><span class="nx">packages</span><span class="p">.</span><span class="nx">reducers</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kr">const</span> <span class="nx">rootSaga</span> <span class="o">=</span> <span class="nx">sagaCreator</span><span class="p">(</span><span class="nx">packages</span><span class="p">.</span><span class="nx">sagas</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kr">export</span> <span class="p">{</span> <span class="nx">rootReducer</span><span class="p">,</span> <span class="nx">rootSaga</span> <span class="p">};</span>
</span></span></code></pre><pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// store.js
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">applyMiddleware</span><span class="p">,</span> <span class="nx">createStore</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">"redux"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">import</span> <span class="nx">createSagaMiddleware</span> <span class="nx">from</span> <span class="s2">"redux-saga"</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">({</span> <span class="nx">initState</span><span class="p">,</span> <span class="nx">rootReducer</span><span class="p">,</span> <span class="nx">rootSaga</span> <span class="p">})</span> <span class="p">=></span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> <span class="kr">const</span> <span class="nx">sagaMiddleware</span> <span class="o">=</span> <span class="nx">createSagaMiddleware</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"> <span class="kr">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createStore</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> <span class="nx">rootReducer</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"> <span class="nx">initState</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"> <span class="nx">applyMiddleware</span><span class="p">(</span><span class="nx">sagaMiddleware</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"> <span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="nx">sagaMiddleware</span><span class="p">.</span><span class="nx">run</span><span class="p">(</span><span class="nx">rootSaga</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="k">return</span> <span class="nx">store</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">};</span>
</span></span></code></pre><pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// index.js
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">Provider</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'react-redux'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">render</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'react-dom'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kr">import</span> <span class="nx">createState</span> <span class="nx">from</span> <span class="s1">'./store'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">rootReducer</span><span class="p">,</span> <span class="nx">rootSaga</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">'./packages'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kr">import</span> <span class="nx">App</span> <span class="nx">from</span> <span class="s1">'./components/app'</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kr">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createState</span><span class="p">({</span> <span class="nx">rootReducer</span><span class="p">,</span> <span class="nx">rootSaga</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">render</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"> <span class="o"><</span><span class="nx">Provider</span> <span class="nx">store</span><span class="o">=</span><span class="p">{</span><span class="nx">store</span><span class="p">}</span><span class="o">></span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"> <span class="o"><</span><span class="nx">App</span> <span class="o">/></span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"> <span class="o"><</span><span class="err">/Prodiver>,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">);</span>
</span></span></code></pre><p>I have extracted the package loader code and moved it into its own npm package
<a href="https://github.com/neurosnap/redux-package-loader" rel="nofollow">redux-package-loader</a>.</p>
<p>I also wrote a saga creator helper
<a href="https://github.com/neurosnap/redux-saga-creator" rel="nofollow">redux-saga-creator</a></p>
<h3 id="circular-dependencies"><a class="anchor" href="#circular-dependencies" rel="nofollow">#</a> Circular dependencies</h3>
<p>Circular dependencies were a very important signal when developing. Whenever I
came across a circular dependency, some feature was organized improperly. It was
a code smell, something I need to get around not by ignoring it, not by trying
to force the build system handle these nefarious errors, but by facing it head
on from an organizational point of view.</p>
<p>One of the :key: topics I learned about along the way was
<a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph" rel="nofollow">Directed acyclic graph</a></p>
<p>I'll explain by example, give the following packages:</p>
<pre><code>packages/
mailbox/
thread/
message/
</code></pre>
<p>I would regularly run into situations where pieces of code within the <code>mailbox</code>
package would want to access functionality inside the <code>thread</code> package. This
would usually cause a circular dependency. Why? Mailboxes shouldn't need the
concept of a thread to function. However, <code>thread</code> needs to understand the
concept of a mailbox to function. This is where DAG came into play. I needed to
ensure that any piece of code inside <code>mailbox</code> that needed thread actually
didn't belong inside <code>mailbox</code> at all. A lot of the time what it really meant
was I should simply move that functionality into <code>thread</code>. Most of the time
making this change made a lot of sense from a dependency point of view, but also
an organizational one. When moving functionality into <code>thread</code> did not work or
make any sense, a third package was built that used both <code>mailbox</code> and <code>thread</code>.</p>
<h4 id="cannot-find-x-of-undefined"><a class="anchor" href="#cannot-find-x-of-undefined" rel="nofollow">#</a> Cannot find X of <code>undefined</code></h4>
<p>For whatever reason, the build system (webpack, babel) had no problem resolving
circular dependencies even though at runtime I would get this terribly vague
error <code>cannot find X of 'undefined'</code>. I would spend hours trying to track down
what was wrong because it was clear that this was a circular dependency issue.
Even when I knew it was a dependency issue, I didn't know what caused it. It was
a terrible developer experience and almost made me give up completely on strict
package boundary setup.</p>
<h4 id="tools-to-help-detect-them"><a class="anchor" href="#tools-to-help-detect-them" rel="nofollow">#</a> Tools to help detect them</h4>
<p>Originally the tool that helped detect circular dependency was
<a href="https://github.com/pahen/madge" rel="nofollow">madge</a>. It was a script that I would run and it
would normally indicate what would be the dependency issue.</p>
<p>Once I moved to yarn workspaces however, this tool failed to work properly.
Thankfully, because every package had an up-to-date <code>package.json</code> file with all
inter-dependencies mapped out, it was trivial for to traverse those dependencies
to detect circular issues.</p>
<hr>
<h2 id="an-open-example"><a class="anchor" href="#an-open-example" rel="nofollow">#</a> An open example</h2>
<p>The project codebase is not publicly accessible but if you want to see some
version of it, you can go to my personal project
<a href="https://github.com/neurosnap/youhood" rel="nofollow">youhood</a>. It is not a 1:1 clone of the
setup, primarily because I am using TypeScript for my personal project and yarn
workspaces was not necessary to accomplish what I wanted, but it organizes the
code in the exact same way by leveraging <code>redux-package-loader</code>.</p>
<hr>
<h2 id="its-not-perfect"><a class="anchor" href="#its-not-perfect" rel="nofollow">#</a> It's not perfect</h2>
<p>There are a few issues when developing an application like this.</p>
<ul>
<li>Importing a package brings everything with it</li>
<li>Import statements are more verbose</li>
</ul>
<p>In a follow up blog article I will go into more detail about these issues.</p>
<hr>
<p>This code organization could build multiple platforms using most of the same
code. As with most things in life, this was not a silver bullet. They :key:
take-aways were:</p>
<ul>
<li>Feature-based organization scaled really well</li>
<li>A consistent package interface allowed for tooling</li>
<li>Force developers to think about dependency graph</li>
</ul>
<hr>
<h2 id="references"><a class="anchor" href="#references" rel="nofollow">#</a> References</h2>
<ul>
<li><a href="https://github.com/neurosnap/redux-package-loader" rel="nofollow">redux-package-loader</a></li>
<li><a href="https://github.com/neurosnap/lint-workspaces" rel="nofollow">redux-saga-creator</a></li>
<li><a href="https://github.com/neurosnap/lint-workspaces" rel="nofollow">lint-workspaces</a></li>
<li><a href="https://github.com/neurosnap/tslint-package-config" rel="nofollow">tslint-package-config</a></li>
<li><a href="https://github.com/neurosnap/youhood" rel="nofollow">youhood</a></li>
</ul>
<p>Have a comment? Start a discussion by sending an email to
<a href="mailto:~erock/public-inbox@lists.sr.ht" rel="nofollow">my public inbox</a>.</p>
A brief intro on building a large react web app