DEV Community

Cover image for Throw Out Your React State-Management Tools

Throw Out Your React State-Management Tools

Adam Nathaniel Davis on February 23, 2020

A few days ago, I wrote a post about a workaround/hack that I've been using in React to pass around components' state variables and functions. I k...
Collapse
 
latobibor profile image
András Tóth • Edited

Overmind.js - the redux that should have been written in the first place.

It's not forcing an overly complex "functional" style with all its unnecessary wrapping a function in a function. You don't need 5 libraries just to be able to do async events. It just works as you expect it.

A random thought: there is a lot of hatred for OOP in JS circles - rightly so, since the language was not built for that (and neither for FP). However the idea of having private vs. public fields is excellent and it is connected to global states: if you put any minuscule implementation detail into it, people are going to depend on them making the application hard to change (but it would be still "theoretically correct").

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Interesting. I haven't been exposed to Overmind before, but I'll definitely check it out!

Collapse
 
isaachagoel profile image
Isaac Hagoel

Thanks for another interesting post. I wonder, does 'logToConsole' and the other 'methods' belong in the App state?
I have never used the context API but it looks like you could have stored them in the context outside of the whole 'state management' system, right?

Regarding

the "problem" is that the declarative syntax is usually implicitly tied to the render() process. But there are times when you want to leverage such functionality without depending upon the rendering cycle.

I fully agree with the sentiment. This is a mistake the React team keeps repeating for some reason (most recently with Hooks).

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited

Thanks for the feedback! As for the location of the methods in <App>... yeah, I get what you're saying. Whenever I'm writing a "real" app that doesn't exist purely for demo reasons, I never put any real logic (or methods, or state) in <App>. To me, it's a kinda "best practice" to treat that initial "springboard" component as a completely blank container whose sole purpose is just to launch the rest of the app. No one's ever explicitly told me that this should be done as a "rule". It's just something that I feel has kinda become understood outta (too many) years of experience.

For me, a salient analogy would be Java's public static void main() method. Every Java app must have it. And yet, it's kinda an amateur practice to shove a buncha logic into it. (In fact, I think it's kinda amateurish to put any real logic into it.) It's only "real" function, is just to launch the rest of the app. When I create React apps (for me, or for employers) that I expect to actually be deployed into a proper production setting, I always treat <App> like Java's public static void main().

The only time I break this "rule" is when I'm throwing something up purely for demo purposes (e.g., like on StackBlitz).

Collapse
 
isaachagoel profile image
Isaac Hagoel

I get you point. What I meant is that usually methods that can't change don't belong in 'state'. They are not part of anyone's state :)
They can still be part of the context though (I guess).
It is a minor point and not directly related to what you were trying to illustrate. It just popped to me as I was scanning through the code.

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

OK, I think I see what you're getting at. I will freely admit that I'm still figuring out all the ins-and-outs of the Context API. But I found, in my initial setup, that I had to put references to those functions into the associated component's state for the whole context-reference thing to work properly.

Now... am I stating that as an "absolute truth" that must be done to make the Context API work?? Definitely not. I've been doing dev for 20+ years. But I literally started experimenting with the Context API yesterday. So it's perfectly possible that I didn't actually need to stuff those function references into state to make the whole thing work. That's just the way that my solution "ended up" when I finally got it all working.

(Side note: I will freely admit that, in some respects, the Context API was a little bit challenging for me to "grok". I actually played around - with a dozen-or-more approaches - before settling on the example that I put in the demo.)

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited

Continuing that thought, if this were a "real" app, most/all of that state/functionality would be shunted down into <TopTier>. That probably woulda been more effective for the demo, cuz the whole point was just to show, "Hey... See? We're skipping the <MiddleTier> component and porting values/functions directly into <BottomTier>."

Of course, if this were a "real" app, there would be no logToConsole() function. No self-respecting dev would create a wrapper function whose sole purpose is to perform a console.log(). My only purpose in including that (silly) little function was to demonstrate that, "See! We're calling an <App> function directly from <BottomTier> - and we're doing it solely through a Context API reference."

Now, as for the toggleTheContextApiIsCool() function, there is a specific reason why it was placed in the <App> component. I can easily accept the idea that the theContextApiIsCool state variable should possibly "live" in the <TopTier> component. But one of the (lesser) points of my post is that, IMHO, if we have stateVariableX that "lives" in <ComponentX>, then, whenever possible, the functions that update that state variable should also "live" in <ComponentX>.

So to put this another way, in this example, I don't particularly care where the state variable of theContextApiIsCool resides. But wherever you choose to place it, the subsequent function(s) that call setState() on that value should also reside in the same component.

Collapse
 
isaachagoel profile image
Isaac Hagoel

I agree with where state should live. I was saying a function is not state (unless it is dynamically changing). Again, purely semantic point.

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

Yeah. I get that. And I think I answered that in my other answer to your comment. To be frank, the only part of my proposed Context API solution that feels a little "weird" or... "off" is the stuffing of those function references into state. I'm not sure if I had to do it that way. But that seemed to be the way that I "had to" do it to get my demo to compile/work. When I tried it without the state-function references, it broke when I tried to invoke a context-based function that resolved to a function that invoked setState().

(Does that make sense?? It's fairly clear in my mind at the moment - but it's one of those concepts that's kinda confusing to try to spell out in text.)

That being said, once I got it working, I didn't necessarily dislike it. On some level, it's almost kinda cool. Cuz, aside from trying to find a better approach to (what is, IMHO) the bloated mess of Redux, I kinda liked the idea that we can (or... have to) explicitly define the functions that are available via the Context API.

I dunno... I could probably be convinced that it's somehow "sub-optimal". Or that it's somehow an "anti-pattern". But, for the time being, it kinda seems like a good thing.

Thread Thread
 
isaachagoel profile image
Isaac Hagoel

Thanks for all of the replies. We were just replying to one another too quickly so I missed your previous one :)
Maybe I will play with it a bit too (will try to make time). As I probably expressed before, one of my main issues with React (some other frameworks have that too) is its insistence that everything is 'ui related state' and rendering logic. I find this to be thoroughly lacking.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited

As for the whole "rendering cycle problem", I'm glad that you understand my point. Lemme give you a more tactical "real world" example that I ran into recently.

I had to build a React app with backend/API connections. And I was specifically requested to craft those API connections via GraphQL. To be frank, I'd read about GraphQL before, but I hadn't actually implemented GraphQL endpoints before. (Obviously, I'm all-too-familiar with REST.)

So, after doing some basic get-up-to-speed reading, I did what a lotta React devs do - I started playing with some of the major React/GraphQL libraries that I could leverage on NPM. I wasn't using some esoteric packages that have minuscule downloads. I was trying to use the "official", "approved" React/GraphQL libraries. The ones that are either written, or explicitly endorsed, by the core GraphQL team.

But I kept running into one core problem that bugged the hell outta me. All of the "official" libraries wanted me to embed/invoke the GraphQL components directly in the render() function.

Once I got everything properly loaded up and connected, I noticed that the damn components were constantly calling my backend API - sometimes, two or three times - for a single load of the application.

To be honest, I don't think the client woulda really given a shit. But it bugged me greatly. I HATE invoking unnecessary API calls. And when I'm doing an SPA, I take great pride in trying to minimize any unneeded renders and/or API calls.

Of course, one of the "hallmarks" of React development is the (near constant) struggle to halt unnecessary renders. So, at first, I attacked it as a "stop the unnecessary renders" problem. But no matter what I was doing, I just couldn't get it to where it would only call the backend API once. It always called the API at least twice.

Finally... I told myself that this was just stupid/wasted effort. I mean, in the end, a GraphQL query (or a REST query, or a SOAP query, or any kinda query) is just text that's gotta be formatted in a very-specific way. So I ended up just ripping out the React/GraphQL libraries and writing the queries manually - in a fetch() - that didn't "live" in the render() function.

Once I did it the "manual" way, it worked beautifully. It called my backend API no more than the exact number of times that were necessary to provide the needed functionality.

And yet... soooo many React libraries just assume that you're gonna invoke all their custom components in the render() function.

Sigh...

Collapse
 
isaachagoel profile image
Isaac Hagoel

Thanks for sharing. I didn't use graphQL yet (beyond playing around in a sandbox) and didn't know the problem extends to it. I love the fact you didn't just accept it and went back to basics:)
I think one major thing a lot of framework users don't understand is that the people who make these frameworks are just normal developers that don't like to other people's frameworks (so they make their own) :)

Collapse
 
thatzacdavis profile image
Zachary Davis

Have you run into rerendering issues with passing functions that update state via Context? I think one solution is to memoize the functions, but I'm still experimenting with that.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis • Edited

I'm playing around with something today and your question came to mind. If you want the context to be properly updated on state changes, you must be referencing it from within the render() function. I'm gonna do a follow-up post on this soon that will illustrate this. This might be part of the issue you've experienced.

Collapse
 
thatzacdavis profile image
Zachary Davis

Looking forward to it!

Thread Thread
 
bytebodger profile image
Adam Nathaniel Davis

And... here it is:

dev.to/bytebodger/a-context-api-fr...

It's not radically different from what I already highlighted. But it emphasizes the need to put the context references in the render() function. It also provides a few stylistic improvements to the first post...

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

No, I haven't run into any issues with that. I haven't used it enough yet to say that there are no issues with it. But my initial tests haven't revealed any problems... so far.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

I agree. And that kinda brings us full circle back to the original point of this particular post. The typical flow seems to go like this:

  1. Team wants to share state.
  2. Team creates global store.
  3. Team starts to realize all the headaches of unplanned use.
  4. Team starts bolting a ton of extra controls on top of the global store to avoid these headaches.
  5. The team's code is now a Frankensteined aberration of what they originally set out to build.

But this is why I'm excited about the Context API. By using that, you can keep full control of that value in its original component. No need to route all changes through a series of actions or reducers. In other words, you can let React's native setState() handle all state updates - just as it was originally designed to do.

It is true that, with the Context API, those value do have to be "exposed". But that should usually be quite painless. In the case of state variables, if you're passing all of state into the ContextProvider's value, there's no need to worry about making future state variables accessible. It's already done.

Collapse
 
sumanthyedoti profile image
Sumanth Yedoti

I had developed a pertty complex app without any state management tool, with Context API. If there are any downfalls where components are getting rerendered, we can definitely optimize that. We can surely architecture state with context API like there is no need of external tool to do that

Collapse
 
frankdspeed profile image
Frank Lemanschik

I Love this improvement of state sharing while I prefer to keep even react out of the way. I would always prefer modules for shared state as you can dynamic import them and this way share state on module level inside components.

i would love to hear your feedback about this dev.to/frankdspeed/the-html-compon...

Collapse
 
zenventzi profile image
Zen Ventzi

Thanks for sharing but,WOW. That was a lot of text. Probably a lot of the words could be removed and the idea would still come across just as clear if not more.