What should be simpler, actions or reducers?

This is one way to record actions when using thunk, which leads to very simple abbreviations.

getCurrentUserPicture(){ return (dispatch,getState) => { dispatch({type: "isloading", isLoading: true}); // shows a loading dialog dispatch({type: "errorMessage"}); // clear errorMessage dispatch({type: "warningMessage"}); // clear warningMessage const userId = getState().user.get("currentUser").id; getUserPicture(userId) // get from backend .then(picture => { dispatch({type: "userPicture", picture}); dispatch({type: "isLoading", isLoading: false}); } ) .catch(e=>{ dispatch({type: "errorMessage", e}); dispatch({type: "isLoading", isLoading: true}); } ) } } 

With gearbox, including the following:

 export reducer(state = initialState, action = {}) { switch(action.type) { case "isLoading": return state.set("isLoading", action.isLoading) 

Here is another approach when the actions are cleaner, but the gearboxes are more complex:

  getCurrentUserPicture(){ return (dispatch,getState) => { dispatch({type: "gettingCurrentUserPicture", true}); const userId = getState().user.get("currentUser").id; getUserPicture(userId) .then(picture => { dispatch({type: "retrievedCurrentUserPicture", picture}); } ) .catch(e=>{ dispatch({type: "errorRetrievedCurrentUserPicture", e}); } ) } } 

In the gearbox for the above action you will have, for example:

 export reducer(state = initialState, action = {}) { switch(action.type) { case "gettingCurrentUserPicture": return state.set("isLoading", true) .delete("errorMessage") .delete("warningMessage") 

Is one approach better than another?

+10
immutability reactjs redux redux-thunk thunk
source share
4 answers

Why not both?

Actions and reducers should be as simple as possible.

This is easier said than done, but by introducing other concepts and patterns, such as a selector , sagas and even simple utility functions / classes , complexity can be significantly reduced for both actions and gearboxes.

Actions

I know that the term “broadcast” is really puzzled in the world of streams, but I find that it helps me clarify what should belong to my actions. The action should "translate" into the world what has just happened - he personally does not care about what should be observed, or about how many gearboxes prefer to react to it - his is simply a messenger. i.e.: it doesn’t matter how the application looks after the specified action.

My opinion is that business logic / rules either belong here directly, or can be routed here through auxiliary service functions. See the sections on async and the utility below.

He must describe what happened. Describe, describe, describe

Adapters

Gearboxes should form a complete (ish) representation of your application with a minimum amount of data. Whenever possible, keep your state tree normalized to make sure you keep the minimum state.

My opinion is that they should be as light as possible, and at best it should be just basic manipulation of objects - adding / removing keys or updating values.

What should the state look like on the basis of the description that I just received? (from action)

An approach

It sounds crazy, but debugging a rubber duck (in this case, programming a rubber duck) really helps in planning the structure of the reduct.

I will literally say (sometimes even outloud) through steps, for example:

  • Hover over the message heading and click edit
  • The application goes to the "Edit Message" page.
  • You can edit the header field (and the field will be updated)
  • You can edit the body field (and the field will be updated)
  • The application will save every 90 seconds as a draft, the save icon will be displayed in the upper right corner during automatic saving, but you can continue to work
  • You can also save by clicking the save button at any time. During this time, you will not be able to edit, and you will be redirected to the index page.

This can be easily translated into actions and gears by looking at what describes and what is, as a result, a state change:

  • Click "Edit": "Action". We describe for the application in which the entry with the identifier X was clicked.
  • Switches to the "Edit Message" page: Reducer. When we “hear” “message editing,” we change the state of the application so that it now displays the message editing page.
  • Change name / body: Action - We describe which field and what value is entered by the user.
  • Update name / body: Reducer - The application state has been changed in accordance with the entered fields in
  • Auto save: action / gear / action / gear
    • Action: Describe the application in which autosave started.
    • Gear: The state of the application changes to indicate that autosave is in progress.
    • Action: describe completed autosave
    • Reducer: the state of the application is changed to hide the autosave operation.
  • etc.

What I try to do, although for a long time from time to time, is that its not something that should be simpler, its where there are things (kind), but most likely, you will see that your actions in end up becoming more complex with your gearboxes to a lesser extent.

But my actions are still not subtle ..

All of the above is simple enough to say, but how do you keep your business activities clean / slim / light / simple / etc?

At the end of the day, most business logic has little to do with React / Redux - so you can usually dig it into your utility functions or classes. This is great for several reasons: a) it’s easier to check because it does not have a zero binding to React / Redux and b) it keeps your actions light, and c) it is more encapsulated - with minimal knowledge about reaction / reduction - this is not bad.

All this is just Javascript at the end of the day - there is nothing wrong with importing custom business classes or utility functions.

But but what about async ..

Async usually begins to clutter up actions very quickly. The saga is really worth a look if you want to clear your actions, but enter a certain learning curve if you are not familiar with generators and something else. In this case, thunks are still useful and remember that you can combine several asynchronous functions into one promise that thunk can play (which again, that a grouped promise can be divided into a utility function class, for example generateSaveSequencePromise() , which can wrap 4 or 5 asynchronous samples and return one promise).

Lateral note - you should minimize sending several times from one stream, as in the first example. If possible, try creating a parent action that combines the entire action into one. Therefore, using your first example, this could be:

 // action dispatch({type: "FETCHING_USER_IMAGE", id: userId }); 

And then your various reducers should clear their message queues or something else if they "hear" this type.

+3
source share

There are advantages / disadvantages if you go anyway. But my options are to simplify reducers than action / action creators.

Processing business logic in gearboxes (simplifying actions)

Perform all your business logic synchronous in your gearboxes, simplifying the actions / actions of the creators.

Benefits

  • Based on your business logic, you can decide how your next application state is. should be.
  • Easily handle business logic in gearboxes.

disadvantages

  • You can only perform synchronous tasks (but there are intermediaries that support asynchronous tasks).
  • No access to dispatch .
  • You cannot access the entire state of the application if you split gears.

Processing business logic in Action Creators (simplifying simplification)

You can simply execute some business logic and initiate an action whenever you want.

Benefits

  • You have access to dispatch .
  • You can perform asynchronous tasks (see redux-thunk ).
  • The creators of Action have access to the whole state ( redux-thunk ), even if you combined reducers.

disadvantages

  • There is no easy way to have business logic that works on the basis of all actions. For example, if you want to attach an identifier for all actions, then you must have a function associated with all actions.

It is discussed in detail here .

+2
source share

If you have a complex actionCreators that causes a lot of small mailings with small actions, you gradually advance to your state, actually having “setters” (each action becomes only a setter for a certain field). If actionCreator has widespread effects, you have to send "setter" actions that are related to various different sub-reducers - you push domain knowledge for these different sub-reducers into the creator of the action. When you change the sub-reducer, you will need to find all the action creators who submit the related actions and configure them accordingly.

With bold actions that simply indicate what happened (for example, a request was made, a successful response was received, a timer shot, a button was pressed), you can manage changes in business logic without changing actionCreators. For example, you decide that you want to start displaying the boot masks during operations - now you do not need to update all the creators of your actions to start the Start and loadEnd boot actions, you just update your reducers to set the load state accordingly.

Personally, I think that the best model for a complex application is to make actionCreators trivial (or completely drop them, and just send the payload directly from your connected components) and use the redux saga ( https://github.com/yelouafi/ instead) redux-saga ) to handle your asynchronous and unclean things is a much more powerful model. For example, it simplifies the execution of actions, such as debouncing and throttling operations, reacts to changes in the state of the storage, as well as actions, etc.

+2
source share

I agree with @TomW that submitting many small actions causes the actions to become setters.

The creators of Action are initially simple functions that form very simple objects that are then dispatched.

Sending an action means sending it down the pipeline to be processed. The responsibility for carrying out the action lies with the gearbox. The action itself should not be performed. Instead, it should be a simple descriptor of what needs to be done.

The creator of the asynchronous action is an afterthought that requires aftermarket parts to be installed for normal operation. It should be asynchronous and as close as possible to the idea that actions should be performed in reducers, and action objects should be descriptions of actions.

You can give someone a showdown and call it source code. Of course, this is the source code. But it is much lower than the level of the real source and contains much less information. The initial statements are high-level and therefore contain information about intentions, and not just information about how to get there. Variable names, for example, do not exist during disassembly.

In the same way, sending multiple actions that do not convey intentions on their own is not suitable for Redux.

In fact, the action should always describe the intention and leave as many implementation details as possible.

Redux doesn't just take action. Middleware can record and return them. You can view the story and see what happened.

If actions do not include the intent or reason why they exist, then the story is just an ordered set of changes, and the type of action can be obtained simply by comparing two consecutive states and checking which property has changed. It just shows that the type of action is pretty pointless. The type of action should hold most of the value.

The creators of asynchronous actions are a bit of a shred. What is sent is not pojo, like Redux credentials, but a function. The fact that it is being sent is not recorded if you use the Redux Dev tools. It cannot be reproduced. Thus, the creators of the asynchronous action do not create the action.

In addition, the search for an invalid state should allow you to find the action that puts this state, and from there the code that processed this action. This is the gear code. But if the real problem is not in the reducer, but in the creator of the action that sent the action, then it will be much harder to find, because he did not understand which one sent the action.

(Ab) using asynchronous actions to implement business logic means giving up some of the most important benefits of using Redux.

+2
source share

All Articles