Understanding React Interceptors

Original author: Dan Abramov
  • Transfer
Hi, Habr!

With a sense of incredible pride and relief, we have put a new book about React in the printing press on

this issue. In this regard, we offer you a slightly abridged translation of the article by Dan Abramov about the use of interceptors in the 16th version of React. In the book, which we ourselves are already looking forward to, this is discussed in the 5th chapter.

Last week, Sophie Olpert and I presented the concept of “interceptors” at the React Conf conference, followed by a detailed discussion of the topic by Ryan Florence .

I strongly recommend that you watch this plenary lecture to familiarize yourself with the range of problems that we are trying to solve with the help of interceptors. However, I appreciate even the hour of your time very highly, so I decided to briefly present in this article the main considerations on interceptors.
Note: React interceptors are still experimental. No need to delve into them right now. Also note that this post contains my personal views, which may not coincide with the position of React developers.
Why are interceptors needed?

It is known that component organization and downstream data flow help organize large UI in the form of small, independent and reusable fragments. However, it is often not possible to break up complex components beyond a certain limit, since the logic saves the state and is not removable into a function or some other component . Sometimes those who say that in React it is impossible to achieve “separation of duties” complain about this.
Such cases are very common, and are associated, for example, with animation, form processing, connecting to external data sources and many other operations that we may need to perform with our components. Trying to solve such problems with the help of components alone, we usually get:

  • Giant components that are difficult to refactor and test.
  • Duplication of logic between different components and methods of the life cycle.
  • Complex patterns , in particular, property rendering (render props) and higher order components.

We believe that interceptors are the most promising for solving all these problems. Interceptors help organize the logic inside a component as reusable, isolated units :

Interceptors are consistent with the React philosophy (explicit data flow and composition) and within the component, not just between components . That is why it seems to me that interceptors fit naturally into the React component model.

Unlike patterns such as rendering properties or higher-order components, interceptors do not burden your component tree with unnecessarily deep investments. Also, they do not have those deficiencies that are inherent in impurities.

Even if at first glance interceptors are jarring on you (just like me at first!) I recommend giving this option a chance and experimenting with it. I think you will like it.

Is React swelling due to interceptors?

Until we look at the interceptors in detail, you may be worried that adding interceptors to React is simply multiplication of entities. This is a fair criticism. I think this: although in the short term you really feel an extra cognitive load (to study them), in the end it will only become easier for you.

If interceptors take root in the React community, then the number of entities that have to be controlled when writing React applications will actually decrease.. With the help of interceptors, you can constantly use functions, and not switch between functions, classes, higher order components and component rendering.

With regard to increasing the size of the implementation, the React application, with the support of interceptors, is increased by only about ~ 1.5kB (min + gzip). While this in itself is not too much, it is very likely that when using interceptors, the size of your assembly will even decrease , since interceptors' code is usually minified better than equivalent code using classes. The following example is slightly extreme, but it clearly demonstrates why this is all so ( click to expand the whole thread):

There are no revolutionary changes in the proposal for interceptors. The code you have will work fine, even if you start using interceptors in new components. In fact, this is exactly what we recommend: do not rewrite anything globally! It will be reasonable to wait until the use of interceptors is settled in all critical code. Still, we will be grateful if you can experiment with the alpha version 16.7 and leave us feedback on the proposal for interceptors , as well as report any bugs .

What is it - interceptors?

In order to understand what interceptors are, you need to go back a step and think about what code reuse is.

Today, there are many ways to reuse logic in React applications. So, to calculate something, you can write simple functions and then call them. You can also write components (which themselves can be functions or classes). The components are more powerful, but when working with them you need to display some UI. Therefore, it is inconvenient to transfer non-visual logic with the help of components. So we come to complex patterns like property rendering and to higher order components. Wouldn't React be easier if there was only one common way of reusing code, and not so much?

It seems that the functions are ideal for reusable code. Transferring logic between functions is less expensive. However, the local React state cannot be stored inside functions. You cannot extract behavior like “monitor window size and update state” or “animate value for some time” from a class component without restructuring the code or introducing such abstractions as observable objects (Observables). Both approaches only complicate the code, and React is pretty nice to us.

Interceptors solve exactly this problem. Thanks to interceptors, you can use the capabilities of React (for example, a state) from a function — by calling it only once. React provides several built-in interceptors that correspond to React “bricks”: state, life cycle, and context.

Since interceptors are normal JavaScript functions, you can combine the built-in interceptors provided in React to create your own interceptors . Thus, complex problems can be solved with a single line of code, and then multiplied in your application, or shared in the React community.

Attention: strictly speaking, your own interceptors are not among the React features. The ability to write your own interceptors naturally flows from their innermost organization.

Show the same code!

Suppose we want to sign a component to the current width of the window (for example, to display other content or a narrower viewing area).
Such code today can be written in several ways. For example, make a class, create several life-cycle methods, or maybe even resort to property rendering or use a higher-order component if you expect to be reusable. However, I think nothing compares to this:


If you read this code, it means that it does exactly what is written in it . We use the width of the window inside our component, and React redraws your component if it changes. It is precisely behind this that interceptors are needed - to make the components truly declarative, even if they contain a state and side effects.

Consider how you could implement this own interceptor. We could use local state React, to keep it current width of the window, and with the help of side effect is a condition set when the window is resized:


As shown above, the built-in hooks React like useStateand useEffectserve as "building blocks ". We can use them directly from our components or to collect them from their own hooks, for example useWindowWidth. Using your own interceptors is no less idiomatic than working with the built-in React API.

Read more about the built-in interceptors described in this review .

Interceptors are encapsulated — whenever an interceptor is called, it gets an isolated local state inside the component that is currently being executed . In this particular example, this is not important (the width of the window is the same for all components!), But this is precisely the strength of the interceptors! They are intended to separate not the state, but the logic that preserves the state. We do not want to break the downstream data stream!

Each interceptor may contain some local state and side effects. You can transfer data between multiple interceptors, just as is usually done between functions. They can take arguments and return values ​​because they are JavaScript functions.

Here is an exampleReact animation library, where we experiment with interceptors:
Notice how the stunning animation is implemented in the demonstrated source code: we pass values ​​between several of our own interceptors within the same rendering function.


(This example is discussed in more detail in this guide .)

Thanks to the ability to transfer data between interceptors, they are very convenient for translating animations, data subscriptions, managing forms and working with other state-saving abstractions. Unlike rendering properties or components of a higher order, in the case of interceptors, a “false hierarchy” is not created in your rendering tree.. They are more like a two-dimensional list of “memory cells” attached to a component. No additional levels.

What about classes?

In our opinion, own interceptors are the most interesting detail in the entire sentence. But in order for their own interceptors to work, React must provide at the function level the ability to declare state and side effects. This is exactly what allows us to do built-in interceptors like useStateand useEffect. Read more about this in the documentation .

It turns out that such built-in interceptors are convenient not only when creating your own interceptors. They are also sufficient for defining components as a whole, since they provide us with the necessary capabilities — for example, a state. That is why we would like in the future interceptors to become the primary means for determining React components.
No, we do not plan to gradually eliminate classes. We use tens of thousands of class components in Facebook and we (just like you) absolutely do not want to rewrite them. But, if the React community starts using interceptors, it will be inappropriate to keep the two recommended ways of writing components. Interceptors cover all those practical cases in which classes are used, but they offer greater flexibility in extracting, testing, and reusing code. That is why we associate with interceptors our ideas about the future of React.

What if interceptors are magic?

The interceptor rules may be puzzling you.

Although it is not customary to call the interceptor at the top level, you yourself probably would not want to determine the state in the condition, even if you could . For example, a condition linked to a condition cannot be defined in the classroom, and for four years of communicating with React users, I have not heard any complaints about this.

Such a design is crucial for the introduction of your own interceptors without the simultaneous introduction of excessive syntactic noise or the appearance of pitfalls. We understand that being unaccustomed is difficult, but we believe that this compromise is acceptable, given the possibilities it offers. If you do not agree - I suggest experimenting yourself and trying out how you like this approach.

We have been using interceptors in production for a month to check if programmers will be confused by the new rules. Practice shows that a person is mastered with interceptors in a matter of hours. I confess that at first glance these rules seemed to me to be heresy too, but this feeling quickly passed away. That was the impression I had when I first met React. (You didn’t like React? I only liked it a second time.)

Please note: there is no magic at the interceptor level either. As Jamie writes , it turns out like this:


We maintain an exploded list of interceptors, and proceed to the next component in the list every time after using the interceptor. Due to the rules of interceptors, their order is the same in any rendering engine, so with each call we can provide the component with the correct state.

( In this article by Rudi Yardley, everything is beautifully explained in pictures!) You

may have wondered where React stores the state of the interceptors. In the same place where the state of classes. React has an internal update queue that holds the ultimate truth for each state, no matter how you define your components.

Interceptors are not dependent on proxies and getters, which are so common in modern JavaScript libraries. Therefore, it can be argued that there is less magic in interceptors than in other popular approaches to solving such problems. No more than in array.pushand array.pop(in the case with which the order of calls is also important!)

The design of interceptors is not tied to React. In fact, already a few days after the publication of the proposal, various people showed us experimental implementations of the same interceptor API for Vue, web components, and even for ordinary JavaScript functions.
Finally, if you are fanatically devoted to functional programming, and you feel uncomfortable in your heart when React begins to rely on a changeable state as an implementation detail. But, perhaps, you are comforted that the processing of interceptors can be fully realized in a pure form, limiting it only to algebraic effects (if they were supported in JavaScript). Naturally, at the intra-system level, React has always relied on a variable state — and that’s what you would like to avoid.

No matter what point of view is closer to you - pragmatic or dogmatic - I hope that at least one of these options seems logical to you. Most importantly, in my opinion, interceptors simplify our work, and it becomes more convenient for users to work. This is what interceptors bribe me so much.

Also popular now: