Last time I talked about workflow and scrum in our latest big web app project.
Today, I’d like to share on what were our initial assumptions that served as the starting point, the process of learning and taming React, and how we treated React throughout the project (or rather how React treated us).
Approach to the React/Redux states
We started with putting some facts about React in place. Remember, it’s not like we didn’t touch React AT ALL before. Still, on our team, I had the most experience with React, while others were more experienced in software engineering per se.
From the beginning, we established that we’ll use React to do things it’s the best at - presenting data to the user (view layer). To keep data transformations separate from the presentation we decided to use stateless functions and manage all user and API interactions through Redux, maximizing separation between view layer (user interface) and the business logic. We wanted React to take care of capturing user behavior and putting it into a proper action and Redux to handle action that React sent. Thus, the app state in its entirety was stored in Redux.
This helped us avoid the unstable React state. The superiority of Redux state over React state is especially easy to notice when reproducing an accurate app state for the purpose of testing. Redux state can be easily dumped into JSON file and reproduced to see what was the error user encountered. Also, thanks to Redux Event Bus we can also see which actions were performed by a user before his error occurred.
Actually, when you think about it, it's a paradigm standing in the opposition to the current trend in backend programming, which glorifies stateless servers. But IMO more and more developers are keen to use event-bus / event-wise servers rather than stateless servers, because tracking user activity over time and being able to adjust response to past responses gives more possibilities than stateless approach. Also, just like on the front-end side, it's very helpful in debugging and recreating user cases that failed.
Still, there were cases in which sticking to Redux state would be highly inefficient and counterproductive. Sometimes we wanted to have programmable key navigation, just like in Excel, or when a user had to fill long forms with many simple fields included, and, by using Redux, every form field had to have its separate action and reducer case. With React, it is all easier and faster because all changes are kept in one file (view) instead of Redux's three (view, action, and reducer). Moreover, variable only needs to be initiated in the view file's beginning and then you just need to change variable's value through
setState, which is much faster than dispatching Redux actions.
We had many discussions whether to use React or Redux state, where we touched on topics like time of development, performance, testability and so on. Even though some of us weren’t entirely convinced to stateless functions, this approach made a lot of features and bug-fixes very easy to handle. We could easily find the place where logic should be placed, we had our whole state in one place so we could see which data was downloaded and see whether it’s correct or not.
Hell-bent on Using ESLint
In my previous projects in React, which I did on my own, I very quickly started to use ESLint, mainly, because I’m a fanatic of the clean and pretty code. ESLint made keeping these things in place and that’s why I wanted to introduce it in this project right from the beginning. However, since I had very tough rules, which disallowed you to even see the application without making the code clean, team wasn’t really happy about my choice. And I can understand it - you try to learn new technology during a hard and serious project and on top of that you have a tool of any kind that prevents you from running the code you just wrote until it’s crystal clear.
Thankfully, many meetings, discussions and raised arguments finally resulted in a satisfying compromise. We made the rules more forgiving transforming more common errors into warnings. That way we still got all information about wrong code but we also could run the application. We also stuck to the rule to not send any pull request as long as there are any warnings or errors.
This helped us to create crystal clear code, thus everything was lighter, faster and more optimized. It was very helpful in some debugging cases. We could easily see variables used in code that we forgot to initialize or that we shadowed some arguments with other variables. Also, when we wanted to get rid of duplicated imports, all we needed to do was to add a new rule and all the places that needed refactoring were visible right away.
Code Review, Generic Programming, and Infinite Refactoring Loop
As mentioned, most of us had no previous experience in heavy caliber React projects. But, our whole team is composed of professional software engineers and developers with many years of experience.
That two simple facts transformed what "normal" code review process into a hardcore code studying sessions. Instead of senior speaking with junior about his piece of code, we all sat down and brainstorm over the smallest pieces of code included in given merge request. Which is, by the way, a good way to conduct code review in general. Truth is, programming, as an activity, stays the same no matter what technology you use. You’re required to solve a problem in the most effective way.
So, without a high knowledge of the technology, we thoroughly tested the possible applications of the technology and its quirks. To be honest, it was a very educational and valuable experience. In time, it also helped us code in a very generic way - we started to export more and more components that we found mutual for different parts of the code.
Our rising skill in React brought us to a state in which we really wanted to go back to the initial parts of the code that were done at the beginning. We had one whole sprint dedicated to refactoring but it is always not enough. You know you can do it better. However, it can easily lead you into a phenomenon that I call an Infinite Refactoring Loop.
So you have a code that can be roughly divided into three pieces - A, B and C. Let's say that all pieces were done chronologically, one by one in that order. After finishing the piece C you wish to come back to piece A; you know the technology better now, you already picked some very clever solutions and methods that can be applied to piece A and B. So you go back to refactor it. And, surprise, surprise! You found other clever ways to solve some issues. And they’re not present in piece C, the one you finished coding just before the refactoring.
My advice? Think about whether you will use this code in the future. If the answer is “Yes”, refactor it. If not, put it away until you acquire a business case that leads you back to the first component and then perform the refactoring.
What comes next?
Next time I'll talk more on and the architectural choices we made in this project as well as some of the libraries we used and can wholeheartedly recommend to you.
If you missed the opening part of these series and wish to read it, here it is.