Firstly, I'm not entirely sure what is the ## prefix in the actions which most third-party packages I've installed in my React/Redux app mean -- if they mean "private", then the answer to my question is obvious. I see them in the Redux devtools extension for Chrome.
In case it's just a scoping convention ##<package>/<action>, then I have a concrete question regarding actions in redux-form package. Actions are, apparently, not documented, so I'm not sure if I can safely use them in my reducers without worrying for breaking changes.
For more context in case my approach is completely wrong (still figuring out patterns): I want to remove the register form and present a success message after successful completion. redux-form dispatches an action of type ##redux-form/SET_SUBMIT_SUCCEEDED, with meta.form telling me that it was a form I named register. So I'd use it in my RegisterPage reducer to set a boolean isSuccess and use that to change the view.
If they are not explicitly documented I would consider them private and not use them directly. Even though it's not stated officially anywhere, the ## prefix often indicates private actions which shouldn't be handled by other reducer code. I think it originated from the ##redux/INIT_{random string} action (discussion about the naming can be found in this github thread):
In the new docs (#140) we should clarify that any actions prefixed
with ## are not meant to be handled. For example, you should never try
to handle ##INIT.
In the redux code itself the privateness is also explicitly stated:
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
For your specific problem: you could provide a onSubmitSuccess function to the HOC and dispatch your own action to update your own reducer or you could directly depend on the redux form state and get this boolean flag via the hasSubmitSucceeded selector. Personally I think the second option is the better one because it doesn't introduce redundant state in your store - redux form already stores the information whether a form was submitted successfully, doing so on your own in another sub-reducer could lead to the two boolean values diverging unintentionally.
Related
It seems to me the trend is replacing Redux with React Context API and useReducer.
I personally do not like a huge store at root with states from user data to if a dialog is opened all mixed together.
Switching to React Context API allowed me to push the state down and closer to where they will be used. However I can only push it down to the level that child components are either displaying or modifying that value. For example:
<Parent>
<CounterDisplay/>
<CounterIncreaseButton/>
</Parent>
I have to have count on Parent and create a Context. Within the Context, I'll add the count value and an increaseCount method (or a state and a dispatch function when use reducer pattern). Then I provide the context to those 2 child components. Now one can display it and one can modify it.
Now what if I need another button located far from this part of the component tree that also need to change the count value? I have to lift the state up and maybe all the way to the root. That feels strange to me.
2nd issue is when states are scattered at multiple level along the path in the tree, when something happens say user click a button, you may need to call multiple functions from multiple contexts (or dispatch multiple actions, one for each state that may or may not change). Unlike when use Redux since everything is at the root, you just need to dispatch one action.
So what if instead I have an event pub/sub at the root level? I can have the counter state and code manipulate it pushed down even more to CounterDisplay. CounterDisplay need to subscribe to the pub/sub system and listen to the event and update counter correspondingly. And whichever component want to change the counter can just raise an event.
What am I missing in this pattern? Circular event loop? Raise conditions? Feels a good pub/sub library can prevent these. I looked around and did not find something existing. I looked at RxJS but don't feel that fits.
Thanks in advance for sharing your thoughts.
You've basically just described the exact reason for Redux to exist :)
Redux is "an event pub/sub at the root level", and can specifically be beneficial in cases where widely separated components need to make use of the same data.
You may want to read my post Redux - Not Dead Yet!, which describes how Redux fits into today's ecosystem (including comparisons vs context) and some of the reasons you might find it useful.
If you are put off redux by the amount of boilerplate.
I would suggest taking a look at redux-zero.
Under the hood. react-redux uses a context provider at the root.
When you use the connect Higher-Order-Component that is using the context.
Same with the redux hooks.
So it's quite normal to have the provider at the very top.
It would be the same for any library like the react-router to do the same.
I would suggest you keep trying without redux so you can learn more. Put the provider at the root. It won't impact performance in anyway.
Isolate the context you create and the provider to a singleton file and import it to the components you need
Each big components get its own list of actions. Separation of files implies that they are isolate actions. But from my understanding if a type of an action matches the type of another action in completely separate file meant for different reducer will still cause problem.
EDIT:
if i have two sections in the app. One has reducer for action SET_SCROLL, and other section has that too. If i update scroll position in section 2, by firing SET_SCROLL. This would cause section 1's state to change. Now imagine 100s of actions, how do you prevent naming conflicts? I understand in redux you can't associate set of actions with a certain reducers.
Yes, this is why you should be careful when defining action types. The "Reusing Reducer Logic" docs page gives examples of how this can be a problem when you want to reuse a given reducer in more than one place, and shows some ways around that.
We specifically recommend defining action types as "domain/someAction" to help avoid clashes.
Note that it's also possible (and recommended) to have many different parts of the reducer logic all independently respond to the same dispatched action
In Redux why should keep the action file and action type separately. There is an action type which is already there inside the action Object then why again separate file for type?
While you don't technically need to have a separate "types" file for defining action types, it's a pretty common convention. From the Redux docs (https://redux.js.org/recipes/reducingboilerplate#actions) they list a number of benefits to this pattern:
It helps keep the naming consistent because all action types are
gathered in a single place.
Sometimes you want to see all existing actions before working on a new
feature. It may be that the action you need was already added by
somebody on the team, but you didn't know.
The list of action types that were added, removed, and changed in a
Pull Request helps everyone on the team keep track of scope and
implementation of new features.
If you make a typo when importing an action constant, you will get
undefined. Redux will immediately throw when dispatching such an
action, and you'll find the mistake sooner.
In the end it's up to you on how you decide to implement things in your project.
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.
I am tempted to add error data to the store. For example,
var store = {
error: {msg:'',info:{}},
others: '',
etc: ''
}
On an error in the app, an action will update the error via the dispatcher, and the error panel will be shown to the user. The render of the error panel conditionally shows the div by testing the error msg state.
On the next user input, an action, i.e., userAction, the model state will be updated by the dispatcher. Problem: the error panel will still be shown, since the error msg state was not 'reset'.
The userAction will be setting other non-error state. Flux will emit a change for this change. Yet, if I follow the Flux model, a reset of the error should also be done here, in this action, but that would cause an emit, which tells to UI to update. Seems incorrect.
My thinking is:
1. Don't put this kind of stuff in the store? Or,
2. The store will reset the error state for every non-error update of state. Or,
3. Each action will also include an error state object for any state updates.
Currently my solution is to clear the error data within the store functions:
}, function(payload){
API.setError({msg:'',info:{}});
switch(payload.actionType){
case "BRANCH_SELECTED":
What is the non-idiotmatic way of doing this?
I'm new to React and Flux, so I'm sure this is newbie question. I'm using McFly as Flux implementation.
Though your question may have already been answered within the comments: I meditated on a similar question in my current React project and so I'm going to share my experience and outcome. I'm using fluxxor instead of McFly but that shouldn't matter here.
As flux stores should contain all application state and logic, I came to the conclusion that it's absolutely okay and in the sense of the flux architecture if you programmatically clear your error states conditionally within your store functions.
In my understanding it makes sense to keep error state handling related to a specific store within exactly that store (and therefore probably received and rendered by few listening components). As mentioned by #fisherwebdev, the store logic should determine the state of an error, specifically based on the action types it registered callback functions to. In your case, think of a BRANCH_SELECTION_ERROR type action being dispatched that causes error state to be set. On the other hand the BRANCH_SELECTED action type should always clear this state.
My concrete solution is in fact to call "private" store functions clearErrorMessages() or clearFormValidationMesssages() which simply clear state variables dependent on the actions being currently dispatched.
Global errors, i.e. errors that are somehow related to the application state like server communication timeouts, may go into some "appStore" and being updated or cleared in a comparable way. So e.g. router transitions may cause global error state to be cleared.