As the goal here wasn't to design an UI, but to develop it using React.js, searching around I found an amazing redesign concept by Gregoire Vella that was pretty close to what I had in mind. Inspired by his work I coded the interface proposed. Here is a quick video of the final result:
I'm writing this article to share a quick overview of the development process, walking through some challenging parts, explaining some decisions I made on the way, some of the troubles and the learnings during the whole process. I'm hoping this article can help someone in some way.
Before jumping into the interface, the first task in my To-Do list was to create an API able to serve the data in the structure that I would need them. I didn't need to create it from scratch using a real database since it wouldn't be necessary to insert/update or delete any data — I just needed an API returning all the data that I would use. To achieve that, I used the awesome package json-graphql-server which does an amazing job creating a GraphQL API with static data that can be stored in a simple JSON file on the server — I just realize that the package name does a pretty good job describing it.
The only inconvenience I had with the API was that json-graphql-server, unfortunately, could not easily handle a scenario where a collection refers to multiple items from another collection. For instance, a "Recommendation" system, where users can write a recommendation to a friend and also receive a recommendation from someone else. In this case, we would have the author and the target fields, both referring to the "Users" collection. This issue could be solved with an intermediate collection acting in the middle. Initially, this collection wouldn't be necessary, but apart from that, everything went well.
So, static GraphQL API as a back-end, done! To the UI!
With the all components created and working property, it was about time to integrate the app with our static API. In order to do that, Apollo Client was imported into the project to manage all the GraphQL requests the app would run. With Apollo in place, the pages could be created individually, requesting the data from the API, passing them to the child components, and rendering them. Even though Function-based components were the choice I made for the display components, the pages were kept as Classes to handle some complex scenarios with the infinite scroll functionality and conditional data fetching. No problem so far, just a lot of work.
Besides the user interactions, we also need to have smooth navigation between pages, to create a friendly user experience, and that's where Framer Motion API takes place in our LinkedIn. It has the ability to intermediate between Next router system applying CSS animations while leaving/entering a page. The implementation was quite easy, without any trouble... Except for the fact that it had a serious issue after building the project and testing it live.
The problem here was that Next was creating a conflict with Motion API for some components down in the component tree from React — when a page was changed, Next core unmounted only the CSS Modules from some components too fast, not giving Motion API enough time for the page to fade-out of the screen, so the CSS was lost before the elements leave the page — the problem only happens after the project is bundled into static files, strangely enough, this behavior doesn't happen on Next development mode.
In fact, this is still an open issue at Next (by the time this post was written). Currently, there are some workarounds available to solve the problem, but they have their downsides as well. Fortunately, the problem drew a lot of attention at GitHub community, and hopefully, it will soon be fixed.
Working with animations that takes a big space at the device screen can be tricky sometimes. Targeting the right elements and choosing the right CSS properties is an important task to get a great performance. At first, it may not look a big deal, but it made a huge difference in the performance tests I ran with some old mobile and desktop devices.
There is also a second topic here. I wouldn't say it was a "problem" but more like a point of attention with the Framer Motion API integration. As some of the routes are dynamic generated based on data from the server, it is important to handle them with React Memo, where it can prevent components from unnecessary multiple renders. Otherwise, as soon as the router change was triggered — but before the animation — the current page would be re-render, not giving enough time for it to fade-out of the screen (again). These re-renders have a serious impact on the app's final performance, not only affecting the client-side of the application but also increasing the requests to the server leading to possible overloading problems. I would say that the memoization system when used wisely is an awesome optimization technique with a huge performance impact and deserves special attention while developing an application.
Working with individuals stand-alone display components fit most cases, but there are some scenarios that it's not enough to achieve a goal. The Chat component — which is composed of a master component and three sub-components — is a perfect example of that. The challenge here is not only the interface by itself — with a decent mobile version — but also to make the sub-components communicate with each other in harmony. At first, I thought of using Redux.js for that, but even though it would fulfill the requirements and solve our problem, I've chosen to work with React Context API instead, that is meant to share data that can be considered “global” for a tree of React components, fitting perfectly to the Chat component case. This wasn't the only place where Context API was required, but as mentioned, this is a "quick overview", so we are going to stick only with the Chat component.
React first introduced Context API in version 16 with the goal to solve the issue of props drilling where you avoid passing props through many components in the component tree. Using Redux, on the other hand, requires not only adding more libraries to the application bundle but also requires following a set of configurations and their boilerplates for the library to be able to manage the application states. Which doesn't mean that Context API replaces Redux and its purpose — to be honest Redux is perfect for larger applications where there are high-frequency state updates — but the point here is to understand the problem and balance the best solution for each case where sharable states are necessary.
Well, usually the side-projects that I decide to build end up taking me more time than I expected at the beginning, but overall the final result, in this case, was actually better than I expected as well, so I guess it's forgivable.
The development process should not be a blurry thing, but you don't need to know every single thing you will do beforehand. I divided the development process into individual steps here, only to create a clear structure for the article. These steps are usually merged with each other while developing, and sometimes you'll need to go back a few days and work on something you thought was completely done.
Start from the basics, recognize how the next step looks like, and work on it. Problems are inevitable and that's what makes each project unique in some way, and winning these small battles is one of the things that motivate us to go to the next one.
That's all everyone, if you have come this far, thank you for reading. And also, feel free to connect with me on LinkedIn (this time it's the real one).