📜 ⬆️ ⬇️

Redux business logic

Once in the Telegram-chat React_JS (by the way, the Russian-language chat, join) discussed the question: "where in the React-application should be located the code responsible for the business logic." There are several options, opinions are divided. Well, I decided to record a podcast (by @PetrMyazin ).


Consider a particular example of business logic exclusively on the client. Application "loan calculator". The user enters into the form the initial data: amount, term of the loan, some other parameters; and on the fly gets the result, for example, the amount of overpayment. The whole cunning algorithm for calculating the amount of overpayment is known and already implemented as a JS function, let's call it f, which takes several parameters - the same data from the form, let it be abcd, and this function returns a numerical result (the amount of overpayment, we denote it as x) - this is our business logic.


Please note that the function is clean. Its result depends only on the input parameters. It does not produce any side effects, does not read from global variables. Also, this function can be called independent of the framework, because it will work equally in React-application, and in Angular, and in Ember, and in jQuery, and even on VanillaJS.


But we decided to build an interface on React + Redux, so now the question has arisen: "at what moment to start the calculation of our business logic." Once again I will emphasize that in this podcast we will consider only a simplified example - all calculations on the client, no requests to the server. In a real application, we will probably have some of the logic tied up to communicate with the server, but there will also be a part that can be completely calculated on the client from the data already available. So all further reasoning is applicable to the second part - calculations on the client.


Well, take a look at the form. It consists of: a pair of text fields, sliders, drop-down lists, checkboxes. The values ​​of these fields entered by the user will become input parameters to the function f. The first question: "where and how will we store field values". There are only two options: either in the local state of the components themselves (checkboxes, sliders, and the like), or in the Redux-store.


A local state, spread over several components, is inconvenient in this case, since we finally need to get all the values ​​at the same time to pass them abcd parameters to the business logic function. We would like to store everything in one place: either in a certain common ancestor (remember the lifting state up ) or in the Redux-store. Since the topic of this podcast is a review of business logic in a Redux application, we will store it in Redux.


Let's go through the steps of the whole chain of events and try to estimate at what point it is best to call the function of business logic f.


So, the user changes something on the form, enters a new number into the text input field, or moves the slider. A handleChange handler is running - this is the first place to invoke business logic. But in the handleChange handler, we can read only the value of the currently modified input field from event.target.value, and the rest of these forms are not yet available to us. Let me remind you, the function f is pure, it does not read any data from global variables. It only takes the abcd parameters, and returns the result x. This means that the handleChange handler is not suitable by itself.


The next step is the action-creator function. Here, using Redux-thunk , we can get the entire state object, i.e. get abcd to call f. Action-creator is a good challenger, remember it.


Go ahead. The flow of execution proceeds to the launch of all reducer functions. Reducers can be implemented in different ways. For example, we will prepare four separate functions for the field a, for the field b, for the field c and for the field d. In this case, each of the reducers has access to the previous state of its input field and to the action object. We, to start the function f, need all the abcd values ​​at the same time. With such an organization of redirectors, the only chance is if we forward all four abcd values ​​inside the action object. In fact, we thus transfer to the action object an almost complete copy of the store. It does not sound very much, I do not want to do that.


Another way of organizing a reducer is a single reducer that will update all form fields at once, a kind of formData reducer. It takes the previous state of the form fields as an object containing abcd, updates the changed value, and then starts the business logic function f. But wait, but where will we add the result of the calculations, that same amount of overpayment x? Being inside the formData-reducer, the only place where we can save x is to the same object, which now we will have five fields: abcd and x. It is just right to rename this reducer into formDataAndResult-reducer. He does everything: he remembers the change of form, and remembers the result of the calculation of business logic. All in one large object of five fields. It does not sound too. Too fat reducer, too much of everything. Redux sets us up on functional approaches, on the composition of the reducer. And here we are writing one big function to control everything at once.


Next, we run selectors in container components. Selectors are functions in mapStateToProps. We are interested in the selector for the component that displays the final result x. Yes, here you can make a call to the business logic function, since the selector has access to the entire state, and to the already updated state with fresh abcd values ​​(I’ll make a slight emphasis on this); and the result of calculating the function f, called inside the selector, falls into the props component, which represents the amount of overpayment, which is a working solution.


If not in the selector, then the last chance to call the business logic is to call the function f directly in the render method of the x component, the one that displays the overpayment amount. To do this, in props you have to drop all four required abcd values. In my opinion this is a less elegant solution than the previous one. Selectors appear to be the more appropriate place to run calculations, and pass an already prepared x for display.


We considered all the options. Well, almost everything. It was possible to discuss dumb methods, such as launching calculations in the component will receive props, or even think of something else. But let's not waste time.


So, we have two obvious candidates for launching business logic - this is the action creator and the component selector x.


Take a look at the action creator for details. First, we will answer the question: "we will have one action creator for all fields of the form, or a separate function for each of the fields". If we make separate functions, we get four action creaters: changeA, changeB, changeC and changeD; which will actually resemble each other like two drops of water. If we want to call the function f inside the action creator, these calls will have to copy all four functions into the code. A lot of copy-paste. Although, who works closely with Redux, the boilerplate code is not afraid. Here you can create a factory - create action creator, to avoid copying the code.


But I propose not to go into this direction, let's describe one action creator function better, it will accept fieldName and newValue. fieldName is a string variable denoting the input field into which the user has entered something. Using Redux-thunk, we can access the entire current state inside our action creator, which we need to call the function f.


But note that the f function should get the latest abcd values, and the state that we get thanks to Redux-thunk is already an outdated state. One of the fields has the old meaning. Before calling the function f, we need to understand exactly which parameter to substitute with newValue. From the point of view of JS-syntax, you can come up with a dozen elegant and not very solutions. But the fact remains that if we want to call f inside the action creator, we need to take the current state, i.e. already a bit outdated, and merge it with the newValue just arrived. Does this not remind us of a reducer function that does exactly the same thing? It turns out that we have to duplicate the logic of the reducer inside the action creater to form the latest abcd values.


Further more. Suppose we get the result of calling the business logic function inside the action creater, but now we need to bring the resulting value of x to the component that represents the amount of the overpayment. We'll have to forward through the Redux store, writing the appropriate reducer and selector. Having done this, note that the store now stores both the abcd form data and the result of the x calculation at the same time. Store turned out to be redundant. In addition to the original data, it also has the result of derived calculations. This is not good for many reasons. Some of them we discussed in previous releases. Conclusion: the action creator is a poor choice for calling a business logic function.


We proceed to the variant with calling the business logic function from the selector. I already mentioned his mechanics above. The component selector that displays the overpayment amount has access to the entire store. And to the most current values ​​of abcd. No duplication of the reducer code. Having abcd inside the selector, we call f, and pass the result x as props to the component that represents the overpayment amount. Simple, logical, without duplication of code and without redundant state. A selector is a great place to call a business logic function, which is represented by a pure function of data.


Focusing on the last phrase that we considered only the calculation on the client. If your business logic is not a pure function, but a whole process, with a trip to the server, i.e. with some side effects, this is a topic for another conversation. Here you just need to pay attention to the action creator, and the middleware. But let's discuss it another time.


Write on React and prosper!


PS There are sharp questions, answers in the next podcast .


')

Source: https://habr.com/ru/post/332146/


All Articles