{"id":21305,"date":"2023-05-04T07:30:07","date_gmt":"2023-05-04T14:30:07","guid":{"rendered":"https:\/\/www.jamasoftware.com\/?p=21305"},"modified":"2024-01-18T00:21:45","modified_gmt":"2024-01-18T08:21:45","slug":"lets-write-redux","status":"publish","type":"post","link":"https:\/\/www.jamasoftware.com\/legacy\/blog\/2023\/05\/04\/lets-write-redux\/","title":{"rendered":"Let&#8217;s Write Redux!"},"content":{"rendered":"<p><img decoding=\"async\" class=\"aligncenter wp-image-68645 size-full\" src=\"https:\/\/www.jamasoftware.com\/media\/2023\/05\/2023-5-4-lets-write-redux.jpg\" alt=\"Redux\" width=\"1024\" height=\"512\" srcset=\"https:\/\/www.jamasoftware.com\/legacy\/media\/2023\/05\/2023-5-4-lets-write-redux.jpg 1024w, https:\/\/www.jamasoftware.com\/legacy\/media\/2023\/05\/2023-5-4-lets-write-redux-300x150.jpg 300w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<blockquote class=\"pullquote full\"><p>&#8220;What I cannot create, I do not understand.&#8221;<\/p>\n<p><cite>Richard Feynman<\/cite><\/p><\/blockquote>\n<p>Redux is pretty simple. You have action creators, actions, reducers, and a store. What&#8217;s not so simple is figuring out how to put everything together in the best or most &#8220;correct&#8221; way. In this blog, we begin by explaining the motivation behind using Redux and highlight its benefits, such as predictable state management and improved application performance. It then delves into the core concepts of Redux, including actions, reducers, and the store, providing a step-by-step guide on how to implement Redux in a JavaScript application. We will emphasize the importance of understanding Redux&#8217;s underlying principles and showcases code examples to illustrate its usage.<\/p>\n<p>To rewrite Redux, we used a wonderful article by Lin Clark as a reference point, as well as the <a href=\"https:\/\/github.com\/reactjs\/redux\">Redux codebase itself<\/a>, and of course, the <a href=\"http:\/\/redux.js.org\/\">Redux docs<\/a>.<\/p>\n<div class=\"callout\">\n<p>You may note we&#8217;re using traditional pre-ES6 Javascript throughout this article. It&#8217;s because everyone who knows Javascript knows pre-ES6 JS, and we want to make sure we don&#8217;t lose anyone because of syntax unfamiliarity.<\/p>\n<\/div>\n<h2>The Store<\/h2>\n<p>Redux, as is the same with any data layer, starts with a place to store information. Redux, by definition of the <a href=\"http:\/\/redux.js.org\/docs\/introduction\/ThreePrinciples.html#single-source-of-truth\">first principle<\/a> of Redux, <em>is a singular shared data store<\/em>, described by its documentation as a &#8220;Single source of truth&#8221;, so we&#8217;ll start by making the store a singleton:<\/p>\n<pre><code class=\"js\">var store;\r\n\r\nfunction getInstance() { \r\n if (!store) store = createStore();\r\n return store;\r\n}\r\n\r\nfunction createStore() { \r\n return {}; \r\n}\r\n\r\nmodule.exports = getInstance();\r\n<\/code><\/pre>\n<h3>The dispatcher<\/h3>\n<p>The next principle is that the state of the store can only change in one way: through the dispatching of actions. So let&#8217;s go ahead and write a dispatcher.<\/p>\n<p>However, in order to update state in this dispatcher, we&#8217;re going to have to <em>have<\/em> state to begin with, so let&#8217;s create a simple object that contains our current state.<\/p>\n<pre><code class=\"js\">function createStore() { \r\n var currentState = {}; \r\n}<\/code><\/pre>\n<p>Also, to dispatch an action, we need a reducer to dispatch it to. Let&#8217;s create a default one for now. A reducer receives the current state and an action and then returns a new version of the state based on what the action dictates:<\/p>\n<pre><code class=\"js\">function createStore() { \r\n var currentState = {}; \r\n\r\n var currentReducer = function(state, action) { \r\n  return state; \r\n } \r\n}<\/code><\/pre>\n<p>This is just a default function to keep the app from crashing until we formally assign reducers, so we&#8217;re going to go ahead and just return the state as is. Essentially a &#8220;noop&#8221;.<\/p>\n<p>The store is going to need a way to notify interested parties that an update has been dispatched, so let&#8217;s create an array to house subscribers:<\/p>\n<pre><code class=\"js\">function createStore() { \r\n var currentState = {}; \r\n \r\n var currentReducer = function(state, action) { \r\n  return state; \r\n } \r\n \r\n var subscribers = []; \r\n}<\/code><\/pre>\n<p>Cool! OK, now we can finally put that dispatcher together. As we said above, <em>actions<\/em> are handed to reducers along with <em>state<\/em>, and we get a <em>new state<\/em> back from the reducer. If we want to retain the original state before the change for comparison purposes, it probably makes sense to temporarily store it.<\/p>\n<p>Since an <em>action<\/em> is <em>dispatched<\/em>, we can safely assume the parameter a dispatcher receives is an action.<\/p>\n<pre><code class=\"js\">function createStore() { \r\n var currentState = {}; \r\n\r\n var currentReducer = function(state, action) { \r\n  return state; \r\n } \r\n\r\n var subscribers = [];\r\n\r\n function dispatch(action) {\r\n  var prevState = currentState;\r\n }\r\n\r\n return {\r\n  dispatch: dispatch\r\n };\r\n}<\/code><\/pre>\n<p>We also have to expose the <code>dispatch<\/code> function so it can actually be used when the store is imported. Kind of important.<\/p>\n<p>So, we&#8217;ve created a reference to the old state. We now have a choice: we could either leave it to reducers to copy the state and return it, or we can do it for them. Since receiving a changed copy of the current state is part of the philosophical basis of Redux, we&#8217;re going to go ahead and just hand the reducers a copy to begin with.<\/p>\n<pre><code class=\"js\">function createStore() { \r\n var currentState = {}; \r\n\r\n var currentReducer = function(state, action) { \r\n  return state; \r\n } \r\n\r\n var subscribers = [];\r\n\r\n function dispatch(action) {\r\n  var prevState = currentState;\r\n  currentState = currentReducer(cloneDeep(currentState), action);\r\n }\r\n\r\n return {\r\n  dispatch: dispatch\r\n };\r\n}<\/code><\/pre>\n<p>We hand a copy of the current state and the action to the currentReducer, which uses the action to figure out what to do with the state. What is returned is a changed version of the copied state, which we then use to update the state. Also, we&#8217;re using a generic <code>cloneDeep<\/code>implementation (in this case, we used lodash&#8217;s) to handle copying the state completely. Simply using <code>Object.assign<\/code> wouldn&#8217;t be suitable because it retains references to objects contained by the base level object properties.<\/p>\n<p>Now that we have this updated state, we need to alert any part of the app that cares. That&#8217;s where the subscribers come in. We simply call to each subscribing function and hand them the current state and also the previous state, in case whoever&#8217;s subscribed wants to do delta comparisons:<\/p>\n<pre><code class=\"js\">function createStore() { \r\n var currentState = {}; \r\n\r\n var currentReducer = function(state, action) { \r\n  return state; \r\n } \r\n\r\n var subscribers = []; \r\n\r\n function dispatch(action) {\r\n  var prevState = currentState;\r\n  currentState = currentReducer(cloneDeep(currentState), action);\r\n  subscribers.forEach(function(subscriber){\r\n   subscriber(currentState, prevState);\r\n  });\r\n }\r\n\r\n return {\r\n  dispatch: dispatch\r\n };\r\n}<\/code><\/pre>\n<p>Of course, none of this really does any good with just that default noop reducer. What we need is the ability to add reducers, as well.<\/p>\n<hr \/>\n<h4><span style=\"color: #ff6600;\"><strong>RELATED: <\/strong><span style=\"color: #0000ff;\"><a style=\"color: #0000ff;\" href=\"\/webinar\/new-research-findings-the-impact-of-live-traceability-on-the-digital-thread\" target=\"_blank\" rel=\"\u201cnoopener noopener\"> New Research Findings: The Impact of Live Traceability\u2122 on the Digital Thread<\/a><\/span><\/span><\/h4>\n<hr \/>\n<h3>Adding Reducers<\/h3>\n<p>In order to develop an appropriate reducer-adding API, let&#8217;s revisit what a reducer is, and how we might expect reducers to be used.<\/p>\n<p>In the <a href=\"http:\/\/redux.js.org\/docs\/introduction\/ThreePrinciples.html\">Three Principles<\/a> section of Redux&#8217;s documentation, we can find this philosophy:<\/p>\n<blockquote class=\"pullquote full\"><p>&#8220;To specify how the state tree is transformed by actions, you write pure reducers.&#8221;<\/p><\/blockquote>\n<p>So what we want to accommodate is something that looks like a state tree, but where the properties of the state are assigned functions that purely change their state.<\/p>\n<pre><code class=\"js\">{ \r\n stateProperty1: function(state, action) { \r\n  \/\/ does something with state and then returns it\r\n }, \r\n stateProperty2: function(state, action) { \r\n  \/\/ same \r\n }, ... \r\n}<\/code><\/pre>\n<p>Yeah, that looks about right. We want to take this state tree object and run each of its reducer functions every time an action is dispatched.<\/p>\n<p>We have <code>currentReducer<\/code> defined in the scope, so let&#8217;s just create a new function and assign it to that variable. This function will take the pure reducers we passed to it in the state tree object, and run each one, returning the outcome of the function to the key it was assigned.<\/p>\n<pre><code class=\"js\">function createStore() { \r\n var currentReducer = function(state, action) { \r\n  return state; \r\n } ...\r\n\r\n function addReducers(reducers) {\r\n  currentReducer = function(state, action) {\r\n   var cumulativeState = {};\r\n   \r\n   for (key in reducers) {\r\n    cumulativeState[key] = reducers[key](state[key], action);\r\n   }\r\n  \r\n   return cumulativeState;\r\n  }\r\n }\r\n}<\/code><\/pre>\n<p>Something to note here: we&#8217;re only ever handing a subsection of the state to each reducer, keyed from its associated property name. This helps simplify the reducer API and also keeps us from accidentally changing other state areas of the global state. Your reducers should only be concerned with their own particular state, but that doesn&#8217;t preclude your reducers from taking advantage of other properties in the store.<\/p>\n<p>As an example, think of a list of data, let&#8217;s say with a name &#8220;todoItems&#8221;. Now consider ways you might sort that data: by completed tasks, by date created, etc. You can store the way you sort that data into separate reducers (byCompleted and byCreated, for example) that contain ordered lists of IDs from the todoItems data, and associate them when you go to show them in the UI. Using this model, you can even <em>reuse<\/em> the byCreated property for other types of data aside from todoItems! <a href=\"http:\/\/redux.js.org\/docs\/basics\/Reducers.html#note-on-relationships\">This is definitely a pattern recommended in the Redux docs<\/a>.<\/p>\n<p>Now, this is fine if we add just one single set of reducers to the store, but in an app of any substantive size, that simply won&#8217;t be the case. So we should be able to accommodate different portions of the app adding their own reducers. And we should also try to be performant about it; that is, we shouldn&#8217;t run the same reducers twice.<\/p>\n<pre><code class=\"js\">\/\/ State tree 1 \r\n{ \r\n visible: function(state, action) { \r\n  \/\/ Manage visibility state \r\n } ... \r\n}\r\n\/\/ State tree 2\r\n{ \r\n visible: function(state, action) { \r\n  \/\/ Manage visibility state (should be the same function as above) \r\n } ... \r\n}<\/code><\/pre>\n<p>In the above example, you might imagine two separate UI components having, say, a visibility reducer that manages whether something can be seen or not. Why run that same exact reducer twice? The answer is &#8220;that would be silly&#8221;. We should make sure that we collapse by key name for performance reasons, since all reducers are run each time an action is dispatched.<\/p>\n<p>So keeping in mind these two important factors &#8212; ability to ad-hoc add reducers and not adding repetitive reducers &#8212; we arrive to the conclusion that we should add another scoped variable that houses all reducers added to date.<\/p>\n<pre><code class=\"js\">... \r\nfunction createStore() { \r\n ... \r\n var currentReducerSet = {};\r\n\r\n function addReducers(reducers) {\r\n  currentReducerSet = Object.assign(currentReducerSet, reducers);\r\n\r\n  currentReducer = function(state, action) {\r\n   var cumulativeState = {};\r\n\r\n   for (key in currentReducerSet) {\r\n    cumulativeState[key] = currentReducerSet[key](state[key], action);\r\n   }\r\n \r\n   return cumulativeState;\r\n  }\r\n\r\n }\r\n ...\r\n}\r\n...<\/code><\/pre>\n<p>The var <code>currentReducerSet<\/code> is combined with whatever reducers are passed, and duplicate keys are collapsed. We needn&#8217;t worry about &#8220;losing&#8221; a reducer because two reducers will both be the same if they have the same key name. Why is this?<\/p>\n<p>To reiterate, <em>a state tree is a set of key-associated pure reducer functions<\/em>. A state tree property and a reducer have a 1:1 relationship. There should never be two different reducer functions associated with the same key.<\/p>\n<p>This should hopefully illuminate for you exactly what is expected of reducers: to be a sort of behavioral definition of a specific property. If we have a &#8220;loading&#8221; property, what we&#8217;re saying with my reducer is that &#8220;this loading property should respond to this set specific actions in these particular ways&#8221;. We can either directly specify whether something is loading &#8212; think action name &#8220;START_LOADING<em>&#8220;<\/em> &#8212; or we can use it to increment the number of things that are loading by having it respond to action names of actions that we know are asynchronous, such as for instance <em>&#8220;<\/em>LOAD_REMOTE_ITEMS_BEGIN&#8221; and &#8220;LOAD_REMOTE<em>_<\/em>ITEMS_END&#8221;.<\/p>\n<p>Let&#8217;s fulfill a few more requirements of this API. We need to be able to add and remove subscribers. Easy:<\/p>\n<pre><code class=\"js\">function createStore() { \r\n var subscribers = []; \r\n ... \r\n\r\n function subscribe(fn) { \r\n  subscribers.push(fn); \r\n }\r\n\r\n function unsubscribe(fn) {\r\n  subscribers.splice(subscribers.indexOf(fn), 1);\r\n }\r\n\r\n return {\r\n  ...\r\n  subscribe: subscribe,\r\n  unsubscribe: unsubscribe\r\n };\r\n}<\/code><\/pre>\n<p>And we need to be able to provide the state when someone asks for it. And we should provide it in a safe way, so we&#8217;re going to only provide a copy of it. As above, we&#8217;re using a <code>cloneDeep<\/code> function to handle this so someone can&#8217;t accidentally mutate the original state, because in Javascript, as we know, if someone changes the value of a reference in the state object, it will change the store state.<\/p>\n<pre><code class=\"js\">function createStore() { \r\n ... \r\n\r\n function getState() { \r\n  return cloneDeep(currentState); \r\n }\r\n\r\n return {\r\n  ...\r\n  getState: getState\r\n };\r\n}<\/code><\/pre>\n<p>And that&#8217;s it for creating Redux! At this point, you should have everything you need to be able to have your app handle actions and mutate state in a stable way, the core fundamental ideas behind Redux.<\/p>\n<p>Let&#8217;s take a look at the whole thing (with the lodash library):<\/p>\n<pre><code class=\"js\">var _ = require('lodash'); \r\nvar globalStore;\r\n\r\nfunction getInstance(){ \r\n if (!globalStore) globalStore = createStore();\r\n return globalStore;\r\n}\r\n\r\nfunction createStore() { \r\n var currentState = {}; \r\n var subscribers = []; \r\n var currentReducerSet = {}; \r\n currentReducer = function(state, action) { \r\n  return state; \r\n };\r\n \r\n function dispatch(action) {\r\n  var prevState = currentState;\r\n  currentState = currentReducer(_.cloneDeep(currentState), action);\r\n  subscribers.forEach(function(subscriber){\r\n   subscriber(currentState, prevState);\r\n  });\r\n }\r\n \r\n function addReducers(reducers) {\r\n  currentReducerSet = _.assign(currentReducerSet, reducers);\r\n  currentReducer = function(state, action) {\r\n   var ret = {};\r\n   _.each(currentReducerSet, function(reducer, key) {\r\n    ret[key] = reducer(state[key], action);\r\n   });\r\n   return ret;\r\n  };\r\n }\r\n\t\r\n function subscribe(fn) {\r\n  subscribers.push(fn);\r\n }\r\n\t\r\n function unsubscribe(fn) {\r\n  subscribers.splice(subscribers.indexOf(fn), 1);\r\n }\r\n\t\r\n function getState() {\r\n  return _.cloneDeep(currentState);\r\n }\r\n\t\r\n return {\r\n  addReducers,\r\n  dispatch,\r\n  subscribe,\r\n  unsubscribe,\r\n  getState\r\n };\r\n}\r\nmodule.exports = getInstance();<\/code><\/pre>\n<h2>So what did we learn by rewriting Redux?<\/h2>\n<p>We learned a few valuable things in this experience:<\/p>\n<ol>\n<li>We must protect and stabilize the state of the store. The only way a user should be able to mutate state is through actions.<\/li>\n<li>Reducers are pure functions in a state tree. Your app&#8217;s state properties are each represented by a function that provides updates to their state. Each reducer is unique to each state property and vice versa.<\/li>\n<li>The store is singular and contains the entire state of the app. When we use it this way, we can track each and every change to the state of the app.<\/li>\n<li>Reducers can be thought of as behavioral definitions of state tree properties.<\/li>\n<\/ol>\n<hr \/>\n<h4><span style=\"color: #ff6600;\"><strong>RELATED: <\/strong><span style=\"color: #0000ff;\"><a style=\"color: #0000ff;\" href=\"\/customer-story\/leading-quantum-computing-company-ionq-selects-jama-connect\" target=\"_blank\" rel=\"\u201cnoopener noopener\">Leading Quantum Computing Company, IonQ, Selects Jama Connect<sup>\u00ae<\/sup> to Decrease Review Cycles, Reduce Rework<\/a><\/span><\/span><\/h4>\n<hr \/>\n<h2>Bonus section: a React adapter<\/h2>\n<p>Having the store is nice, but you&#8217;re probably going to want to use it with a framework. React is an obvious choice, as Redux was created to implement Flux, a core principle data architecture of React. So let&#8217;s do that too!<\/p>\n<p>You know what would be cool? Making it a higher-order component, or HOC as you&#8217;ll sometimes see them called. We pass an HOC a component, and it creates a new component out of it. And it is also able to be infinitely nested, that is, HOCs should be able to be nested within each other and still function appropriately. So let&#8217;s start with that basis:<\/p>\n<p><em>Note: Going to switch to ES6 now, because it provides us with class extension, which we&#8217;ll need to be able to extend <code>React.Component<\/code>.<\/em><\/p>\n<pre><code class=\"js\">import React from 'react';\r\nexport default function StoreContainer(Component, reducers) { \r\n\treturn class extends React.Component { }\r\n}<\/code><\/pre>\n<p>When we use StoreContainer, we pass in the Component class &#8212; either created with <code>React.createClass<\/code> or <code>React.Component<\/code> &#8212; as the first parameter, and then a reducer state tree like the one we created up above:<\/p>\n<pre><code class=\"js\">\/\/ Example of StoreContainer usage \r\nimport StoreContainer from 'StoreContainer'; \r\nimport { myReducer1, myReducer2 } from 'MyReducers';\r\n\r\nStoreContainer(MyComponent, { \r\n myReducer1, \r\n myReducer2\r\n});<\/code><\/pre>\n<p>Cool. So now we have a class being created and receiving the original component class and an object containing property-mapped reducers.<\/p>\n<p>So, in order to actually make this component work, we&#8217;re going to have to do a few bookkeeping tasks:<\/p>\n<ol>\n<li>Get the initial store state<\/li>\n<li>Bind a subscriber to the component&#8217;s <code>setState<\/code> method<\/li>\n<li>Add the reducers to the store<\/li>\n<\/ol>\n<p>We can bootstrap these tasks in the constructor lifecycle method of the Component. So let&#8217;s start with getting the initial state.<\/p>\n<pre><code class=\"js\">... \r\nexport default function StoreContainer(Component, reducers) { \r\n return class extends React.Component { \r\n \r\n  constructor() { \r\n   super(props); \r\n   \/\/ We have to call this to create the initial React \r\n   \/\/ component and get a `this` value to work with \r\n   this.state = store.getState(); \r\n  } \r\n\r\n } \r\n}<\/code><\/pre>\n<p>Next, we want to subscribe the component&#8217;s <code>setState<\/code> method to the store. This makes the most sense because setting state on the component will then set off the top-down changes the component will broadcast, as we&#8217;d want in the Flux model.<\/p>\n<p>We can&#8217;t, however, simply send <code>this.setState<\/code> to the <code>subscribe<\/code> method of the store &#8212; their parameters don&#8217;t line up. The store wants to send new and old state, and the <code>setState<\/code> method only accepts a function as the second parameter.<\/p>\n<p>So to solve this, we&#8217;ll just create a marshalling function to handle it:<\/p>\n<pre><code class=\"js\">... \r\nimport store from '.\/Store';\r\n\r\nfunction subscriber(currentState, previousState) { \r\n this.setState(currentState); \r\n}\r\n\r\nexport default function StoreContainer(Component, reducers) { \r\n return class extends React.Component { \r\n\r\n  constructor() { \r\n   ... \r\n   this.instSubscriber = subscriber.bind(this); \r\n   store.subscribe(this.instSubscriber);\r\n  }\r\n\r\n  componentWillUnmount() {\r\n   store.unsubscribe(this.instSubscriber);\r\n  }\r\n }\r\n}\r\n...<\/code><\/pre>\n<p>Since the store is a singleton, we can just import that in and call on its API directly.<\/p>\n<p>Why do we have to keep the bound subscriber around? Because binding it returns a new function. When unmounting the component, we want to be able to unsubscribe to keep things clean. We know that the store merely looks for the function reference in its internal subscribers array and removes it, so we need to make sure we keep that reference around so we can get it back when we need to identify and remove it.<\/p>\n<p>One last thing to do in the constructor: add the reducers. This is as simple as passing what we received to the HOC into the store.addReducers method:<\/p>\n<pre><code class=\"js\">... \r\nexport default function StoreContainer(Component, reducers) { \r\n return class extends React.Component { \r\n  ... \r\n  constructor() { \r\n   ... \r\n   store.addReducers(reducers); \r\n  } \r\n  ... \r\n } \r\n}\r\n...<\/code><\/pre>\n<p>So now we&#8217;re ready to provide the rendering of the component. This is the essence of HOCs. We take the Component we received and render it within the HOC, imbuing it with whatever properties the HOC needs to provide it:<\/p>\n<pre><code class=\"js\">... \r\nexport default function StoreContainer(Component, reducers) { \r\n return class extends React.Component { \r\n  ... \r\n  render() { \r\n   return (&lt;Component {...this.props} {...this.state} \/&gt;); \r\n  } \r\n } \r\n} \t\t\t\r\n...<\/code><\/pre>\n<p>We are &#8220;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/Spread_operator\">spreading<\/a>&#8221; the properties and state of the HOC down to the Component it is wrapping. This effectively ensures that whatever properties we pass to the HOC get down to the component it wraps, a vital feature of infinitely nestable HOCs. It may or may not be wise to place the state as properties on the Component, but it worked well in my testing, and it was nice being able to access to the state through the <code>this.props<\/code> object of the Component that is wrapped, as you might expect to normally do with a React component that receives data from a parent component.<\/p>\n<p>Here&#8217;s the whole shabang:<\/p>\n<pre><code class=\"js\">import React from 'react'; import store from '.\/Store';\r\nfunction subscriber(currentState, previousState) { \r\n this.setState(currentState);\r\n}\r\n\r\nexport default function StoreContainer(Component, reducers) {\r\n return class extends React.Component { \r\n  \r\n  constructor(props) { \r\n   super(props); \r\n   this.state = store.getState(); \r\n   this.instSubscriber = subscriber.bind(this); \r\n   store.subscribe(this.instSubscriber);\r\n   store.addReducers(reducers); \r\n  }\r\n \r\n  componentWillUnmount() {\r\n   store.unsubscribe(this.instSubscriber);\r\n  }\r\n  \r\n  render() {\r\n   return (&lt;Component {...this.props} {...this.state} \/&gt;);\r\n  }\r\n }\r\n}<\/code><\/pre>\n<p>Implementation of using StoreContainer:<\/p>\n<pre><code class=\"js\">import StoreContainer from 'StoreContainer'; \r\nimport { myReducer } from 'MyReducers';\r\nlet MyComponent extends React.Component { \r\n \/\/ My component stuff \r\n}\r\nexport default StoreContainer(MyComponent, { myReducer });\r\n<\/code><\/pre>\n<p>Implementation of using the Component that uses StoreContainer (exactly the same as normal):<\/p>\n<pre><code class=\"js\">import MyComponent from 'MyComponent'; \r\nimport ReactDOM from 'react-dom';<\/code><\/pre>\n<p>ReactDOM.render(&lt;MyComponent myProp=&#8217;foo&#8217; \/&gt;, document.body);<\/p>\n<p>But you don&#8217;t have to define the data basis of your <code>MyComponent<\/code> immediately or in a long-lasting class definition; you could also do it more ephemerally, in implementation, and perhaps this is wiser for more generalized components:<\/p>\n<pre><code class=\"js\">import StoreContainer from 'StoreContainer'; \r\nimport { myReducer } from 'MyReducers'; \r\nimport GeneralizedComponent from 'GeneralizedComponent'; \r\nimport ReactDOM from 'react-dom';\r\nlet StoreContainedGeneralizedComponent = StoreContainer(GeneralizedComponent, { myReducer });\r\nReactDOM.render(&lt;StoreContainedGeneralizedComponent myProp='foo' \/&gt;, document.body);\r\n<\/code><\/pre>\n<p>This has the benefit of letting parent components control certain child component properties.<\/p>\n<h2>Conclusion<\/h2>\n<p>By gaining a solid understanding of Redux through this blog, we hope teams can enhance their state management and write efficient, scalable code.<\/p>\n<p>In addition to leveraging Redux, teams can further optimize their product development process by utilizing Jama Connect<sup>\u00ae<\/sup>&#8216;s powerful features, such as <a href=\"https:\/\/resources.jamasoftware.com\/traceability\" target=\"_blank\" rel=\"noopener\">Live Traceability\u2122<\/a> and <a href=\"\/datasheet\/research-notes-traceability-score\" target=\"_blank\" rel=\"noopener\">Traceability Score\u2122<\/a>, to improve engineering quality and speed up time to market.<\/p>\n<p>Jama Connect empowers teams with increased visibility and control by enabling product development to be synchronized between people, tools, and processes across the end-to-end development lifecycle. <a href=\"https:\/\/www.jamasoftware.com\/platform\/jama-connect\/trial\/\" target=\"_blank\" rel=\"noopener\">Learn more here.\u00a0<\/a><\/p>\n<hr \/>\n<p><!-- Uberflip Embedded Hub Widget --><\/p>\n<div id=\"UfEmbeddedHub1687991375813\"><\/div>\n<p><script>\n  window._ufHubConfig = window._ufHubConfig || [];\n  window._ufHubConfig.push({\n    'containers':{'app':'#UfEmbeddedHub1687991375813'},\n    'collection': '4131745',\n    'openLink':function(url){\n      window.open(url);\n    },\n    'lazyloader':{\n      'itemDisplayLimit':20,\n      'maxTilesPerRow':3,\n      'maxItemsTotal': 3\n    },\n    'tileSize': 'small',\n    'enablePageTracking':false,\n    'baseUrl': 'https:\/\/resources.jamasoftware.com\/',\n    'filesUrl': 'https:\/\/resources.jamasoftware.com\/',\n    'generatedAtUTC': '2023-06-28 22:29:03',\n  });\n  <\/script><\/p>\n<p><script>(function(d,t,u) {\n    function load(){\n      var s=d.createElement(t);s.src=u;d.body.appendChild(s);\n    }\n    if (window.addEventListener) {\n      window.addEventListener('load',load,false);\n    }\n    else if (window.attachEvent) {\n      window.attachEvent('onload',load);\n    }\n    else{\n      window.onload=load;\n    }\n  }(document,'script','https:\/\/resources.jamasoftware.com\/hubsFront\/embed_collection'));\n  <\/script><br \/>\n<!-- \/End Uberflip Embedded Hub Widget --><\/p>\n<input class=\"fooboxshare_post_id\" type=\"hidden\" value=\"21305\"\/>","protected":false},"excerpt":{"rendered":"<p>&#8220;What I cannot create, I do not understand.&#8221; Richard Feynman Redux is pretty simple. You have action creators, actions, reducers, and a store. What&#8217;s not so simple is figuring out how to put everything together in the best or most &#8220;correct&#8221; way. In this blog, we begin by explaining the motivation behind using Redux and [&hellip;]<\/p>\n","protected":false},"author":127,"featured_media":68645,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[50],"tags":[861,843,840],"industry":[],"class_list":["post-21305","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-requirements-management","tag-ecosystem","tag-jama-connect-platform","tag-product-development-management"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v28.1 (Yoast SEO v28.1) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Let&#039;s Write Redux! - Jama Software<\/title>\n<meta name=\"description\" content=\"Redux is pretty simple. What&#039;s not so simple is figuring out how to put all of that together the best or most &quot;correct&quot; way.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Let&#039;s Write Redux!\" \/>\n<meta property=\"og:description\" content=\"Redux is pretty simple. What&#039;s not so simple is figuring out how to put all of that together the best or most &quot;correct&quot; way.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/\" \/>\n<meta property=\"og:site_name\" content=\"Jama Software\" \/>\n<meta property=\"article:published_time\" content=\"2023-05-04T14:30:07+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-01-18T08:21:45+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.jamasoftware.com\/legacy\/media\/2023\/05\/2023-5-4-lets-write-redux.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"512\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Josh Turpen\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Josh Turpen\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/\"},\"author\":{\"name\":\"Josh Turpen\",\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/#\\\/schema\\\/person\\\/8bb4176558a01d46ab297cee17bb412b\"},\"headline\":\"Let&#8217;s Write Redux!\",\"datePublished\":\"2023-05-04T14:30:07+00:00\",\"dateModified\":\"2024-01-18T08:21:45+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/\"},\"wordCount\":2498,\"image\":{\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.jamasoftware.com\\\/media\\\/2023\\\/05\\\/2023-5-4-lets-write-redux.jpg\",\"keywords\":[\"Ecosystem\",\"Jama Connect Platform\",\"Product Development &amp; Management\"],\"articleSection\":[\"Requirements &amp; Requirements Management\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/\",\"url\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/\",\"name\":\"Let's Write Redux! - Jama Software\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.jamasoftware.com\\\/media\\\/2023\\\/05\\\/2023-5-4-lets-write-redux.jpg\",\"datePublished\":\"2023-05-04T14:30:07+00:00\",\"dateModified\":\"2024-01-18T08:21:45+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/#\\\/schema\\\/person\\\/8bb4176558a01d46ab297cee17bb412b\"},\"description\":\"Redux is pretty simple. What's not so simple is figuring out how to put all of that together the best or most \\\"correct\\\" way.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.jamasoftware.com\\\/media\\\/2023\\\/05\\\/2023-5-4-lets-write-redux.jpg\",\"contentUrl\":\"https:\\\/\\\/www.jamasoftware.com\\\/media\\\/2023\\\/05\\\/2023-5-4-lets-write-redux.jpg\",\"width\":1024,\"height\":512,\"caption\":\"Redux\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/2023\\\/05\\\/04\\\/lets-write-redux\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.jamasoftware.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Let&#8217;s Write Redux!\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/#website\",\"url\":\"https:\\\/\\\/www.jamasoftware.com\\\/\",\"name\":\"Jama Software\",\"description\":\"Jama Connect\u00ae #1 in Requirements Management\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.jamasoftware.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.jamasoftware.com\\\/#\\\/schema\\\/person\\\/8bb4176558a01d46ab297cee17bb412b\",\"name\":\"Josh Turpen\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/503bae25b46dadce2263301537b4e80acbb538e8ced06c9ca8529876afd8a0b5?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/503bae25b46dadce2263301537b4e80acbb538e8ced06c9ca8529876afd8a0b5?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/503bae25b46dadce2263301537b4e80acbb538e8ced06c9ca8529876afd8a0b5?s=96&d=mm&r=g\",\"caption\":\"Josh Turpen\"},\"description\":\"With a deep background in software development and consulting, Josh Turpen oversees the ongoing innovation and refinement of Jama Software\u2019s core product offerings. Beginning as an engineer, his career has taken him from Indiana to Germany, Colorado, and Portland. His work with the U.S. Department of Defense solidified his knowledge of safety-critical systems, and the vital role requirements and risk management plays within them. Having led product and engineering organizations, with teams distributed across the globe, Turpen understands the daily challenges our customers face in a constantly changing marketplace and the tools they need to be successful.\",\"url\":\"https:\\\/\\\/www.jamasoftware.com\\\/blog\\\/author\\\/jturpen\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Let's Write Redux! - Jama Software","description":"Redux is pretty simple. What's not so simple is figuring out how to put all of that together the best or most \"correct\" way.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/","og_locale":"en_US","og_type":"article","og_title":"Let's Write Redux!","og_description":"Redux is pretty simple. What's not so simple is figuring out how to put all of that together the best or most \"correct\" way.","og_url":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/","og_site_name":"Jama Software","article_published_time":"2023-05-04T14:30:07+00:00","article_modified_time":"2024-01-18T08:21:45+00:00","og_image":[{"width":1024,"height":512,"url":"https:\/\/www.jamasoftware.com\/legacy\/media\/2023\/05\/2023-5-4-lets-write-redux.jpg","type":"image\/jpeg"}],"author":"Josh Turpen","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Josh Turpen","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/#article","isPartOf":{"@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/"},"author":{"name":"Josh Turpen","@id":"https:\/\/www.jamasoftware.com\/#\/schema\/person\/8bb4176558a01d46ab297cee17bb412b"},"headline":"Let&#8217;s Write Redux!","datePublished":"2023-05-04T14:30:07+00:00","dateModified":"2024-01-18T08:21:45+00:00","mainEntityOfPage":{"@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/"},"wordCount":2498,"image":{"@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/#primaryimage"},"thumbnailUrl":"https:\/\/www.jamasoftware.com\/media\/2023\/05\/2023-5-4-lets-write-redux.jpg","keywords":["Ecosystem","Jama Connect Platform","Product Development &amp; Management"],"articleSection":["Requirements &amp; Requirements Management"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/","url":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/","name":"Let's Write Redux! - Jama Software","isPartOf":{"@id":"https:\/\/www.jamasoftware.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/#primaryimage"},"image":{"@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/#primaryimage"},"thumbnailUrl":"https:\/\/www.jamasoftware.com\/media\/2023\/05\/2023-5-4-lets-write-redux.jpg","datePublished":"2023-05-04T14:30:07+00:00","dateModified":"2024-01-18T08:21:45+00:00","author":{"@id":"https:\/\/www.jamasoftware.com\/#\/schema\/person\/8bb4176558a01d46ab297cee17bb412b"},"description":"Redux is pretty simple. What's not so simple is figuring out how to put all of that together the best or most \"correct\" way.","breadcrumb":{"@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/#primaryimage","url":"https:\/\/www.jamasoftware.com\/media\/2023\/05\/2023-5-4-lets-write-redux.jpg","contentUrl":"https:\/\/www.jamasoftware.com\/media\/2023\/05\/2023-5-4-lets-write-redux.jpg","width":1024,"height":512,"caption":"Redux"},{"@type":"BreadcrumbList","@id":"https:\/\/www.jamasoftware.com\/blog\/2023\/05\/04\/lets-write-redux\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.jamasoftware.com\/"},{"@type":"ListItem","position":2,"name":"Let&#8217;s Write Redux!"}]},{"@type":"WebSite","@id":"https:\/\/www.jamasoftware.com\/#website","url":"https:\/\/www.jamasoftware.com\/","name":"Jama Software","description":"Jama Connect\u00ae #1 in Requirements Management","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.jamasoftware.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.jamasoftware.com\/#\/schema\/person\/8bb4176558a01d46ab297cee17bb412b","name":"Josh Turpen","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/503bae25b46dadce2263301537b4e80acbb538e8ced06c9ca8529876afd8a0b5?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/503bae25b46dadce2263301537b4e80acbb538e8ced06c9ca8529876afd8a0b5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/503bae25b46dadce2263301537b4e80acbb538e8ced06c9ca8529876afd8a0b5?s=96&d=mm&r=g","caption":"Josh Turpen"},"description":"With a deep background in software development and consulting, Josh Turpen oversees the ongoing innovation and refinement of Jama Software\u2019s core product offerings. Beginning as an engineer, his career has taken him from Indiana to Germany, Colorado, and Portland. His work with the U.S. Department of Defense solidified his knowledge of safety-critical systems, and the vital role requirements and risk management plays within them. Having led product and engineering organizations, with teams distributed across the globe, Turpen understands the daily challenges our customers face in a constantly changing marketplace and the tools they need to be successful.","url":"https:\/\/www.jamasoftware.com\/blog\/author\/jturpen\/"}]}},"_links":{"self":[{"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/posts\/21305","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/users\/127"}],"replies":[{"embeddable":true,"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/comments?post=21305"}],"version-history":[{"count":0,"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/posts\/21305\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/media\/68645"}],"wp:attachment":[{"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/media?parent=21305"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/categories?post=21305"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/tags?post=21305"},{"taxonomy":"industry","embeddable":true,"href":"https:\/\/www.jamasoftware.com\/legacy\/wp-json\/wp\/v2\/industry?post=21305"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}