Can flux actions access stores? - reactjs

Can Flux actions access stores? I recently got a code review comment asking me to pass a certain value in from a React component, instead of getting it from the store directly in the action. This would change Flux's data flow from this:
View->Action->Dispatcher->Store-|
^-----------------------------<
to this
View->Action->Dispatcher->Store-|
^------^----------------------<
It seems to me that, because all data changes are still going through the dispatcher, that the data flow still goes in the intended direction, updates are still atomic, annd the flow is still easy to reason about. Could there be any drawback?

An action can access a Store, but it should be a strict read-only operation.
Actions may want to yield a dispatch that is conditional on the content of a store and keeping track of what store content is required to perform the action is not the responsibility of the invoking component.

Related

Accessing global state from a slice

I'm using #reduxjs/toolkit createSlice feature for the whole application.
I often have a need to access other slices's state. One common example is for the "one to many relationship" where children are stored in normalized form by "byParentId" key in their respective state. So, when user makes some "parent" active, almost every selector / reducer / saga effect in the "child" needs access to "parent" state "active" field. Initially, I simply added "activeParent" field to the action while combining reducers. Later on, with more such cases I ended up with just "global" variable in action with the whole state for every action instead of crafting data preparation in the combine reducers function.
This also improved performance in redux-saga, where yield select(selector) calls where replaced with synchronous selector(global)
Here #gaeron claims this approach to be an anti-pattern, which can be usually solved by:
Removing that logic from reducer and moving it to a selector
Good, when possible.
Passing additional information into the action;
Sometimes it is good, but often introduces unnecessary performance hit.
To pass additional information to an action it should be retrieved by useSelector into component which may otherwise don't need it, read: more redraws.
Letting the view code perform two actions.
Again, good, but not always. It requires to put sequences of actions in multiple components instead of having a simple logic,
when one action results in another one.
One mentioned problem:
reducers become coupled to each other’s state shape which complicates any refactoring or change in the state structure.
can be easily avoided by using selectors instead of accessing "foreign" state directly.
Is there a real reason for this approach to be anti-pattern?
Putting the entire root state into an action is definitely an anti-pattern and should be avoided.
You may want to sometimes put additional data from the state into specific actions if the reducer logic needs it.
Beyond that, I'd need to see more specific details on what you're trying to do and what the code looks like to offer additional advice.

Update Redux store for every character entered in a input field?

I have a sequential data collection app built with React and Redux. Right now, I have internal state for each page which has a form and when user clicks "Submit" button, I am dispatching the data collected in the state and updating my Redux store. Should I be dispatching an action on every key entered(debouncing) or should I store it in a local state and update Redux store at once?
Will there be a performance issue if I dispatch action to Redux store on every keypress since it is not an asynchronous call.
Well, the answer is: it depends.
Some argue that we loose some of the advantages of using a central store (like Redux) if you also have an internal state in the components. Like, if all your application state is just the redux store, storing the store, or the sequence of actions that lead to that store, will give you a complete picture of what was happening before maybe an error occurs.
I believe that is excessive. It is completely fine to store little stuff in your component's internal state, but there should be a well defined line.
In case of a form, you'd have controlled input elements, and you'll want a mechanism to submit the form with an action. It'd be necessary to have the contents of the form in the redux store.
One way would be to have the contents in an internal state, and only put them into redux form after a little debounce. But that has some edge cases. What if the user submits the form in the little debounce time? The form in the store is incomplete. Is thinking about all the edge cases worth it?
I believe just having the form elements controlled from the redux form is the simplest solution. The performance issue one would imagine is not really an issue. This is also what redux-form does (you can look at how that is implemented for inspiration).

Why we decouple actions and reducers in Flux/Redux architecture?

I've been using Flux first and Redux later for a very long time, and I do like them, and I see their benefits, but one question keeps popping in my mind is:
Why do we decouple actions and reducers and add extra indirections between the call that will express the intent of changing the state (action) and the actual way of changing the state (reducer), in such a way that is more difficult to provide static or runtime guaranties and error checking? Why not just use methods or functions that modify a state?
Methods or function will provide static guaranties (using Typescript or Flow) and runtime guaranties (method/function not found, etc), while an action not handled will raise no errors at all (either static or runtime), you'll just have to see that the expected behavior is not happening.
Let me exemplify it a little better with our Theoretical State Container (TSC):
It's super simple
Think of it as React Component's state interface (setState, this.state), without the rendering part.
So, the only thing you need is to trigger a re-render of your components when the state in our TSC changes and the possibility to change that state, which in our case will be plain methods that modify that state: fetchData , setError, setLoading, etc.
What I see is that the actions and the reducers are a decoupling of the dynamic or static dispatch of code, so instead of calling myStateContainer.doSomethingAndUpdateState(...) you call actions.doSomethingAndUpdateState(...), and you let the whole flux/redux machinery connect that action to the actual modification of the state. This whole thing also brings the necessity of thunks, sagas and other middleware to handle more complex actions, instead of using just regular javascript control flows.
The main problem is that this decoupling requires you to write a lot of stuff just to achieve that decoupling:
- the interface of the action creator functions (arguments)
- action types
- action payloads
- the shape of your state
- how you update your state
Compare this to our theoretical state container (TSC):
- the interface of your methods
- the shape of your state
- how you update your state
So what am I missing here? What are the benefits of this decoupling?
This is very similar to this other question: Redux actions/reducers vs. directly setting state
And let me explain why the most voted answer to that question does not answer either my or the original question:
- Actions/Reducers let you ask the questions Who and How? this can be done with the our TSC, it's just an implementation detail and has nothing to do with actions/reducers themselves.
- Actions/Reducers let you go back in time with your state: again this is a matter of implementation details of the state container and can be achieve with our TSC.
- Etc: state change orders, middleware, and anything that is currently achieved with actions/reducers can be achieved with our TSC, it's just a matter of the implementation of it.
Thanks a lot!
Fran
One of the main reasons is that constraining state changes to be done via actions allows you to treat all state changes as depending only on the action and previous state, which simplifies thinking about what is going on in each action. The architecture "traps" any kind of interaction with the "real world" into the action creator functions. Therefore, state changes can be treated as transactions.
In your Theoretical State Container, state changes can happen unpredictably at any time and activate all kinds of side effects, which would make them much harder to reason about, and bugs much harder to find. The Flux architecture forces state changes to be treated as a stream of discrete transactions.
Another reason is to constrain the data flow in the code to happen in only one direction. If we allow arbitrary unconstrained state modifications, we might get state changes causing more state changes causing more state changes... This is why it is an anti-pattern to dispatch actions in a reducer. We want to know where each action is coming from instead of creating cascades of actions.
Flux was created to solve a problem at Facebook: When some interface code was triggered, that could lead to a cascade of nearly unpredictable side-effects each causing each other. The Flux architecture makes this impossible by making every state transition a transaction and data flow one-directional.
But if the boilerplate needed in order to do this bothers you, you might be happy to know that your "Theoretical State Container" more or less exists, although it's a bit more complicated than your example. It's called MobX.
By the way, I think you're being a bit too optimistic with the whole "it's an implementation detail" thing. I think if you tried to actually implement time-travel debugging for your Theoretical State Container, what you would end up with would actually be pretty similar to Redux.

what is this difference between this flux action and this function call?

I could a have a flux action like this:
{type: 'KILL', payload: {target: 'ogre'}}
But I am not seeing what the difference is between having a method on a class People (wrapping the store) like this,
People.kill('ogre')
IF People is the only receiver of the action?
I see that the flux dispatcher gives me two advantages (possibly)
The "kill" method can be broadcast to multiple unknown receivers (good!)
The dispatcher gives me a handy place to log all action traffic (also good!)
These might be good things sure, but is there any other reasons that I am missing?
What I don't see is how putting the actions in the form of JSON objects, suddenly enforces or helps with "1-way" communication flow, which is what I read everywhere is the big advantage of having actions, and of flux.
Looks to me like I am still effectively sending a message back to the store, no matter how I perfume the pig. Sure the action is now going through a couple of layers of indirection (action creator, dispatcher) before it gets to the store, but unless I am missing something the component that sends that action for all practical purposes is updating whatever stores are listening for the kill message.
What I am missing here?
Again I know on Stack Overflow we can't ask too general a question, so I want to keep this very specific. The two snippets of code while having different syntax, appear to be semantically (except for the possibility of broadcasting to multiple stores) exactly the same.
And again if the only reason is that it enables broadcasting and enables a single point of flow for debug purposes, I am fine with that, but would like to know if there is some other thing about flux/the dispatcher I am missing?
The major features of the flux-style architecture are roughly the following:
the store is the single source of truth for application state
only actions can trigger mutation of the store's state
store state should not be mutated directly, i.e. via assigning object values, but by creating new objects via cloning/destructuring instead
Like a diet, using this type of architecture really doesn't work if you slip and go back to the old ways intermittently.
Returning to your example. The benefit for using the action here is not broadcasting or logging aspects, but simply the fact that the People class should only be able to either consume data from a store and express its wishes to mutate the state of said store with actions. Imagine for example that Elves want to sing to the the ogre and thus are interested in knowing the said ogre is still alive. At the same time the People want to be polite and do not wish to kill the ogre while it is being serenaded. The benefits of the flux-style architecture are clear:
class People {
kill(creature) {
if (creatureStore.getSerenadedCreature() !== creature)
store.dispatch({ type: 'KILL', payload: { target: creature } })
return `The ${creature} is being serenaded by those damn elves, let's wait until they've finished.`
}
}
class Elves {
singTo(creature) {
if (!creatureStore.getCreatures().includes(creature))
return store.dispatch({ type: 'SING_TO', payload: { target: creature } })
return `Oh no, the ${creature} has been killed... I guess there will be no serenading tonight..`
}
}
If the class People were to wrap the store, you'd need the Elves class to wrap the same store as well, creating two places where the same state would be mutated in one way or the other. Now imagine if there were 10 other classes that need access to that store and want to change it: adding those new features is becoming a pain because all those classes are now at the mercy of the other classes mutating the state from underneath them, forcing you to handle tons of edge cases not possibly even related to the business logic of those classes.
With the flux style architecture, all those classes will only consume data from the creatureStore and dispatch actions based on that state. The store handles reconciling the different actions with the state so that all of its subscribers have the right data at the right times.
The benefits of this pattern may not be evident when you only have a couple of stores that are consumed by one or two entities each. When you have tens (or hundreds) of stores with tens (or hundreds) of components consuming data from several stores each, this architecture saves you time and money by making it easier to develop new features without breaking existing ones.
Hope this wall-o-text helped to clarify!
What I don't see is how putting the actions in the form of JSON objects, suddenly enforces or helps with "1-way" communication flow, which is what I read everywhere is the big advantage of having actions, and of flux.
Looks to me like I am still effectively sending a message back to the store, no matter how I perfume the pig. Sure the action is now going through a couple of layers of indirection (action creator, dispatcher) before it gets to the store, but unless I am missing something the component that sends that action for all practical purposes is updating whatever stores are listening for the kill message.
What I am missing here?
Facebook Flux took the idea from the event driven GUI systems.
In there even if you move your mouse you get messages. This was called message loop then, and now we have actions dispatching.
Also, we have lists of subscribers inside stores.
And it is really the same principle in Redux where you have one store, while in Flux you may have multiple stores.
Now little mathematics. Having 2 components A and B you need to have just a few possible update chains A updates B and B update A, or self-update (non including in here the updates from outside of the app). This is the possible case.
With just three components we have much more possible chains.
And with even more components it gets complicated. So to suppress the exponential complexity of possible components interaction we have this Flux pattern which in nothing more than IDispatch, IObservable if you worked with these interfaces from some other programming languages. One would be for spitting the actions, and the other for entering the listener's chain that exists inside the store.
With this pattern, your React code will be organized in a different way than common React approach. You will not have to use React.Component state anymore. Instead, you will use the Store(s) that will hold the application state.
Your component can only show the desire to mutate the application state by dispatching the action. For instance: onClick may dispatch the action to increment the counter. The actions are objects with the property type: that is usually a string, and usually in upper case, but the action object may have many other props such as ID, value,...
Since the components are responsible for rendering based on the application state we need somehow to deliver them the application state. It may be via the props = store.getState() or we may use the context. But also check this.
Finally, it is even not forbidden that component uses the internal state (this.state) in case this has no impact on the application. You should recognize these cases.

In flux, why Store is needed?

According to Flux Architecture View uses Action to call Dispatcher that updates the Store, while View listening to Store change events.
My question is: Why do we need Store?
In order to list all users, my Component will call ListAllUsersAction that will in turn call my API and will update the Store with the result of API call. Store then emits change event that the View is listening to. But the store also stores the result. Why? Why this middle layer is needed? I wont call the store directly anyway, so this cache layer makes no sense to me, and as I generate more events that loads more data, eventually all my stores will have all the state of my application because flux architecture says nothing about cleaning the Stores.
Am I missing something?
The goal of Flux is to keep data flow easy to understand even as apps becomes large and complex, such that a new person can be brought up to speed quickly, figure out what's going on by inspecting the source code, and be confident that they can make changes without breaking things. Modularity and separation of concerns are a big part of that. The Stores are a way to keep the data models independent of the details of the view layer and establish a single source of truth for the application state. You can look at any Store's code and see what data it holds, what actions it responds to, which dependencies it has for data in other Stores. It's a matter of organization for the sake of the developers, at the cost of code being slightly less compact.
In order to list all users, my Component will call ListAllUsersAction
that will in turn call my API and will update the Store with the
result of API call.
Since the function of Actions is mainly to provide updated data to stores, you could also just call the API first and then just create one Action to handle the result.
as I generate more events that loads more data, eventually all my
stores will have all the state of my application because flux
architecture says nothing about cleaning the Stores.
Holding the current state of the application is the intended function of the Stores. If user actions or API calls cause the data to change, the Actions notify the Stores and the Stores responsible for keeping that data update accordingly (maybe even being reset to null). There's no need for any other sort of cleaning, because the Stores "having all the state" is exactly what they're supposed to be doing.
Stores are in charge of application state and logic, so for example, let's say you fetch all users through your ListAllUsersAction, you get an array from your API
var users = [{firstName: 'LIMELIGHTS'}, {firstName: 'SKWEE357'}];
Now, the users name are apparently capitalised as your API decides that this is the way to deliver the data.
This just won't do so you want to fix it.
Using just React or just the Action where would you put this code, where would it make sense?
In your view, your dispatcher or your action? No, you definitely don't want to clutter your React component with this type of logic.
Nor does it make sense to do this data manipulation in the Dispatcher or Action, they are after all just notifiers that something should happen.

Resources