Redux sadly died; here’s why

#Redux has served its time. Complicated. More than sophisticated. Cumbersome. I can understand it, but strain to.  #React solves so much with hooks, and a little…just a little sugar goes a long way.

I couldn’t (and still?) didn’t understand #redux because no one ever explained it well enough to me; a programmer. So we try follow their recipes/practices, but never understood “why”. Here, I hope, I will try.

But let’s (perhaps) start with the problems and THEN Understand The Why and THEN understand how #react with “salt” (not sugar) is maybe better (at least many times).

You have a big project. We all do.  You need “state” data at various points.  OOP is good, but less-so in a UI or worse a SPA world.  Why is that? Well because “state” flows and isn’t “bound” — oxymoron yes?.  It’s not #WIN32 any longer is it? And the mentality of isolation and a #microservices world COULD change much but we were thrust between the two (imho). 

The most simple idea/solution to data sharing is a “library”….database-like. “Give me piece X”, and “Maybe lock it”, and “Maybe I’ll send it back for someone else to see/use”.  That doesn’t work in a UI app. Because data is localized at the top and shared top-down, never across (different story on why that is).  So, we, in the #react world we pass EVERYTHING down, hence the cute term prop-drilling.  You pass so much “stuff” (props) down that you routinely just do “…props” because your generalized component doesn’t know what something below it needs from above. That’s OK in the top-down hierarchy view of the world, but then we found outside components.  Whats that? Something down deep in the stack that needs something high up but no one in between did (in a sense, it is data going horizontally across and like mentioned above that doesn’t fit the model).  Redux tries to address that.

Frankly, for most apps, large ones even, the built in browser-database-methods are great — and fast — perf test them if you like.  BUT they dont know or own “top state” and SPA apps have to have top state (ex. my username changes depending on how I log into my bank site or retail site for a variety of reasons, yet it is one app). 

Redux says: Lets confuse you with things like “context” that must be shared and build your library of getters and setters which must also be shared and add-in language to complicate things and let’s make changing/saving data incredibly painful.  My goodness,  Reducers? What does that even mean? They created a new Computer Science “thing” that now you have to learn.

Turns out, you can accomplish all the same magic with just React.  But that too can be confusing.  People struggle to understand “hooks” and terms like context and reducers also which largely were invented to “go class-less” but retain some state.  

Enter: A cool and simple way to use useReducer()!

Let’s just pretend we’re in a Hooks world for fun.  In such a world, we use useState() to allow us to “persist” a datum for the life of a component and to correctly catch updates to it which force a re-render, right?  Works great for simple datums like numbers or strings, and, well, if you just have a small number of such things.

 

But bigger datums, like, say, a “street address object”, are more complicated. For such datums, we will use useReducer() instead of useState(). Why? Because, useReducer() keeps track of the current object structure and hands it to you when an update is occurring.  The importance of this will be seen shortly.

 

Remember the goal here: To allow any component (vertical, horizontal, or unrelated deep) to access some perhaps complicated data object AND to have updates to that data cause React to, well, react (meaning: update all DOM elements related to it).  This is why data-stores, databases, server-side-store etc wont work in the UI world.

Now, keep this important point in mind. useReducer() does NOT have to be as complicated and ugly as Redux;  that whole notion of actions and types and values (so confusing! complicated!) can be boiled into something much simpler. Instead of understand the science of complicated reducers lets build a dirt-simple one have it be the ONLY to use EVERYWHERE in this solutions. A wonderfully simple single global reducer to be used by all users of this technique!   See below…this proposed reducer is trying to essentially imitate how React’s setState() worked:

const ReactStyleMergeReducer = (oldState, actionData) => {

 return { …oldState, …actionData }; // Merge new into old, like React would do

};

 

This is amazingly simple, and knows nothing about specific object-type or attributes and doesn’t deal with Redux-like ‘actions’ etc.  When React invokes this reducer, React passes-in the old complete object, plus it passes-in whatever the caller provides which let’s assume is the same style object (OR fragments of the same).  This code merges the new into the old and returns a new full instance which is what React expects returned. You will use this ‘reducer’ in ALL of your calls to useReducer() for this proposed technique. And you didn’t have to define ACTION types (and import everywhere) and use case statements and all the other Redux mess — we just want a data-store similar to a database or library and those were never complicated to understand or use.

 

Let’s look at a degenerative simple example of a top-level component that has a semi-complex object (think of it as an address record) and think of pieces of that object getting updated by various other code fragments (REST calls, events like the ‘click’ shown here, other) and changes to any of that data needs the UI to automatically update, everywhere, transparently.

 

const MyClickmeComponent = props => {

 const [obj, objSetter] = useReducer(ReactStyleMergeReducer, {

   one: 1, // Initial values, if needed

   two: 2,

   three: 3

 });

 return (

     <div>

       Hi there, one is:{obj.one}

       <button

         onClick={e => {

           obj.one += 4;   // <— Look! Updating the real obj!

           objSetter(obj);

         }}

       >

         CLICK ME

       </button>

     </div>

 );

};

 

Remember, this is a “get started” example component so it’s trivial.  What we see here is that code can update either the real object (directly in place) or a copy of it (whatever, doesn’t matter), and then when it’s ready to go-live with the data it simply calls the “setter()” function (which the Redux and useReducer() worlds like to call ‘reducer()’s).  Because ultimately the data is stored in a React object (via useReducer()), updates via the setter/reducer call are processed by React and thus React will force a re-render (anywhere in the tree where this object is used) — which is exactly what we want.

 

So, even at this scant voyage into this new world, my life is easier and simpler; I can easily update pieces of my object when I see fit, even directly into it, and then announce it.  Pretty close to using setState() in the old days, right? So, Step-1 done.

 

Good. The bigger deal is passing this down into a sub-component which might also need to do random updates to pieces of the object.

 

In short, your object is really now a pair-of-things: The Object, and its Setter().   Really, this is like the old days with State; you had: state, and setState() to reflect updates on it.  No different. They are a pair. If you pass the object to something, anything, anywhere, AND that “thing” needs to update AND you want that update to force rendering wherever it is referenced, well, then, pass along the setter() with it.

 

Let’s see how that top level component now calls a lower component, passing the pair, and how the lower component absorbs the pair and makes-use of the pair EXACTLY the same way as the parent:

 

const MyClickmeComponent = props => {

 const [obj, objSetter] = useReducer(ReactStyleMerge, {

   one: 1,

   two: 2,

   three: 3

 });

 return (

     <div>

       Hi there, one:{obj.one}

       <button

         onClick={e => {

           obj.one += 4;

           objSetter(obj);

         }}

       >

         CLICK

       </button>

       <SubForm2 data={{ obj, objSetter }} />

     </div>

 );

};

 

const SubForm2 = props => {

 const { obj, objSetter } = props.data;

 return (

   <div>

     SubForm2, obj.one:{obj.one}

     <button

       onClick={e => {

         objSetter({ one: obj.one + 100 });

       }}

     >

       Click here now

     </button>

   </div>

 );

};

 

Notice that parent invokes <SubForm2> and passes-in via a prop the tupple; that is, the obj plus its setter.  And then notice that the sub-form absorbs from that prop into the comfortable feeling two items of “obj” and “objSetter”.  The rest of SubForm2 is the exact same type/style of code as the parent.

 

Now, any click to either of the two buttons in either component will share the same obj data and update both components “view” accordingly!!!

 

Ok, so if you followed along, you’re probably saying: Yeah, kinda cute, makes sense. The first part of the magic though is that you couldn’t do this easily before. If you pass-down a prop object AND the lower component(s) needed to update it, you always passed down a callback prop to do the update in the parent. For EVERY object, up and down the tree. So part of this magic is that it is somewhat of a better “style” and works with objects (versus datums) simpler. Good.

 

The real magic, and why Redux goes away, just couple it with ‘context’. Same exact syntax. You receive the Obj and Setter via the context and the rest of your code is exactly like above. No more passing data around. Any code that needs the objects just pulls the context AND updates to the data will flow through the React UI framework. Yeah, Redux uses a similar technique under the hood, but adds so much complexity that it takes volumes of reading to understand it, lots of shared code to share data structures, and has lots of chances for errors.

 

This is just so simple and sweet!!

 

 

Leave a Comment

Your email address will not be published. Required fields are marked *