This is a novel and original look into some of the unexpected behaviors in a React codebase and the anti-patterns that cause them. We discovered them while building a new full-stack web development framework on top of React. We published some of our learnings and received overwhelmingly positive reactions from developers worldwide such as: 1. Selected as the top article of the week by various newsletters 2. Developers voluntarily translated the article into many languages such as South Korean and Russian 3. Reached 100K+ developers with comments such as “Changed my day!” We wish to share these learnings with an even wider developer community through this conference.
(https://betterprogramming.pub/how-we-reduced-bugs-in-our-react-code-base-9a7a979b4442)
Speaker Bio
Darshita Chaturvedi is the Co-Founder & CEO of Atri Labs where she is leading the development of a new full-stack framework for web development. She has five years of experience working in Machine Learning and Data Science fields in various capacities from Graduate Research Assistant at MIT (USA) to Quant Researcher at BlackRock, a US headquartered asset management firm. She completed her undergraduate education in Engineering from IIT Kharagpur, India, and dropped out from graduate program in management at MIT Sloan to work on her startup full-time. Her favorite past times are painting and learning art history.
Check out more about React Native EU conference: https://www.react-native.eu/
and follow us on Twitter to stay up to date:
https://twitter.com/react_native_eu
Hello everyone. I am Darshaka, and I'll be talking about how to reduce bugs in a react code base. A little bit about Me I'm the maintainer of the Open Source Project A Threeengine, which is a full web development framework. I'm also the co founder and CEO of Atrilabs, which is the company behind this project. My journey to web development industry has been a bit unconventional. After studying engineering, I worked in Cornfinance and then moved to academic research at MIT. I later dropped out from my graduate program at MIT to work on my startup, Fulltime. One personal project that I'm very proud about is that I am editing and managing the publication of a set of novels written by my grandfather over the last five decades. Okay, so on to the top. As I mentioned earlier, we are creating a full stack web development framework. Using our framework, the front end of a website can be created using our visual editor or by react writing react code, while its back end can be written in Python. As you can see in this visual editor, there are numerous interactions on one page. For example, we can add components, style them, and move them around. Hence, it's important for us to understand and characterize antipaters in our large react code base, because suboptimal code in one interaction can affect the entire application. Moreover, the objective of our framework is to support building production grade websites. Consequently, building rigorous internal standards on code quality are very important to us. In this talk, I'll share the types of bugs we have identified in our code base and the guardrails we have built to avoid them in future. This discussion is especially important for software engineers who aspire to build similar applications with numerous interactions on one page. Other examples of such softwares include design software such as Canva, Sigma, et cetera. In this talk, we will cover how to prevent three types of unexpected code behavior. First component does not update upon a user event. Second component updates partially upon a user event that is, the previous state is still there. And third component renders unexpectedly while developing react applications. Such bugs often go unnoticed during manual testing, and setting up a pipeline for automated testing is difficult. While building our full stack web framework, we were encountering these bugs repeatedly. So our first resort was to use a combination of random print statements, react dev tools, and browser dev tools to fix these bugs one by one. However, we were also aware that we had to add interactive features in our editors at a rapid pace, so we could not go through this inefficient process of finding a cure when a bug occurs. Hence, we decided to review our code multiple times to instead find preventive measures. And this is the origin story of this talk. The central theme comes from official react documentation that our react code should be independent of the sequence in which components will render and when they will render. We use this theme to codify hints that are easy to remember and can lead us to identify antibodies. And we think that one way to visualize the React code is as a collection of interdependent pieces of code. Hence, if a code's dependency relationship cannot be established, then there is a high likelihood that it will result in bugs. So the key takeaway here is to be very cautious while using hooks such as usage userf that do not take dependency arrays. We will now consider an example app to demonstrate how this hint can help us to identify anti patterns. But first, let me clarify how to characterize a pattern. There are two considerations. First, the component is reusable, and second, the code is easier to review and debug. Note that even if we wrote more lines of code or added a few extra renders to achieve these two objectives, we will still consider our code to be following a good pattern. Let us now look at the example. The desired behavior of this app is that when an article is clicked in the left navigation menu, it appears on the right. This is followed by two actions computation. That is, the total character count of the article is calculated as number of characters in the title and plus the number of characters in text. And second is network request. Based on the total character count of the article, an emoji is fetched via a network request and displayed here. As the total character count increases, the emoji is expected to change from sad to happy. We'll get to the correct way of building this app in four steps. But first, let's take the incorrect approach. Initially. I'm on article One. The total number of characters in this article is ten, and it's visible here alongside the emoji. But when I select a new article, the corresponding text and title are updated, but the total character count and emoji are not updated. Let's have a look at the code. In line 17, we see the react component article content that appears on the right. In line 22, we can see that props have been passed as an initial value for usage. Since you state does not take a dependency array, there is no piece of code that can update the state variable. As a result, competition is not triggered. Moreover, in line 34 we see another interesting thing since this length is not updated, the callback to this used date hook is also not called. As a result, the network request is also not triggered. Let us now discuss the partially correct approach. When I select a new article, both the total character count and emoji are updated as expected. However, there is also this annoying flickering effect. One of the reasons in which we have observed that developers fixed the partial render problem in the previous approach is by using Destroy and Recreate, it removes the component from Dom and destroys all the hooks and states created during the first function call. This is followed by creating the component again from scratch. Let us now review the code. The only change here is on line 65. Here, in the parent of article content component, we are setting the key prop to the article ID. Hence, every time a new article is selected, the article content component is destroyed and recreated. Using this approach, we now get updated competition and network request results. However, this also results in this flickering effect. Let us now move on to the correct but suboptimal approach. Notice that when I change the articles, the flickering effect is no longer visible. To achieve this, instead of using Destroy and Recreate, we are using rerendering. rerendering refers to calling the react functional component again with the hooks and state intact across function calls. Note that in Destroy and Recreate, all the hooks and state are destroyed first and then recreated from scratch. To implement rerendering, we have made following changes to our code. In line 64, you can see that we are no longer using key in the parent component. Similarly, here we are using Usetate and Use effect in tandem and we are using Props as a dependency in the use effect. Thus, we are circumventing the lack of dependency array in USTATE through use effect. This approach is correct. However, it is suboptimal because the computation is being done every time Props change, which in turn starts another rental. Finally, let us look at the correct and optimal way of building this application. Recall that we went down this rabbit hole because we had used a hook with no dependency array. Instead of trying to make usedtate work somehow with patchwork, let us switch to a hook that takes dependencies. So in line 22 here, we have moved the total character count computation to reduce memo cook and thus we are able to prevent extra render. When we applied this refactoring to our framework, we were able to drastically reduce the number of US state per component. Our current status is at 00:35. To give you a context of the size of our code base, it has 112 custom hooks and 192 components. There are many other ways to avoid unnecessary use case. Here we are demonstrating this using a custom slider component. The code on the left has antibiotics. For example, there are four usetates and three callbacks. The start position state records the initial position of cursor. The code on the right is the restructured, the version of this code. Note that we do not have any use date and only one callback because we have used the local context of the callback to store all the ongoing information related to user interactions. Also, we have avoided the value state by converting the component into a controlled component. We are aware that there is an active debate on the use of controlled components, but we believe that the control components should be preferred when the number of interactions per page is high. The next hint for bugs was use of nesting for arranging components. Note that while using nesting, if you are not paying attention, we might pass down duplicate states to children. This may lead to inconsistencies of states between parent and children. Recall that our code should be independent of the sequence in which components will render. Moreover, there should be a single source of truth for each state. Otherwise it will result in bugs. The second thing to note here is that tracing changes in state is one of the most time consuming steps in debugging. Since nesting allows change in state at each level, it takes longer to debug. So, to summarize, be extra careful when you're using hooks without dependencies and while using nesting. Thanks for listening. You can find me on Social by this name.