Lamoda is a technically sophisticated product that is used by 10 million monthly users and has more than 100 internal subsystems. The tip of this iceberg is the online store interface, or frontend. Our team is engaged in the development and support of the UI of desktop and mobile sites, those parts of native applications for iOS, Android, which are made on WebView, as well as various marketing “additions” (these are banners and landing pages).
In our work, we, of course, use components. We come across all sorts of components, but there are some that can rightfully be called complex. Did you immediately think about fancy shapes or spectacular graphics? I will surprise you, but even a regular modal can create problems in development. About a year ago, the task of developing this seemingly simple component fell on my shoulders, which eventually resulted in three calendar months of work. Fortunately, from this awkward experience, I learned three lessons – three principles that I will share with you today so that you do not step on the same rake and make your work more effective.
I’ll make a reservation right away that we in the company use a stack based on Vue.js. But if you have React or Angular, this article will still be useful, since the principles I’ve highlighted are universal.
Principle # 1: “A component is a sandbox”
This slide shows a modal mobile window before and after the product has received a task to conduct an A / B test and improve UX. In the new version, the modal slides out from the bottom and does not occupy the entire screen. The user can swipe it at any time and return to the place where he started.
When developing this component, I had a question: how to make a swipe? It is necessary that the modal not only swipe up, but also follow the movement of the finger.
The usual galleries of our site work according to a simple principle: there is a sequence of slides that we switch with swipes. I decided to do something creative and offered to turn the gallery over, making it an impromptu two slides that take up the entire screen. One slide is empty, the other contains a modal frame. Thus, we can control the visibility of the window by swiping the slides.
Swiper is an unsafe environment for children
It would seem that everything works and everything is fine. But then problems began to arise …
We have a modal with an authorization window where the user logs into the service. Here is Google’s Captcha, which in turn uses a very hackneyed pattern – position: fixed. It is necessary in order to position yourself in the lower right corner.
The problem is Swiper uses CSS transform to move the slides. This means that now it becomes a containing block for the captcha, and position: fixed is calculated from its coordinates. As a result, our captcha, like a puppet, will follow Swiper’s movements and “fly away” from the screen. That is, her behavior becomes completely uncontrollable, which, of course, does not suit us.
And here is an example of another case. There is a gallery inside such a modal that also uses Swiper.
In order for the internal scrolling to work and there are no conflicting touch events, we disable the swipe inside the window and leave it on the upper darkened part. However, Swiper works in such a way that it intercepts touch events, and they no longer fall into the internal Swiper. As a result, we lose the functionality of the gallery and are forced to either disable the swipe or the gallery.
And then I came to the conclusion that using Swiper here is not the best solution. As a result, we got an intermediate layer between our component and its children. This led to the creation of various side effects that indirectly affect the positioning of elements, the ability to use components.
There is such a native solution as CSS Scroll Snap. Now this technology in our project is at the testing stage, but we expect that everything will work completely transparently and not affect our child components in any way.
Conclusion: Based on this experience, I realized that it is worth making sure that the components do not give any hints or side effects to their child components. This is also called the principle of orthogonality or low coherence in programming. This is especially important for basic UI components such as a modal window. If we keep track of this, we will get components that work like a clock, and are easier to reuse.
But there is also a downside to the coin. A “clean” solution like CSS Scroll Snap doesn’t always lie on the surface. It will take time to find it. Since this solution is native, it may not work everywhere. In this case, you will have to make compromises. For example, sacrifice such an important modal function as swipe.
Principle # 2: “A Fancy Feature Can Wait”
Before us is a modal filter window, which is overridden by another window with a choice of colors. It turns out that the top completely overlaps the bottom. And only a thin gray strip – an indicator – shows the user that there is another under one modal.
Yes, I managed to implement this feature, but the component’s code became much more complicated, because it was necessary:
- Keep track of the order of modals.
- Find out the height of the content.
- Create a store for data.
- Calculate visibility.
And in some cases, the indicator may not work at all. For example, in the window with the product gallery, which I talked about a little higher. The point is that the indicator is implemented as a :: before pseudo-element. The photo in the gallery uses the overflow: hidden property to crop its top edge. As a result, the pseudo-element is also clipped. That is, physically, our indicator remains in the document, but it is no longer on the screen. As a result, we lost our feature, and the release had to be postponed.
Yes, the feature is useful and cool, but not critical. It was quite possible to do without it. Well, it would be easy to implement it, so you still need to make a lot of efforts, to complicate the code base. And in the future, I decided not to step on this rake.
We had such a case when I was developing a full-screen window – an input field. And according to the designer’s idea, it was necessary that the input window was closed by pressing the done button on the on-screen keyboard.
And there was a problem: how to track this click in the browser? Native applications make it easy to work with this, but in the browser you will not track it in any way, except for crutches.
In order not to repeat past mistakes, I immediately went to the designer, and we came to a compromise solution: to make this button explicitly in the interface. Well, it turned out not very nice, but effective.
Conclusion: I realized that it is important to find out in time that a feature in a component is a fancy feature. Yes, it’s cool, but its implementation carries big challenges. In this case, it is better to find a compromise solution: simplify the feature or move it to the next iteration. Compliance with this principle will speed up releases, simplify the implementation of the component and, as a result, it will be easier to maintain and add new features in the future.
If you absolutely “bleed from the nose” and the feature needs to be done here and now, then it is worth resorting to the Git Successful Flow approach. In this case, we can poke around the develop branch with the component code without a fancy feature. This will allow at least not to delay the work of the team.
Principle # 3: “Don’t unite and rule”
Once our team lead came running and said: “Guys, Roma has made something so that the modalka can do everything now!” And I pumped it really well this way:
- Implemented maintaining old + new A / B test design.
- Cool features were added: overlap indicator, swipe, different animations.
- Now you can customize – support for different themes for different use-cases.
- Support for preloader and virtual lists is now possible.
As a result, our inflated modalk takes 700 lines of code, it has 15 props, 5 slots.
In my opinion, it turned out too much. And the situation could get worse. The fact is that in addition to the mobile modal, we also have a desktop one. And our plans were to get a responsive modal, XModal, which would be either mobile or desktop, depending on what screen is now. If we solve this problem, then we get a monster component. Namely:
- Another + 50% will be added to the code volume.
- Let’s cover ourselves with ifs, as a bunch of different conditions will be added.
- We will override mobile styles with desktop styles, which will lead to complete chaos in CSS.
- There will be conflicts in the codebase and, as a result, conflicts may arise between the mobile and desktop commands.
And I had the idea not to lump everything together, but to leave two modals, mobile and desktop, in separate files. And also bring their API to the same form and automatically switch to the required implementation without being noticed by the parent component.
And this is how it looks in the code:
At first, the team was skeptical about the fact that there would be copy-paste. But then the people appreciated and realized that when we have serious differences between version A and version B, it is better indeed – not to combine. And this approach can be useful for us for A / B testing or in the case of several environments (website and mobile application).
We came to a common solution in the form a functional component is a transparent glue that does not store any state, and it is not visible even in dev tools.
Thus, we have a proxy component and a selectorFunction, which encapsulates the criterion by which we choose one of the component implementations. If anyone has not guessed yet, then this structural proxy design pattern…
Conclusion: in the case of different implementations of a component, it is better not to combine them, but leave them to live separately. The Proxy pattern acts as a transparent glue that glues together different component implementations and leaves the client in the dark about how everything works there. Component logic is greatly simplified and there is no confusion in styles, and teams can work independently.
The flip side of the coin is that copy-paste cannot be avoided here. In some cases, there will be so much of it that you will have to abandon the use of this approach.
There may also be problems with instrumentation. For example, your favorite IDE will not be able to recognize the API because of the proxy screen, and will not help you with autocomplete.
Thus, don’t underestimate component design. Even a primitive modal can create serious problems in how to do it and how to implement it. The principles described in the article will allow:
- Make sure that there are no intermediate layers that would create overlaps, side effects for “children”.
- Recognize fancy features that are not critical, but rather difficult to implement.
- If there are several implementations of a component, do not lump everything together, but use the proxy pattern in order to glue the pieces into one common component.
Compliance with these principles helped me achieve my main goal – to distribute quickly, to maintain easily.