Frontend app-building dilemma: Custom solution or using a library
Introduction — towards the building React application
When building a React application, a developer often faces the dilemma of which solution to use in order to best implement business requirements, functionality, performance, styling, or simply create an application for the developer’s own needs. There are many options available for solving a single task on the internet, but which one is the right one? Should you use an existing library or perhaps come up with your own idea, or even write your own library? I will try to guide you through the most common dilemmas in your everyday work. Hopefully, this will help you find the right way.
Disclaimer: Blog first published on https://scalac.io/blog
Managing state in an application
When choosing the best state management tool, it’s important to consider how complex the data stored in the state will be. For applications with small data structures, built-in React hooks are sufficient. As the complexity increases, it is recommended to use state management tools. When it comes to choosing a state management library, it’s advisable to opt for one with the highest popularity, such as Redux (6,788,929 weekly downloads) or Zustand (2,202,338 and it’s increasing).
When choosing Redux, it’s worth considering Redux Toolkit, where the syntax is more minimalist compared to the traditional version with switching between actions (2,751,787 downloads per week). Comparison of Redux Toolkit and Zustand with an example:
Redux
Zustand
As seen in the examples, creating a so-called “slice” for state management is nearly identical. While Zustand code is more concise, let’s move on to its usage in a component:
Redux
Zustand example
In Zustand, only one hook is needed to use it in a component, whereas Redux requires two.
Both libraries have middleware, can handle asynchronous actions, modify data using Immer, and have devtools, so what is the main difference?
The bundle size for Zustand is 1.56KB, while Redux is ten times larger. Additionally, the entry threshold for Zustand is lower, specially when configured with TypeScript.
Redux is ideal for large, complex projects with extensive state needs and longer development cycles. Its structured approach fosters consistency and teamwork. In contrast, Zustand is more lightweight and suitable for smaller applications.
For most applications, using context and breaking down state into atoms or several React reducers is often sufficient, which is an approach worth considering.
Communicating with an API
While XMLHttpRequest and Fetch API are powerful, they can involve a fair amount of boilerplate code. To streamline data fetching in your project, using a dedicated library is often preferred. These libraries come with a range of built-in features that can greatly simplify the process. Here are a two popular options:
React Query (34.7k stars on GitHub):
Provides a provider to encompass main components, streamlining request management through hooks within these components. It enables chaining and refetching of requests, cache policy adjustment, and request limitation with flags. The library employs various methods and fetch states. The library also supplies a DevTools extension for debugging.
SWR (26.8k stars):
Simpler API and is focused on the basic use case of data fetching and revalidation. Slightly lighter in terms of bundle size. Extremely easy to get started with.
If Redux is being considered for use in the application, it’s worth considering Redux Thunk or RTK Query instead of the libraries mentioned above. The result will be the same.
Forms and validation
Practically every web application has forms, for the basic ones, simple handling is sufficient: https://react.dev/reference/react-dom/components/input. However, for the more complex forms, we have two leading libraries:
Formik is good for beginners and is suitable for use in less structured forms. However, with larger forms, its syntax can be less clear compared to using plain React, as Formik relies on render props, which are gradually becoming legacy code. The large community around Formik will surely be helpful in the development process.
React Hook Form is the most popular forms library in the React environment (3,761,299 weekly downloads). Its biggest advantage is performance optimization achieved by minimizing re-rendering and updating only the necessary components. The tool seamlessly handles most third-party libraries with ready-made components and validation tools.
The problems start when we want to dynamically create inputs or when we need to update data dependent on multiple variables.
The library offers methods to handle these cases, such as watch() or the useWatch hook to re-render only specific values if needed (codepen). The main principle is to prioritize performance and memoization of individual form fields. If a developer deeply understands memo, useMemo, and useCallback, which essentially operate under the hood of React Hook Form, it will be easier to scale the application and ensure good performance. When it comes to dynamic forms, using a regular array is certainly easier than employing useFieldArray from the library.
Validating complex forms can be frustrating at times, but two packages come to the aid of developers: Zod and Yup.
Zod integrates seamlessly with TypeScript, offering a JavaScript-like syntax for creating robust data validation schemas. Its declarative API simplifies complex type creation.
Yup also supports static type inference but generates typings less aligned with TypeScript. This may limit its utility in TypeScript-centric projects.
Styles and animations
When considering which path to take when styling modern web applications, there are several options to choose from: CSS preprocessors like SASS and LESS, CSS-in-JS (emotion, styled-components), or CSS frameworks with pre-made classes (Tailwind, Bootstrap).
SASS and LESS enhance reusability through the utilization of variables and mixins. This enables the segmentation of styles into smaller, reusable components while still making use of global styles. In JSX, one can implement modules and conditional rendering with tools like classnames and clsx, providing a high degree of flexibility, in this case the syntax can occasionally be less readable. Additionally, SASS offers the advantage of mathematical operations, allowing for a reduction in reliance on browsers API.
CSS classes frameworks
Frameworks with predefined style classes allow for rapid development of applications, including responsive design. Another advantage is the speed of compilation. The provided classes cover most use cases in creating clear interfaces, although it may not be a pixel-perfect solution. For that, additional configuration or the classic approach of adding CSS files and conditional rendering is required, which can be frustrating and make the code less transparent. This solution is ideal for Proof of Concept and Minimum Viable Product applications or just for simpler applications.
CSS-in-JS
The usefulness of CSS-in-JS is immense. Its greatest advantage lies in the ability to directly pass variables, state, or props to styled components. This leads to more organized and well-typed code. It’s possible to extend component styles, nest HTML tags, and establish relationships between components. Calculations based on the browser’s API can be directly passed to styled components as props. Styled components can be inefficient in larger applications as they recalculate styles based on runtime values each time the component is rendered, impacting performance.
Spectacular application animations grab the attention of customers. For simple repetitive actions like opening a menu or a dropdown, using CSS is sufficient (keyframes, transitions etc.). However, creating a sequence of animations can be a greater challenge and something that’s harder to maintain.
There are two popular libraries for creating animation sequences: anime.js and gsap. Both use timelines and have very similar syntax. Additionally, gsap includes ScrollTrigger, which can be used to implement scroll animations. When using numerous transitions, it’s important to keep in mind the potential performance impact.
Component libraries
If creators are contemplating the development of MVPs (Minimum Viable Products) and POCs (Proof of Concepts), leveraging component libraries can be incredibly valuable. These libraries offer pre-designed, customizable components that expedite the development process and ensure a polished user interface.
Material UI makes it easy to quickly create basic versions of apps. It works smoothly with React and looks modern. But, if you want to make very detailed and custom apps, it might take a bit more work.
Ant Design is excellent for creating robust applications, particularly for larger companies. It offers a rich set of tools to handle large amounts of information and complex data setups.
In some cases, based on developers’ experience, it may be simpler and faster to build custom components from scratch rather than using pre-made libraries. Component libraries may become deprecated due to lack of maintenance, evolving technology, changing design trends, security concerns, or dwindling community support.
Utilities and hooks
In certain applications where developers need to handle a large amount of interdependent data, performing operations on collections, objects, and conducting numerous mathematical calculations on the frontend, a utility library can be extremely beneficial. It simplifies these tasks and makes them easier to manage.
The condition should be that the library will be used multiple times, not for individual cases. In such situations, it’s better to opt for a custom solution, as unnecessarily increasing the bundle size should be avoided.
Lodash is a popular JavaScript toolkit with many useful functions that make programming easier.
Math.js is a special math toolbox for JavaScript. It offers a bunch of really advanced math tools. This is super useful for projects that need really precise math, especially in science and engineering.
Hooks
For some time now, hooks have been at the forefront of functional programming, especially in React. When thinking about utility hooks, you can find very useful packages like react-use, which is a collection of universal functions utilizing the browser’s API, state, lifecycle methods, and more. It greatly speeds up work. Another library is @use-gesture, which handles most gestures such as dragging, dropping, and other operations on DOM elements. It also works on mobile devices. The same rule applies here as with utilities for single use — for specific use cases, it’s worth using a custom solution.
Accessibility
It’s crucial for a modern application to be built with accessibility for everyone, including users with disabilities. Ideally, a simple interface should work smoothly on all browsers and devices. React, in which applications are often built as single-page applications (SPAs), can pose challenges for developers to ensure smooth and simple accessibility. This is where React Aria by Adobe comes to the rescue.
With React Aria developers can pass specific props to custom components, accessibility for all devices and browsers will be added automatically. The library uses its own triggers and events to help users with interactions.
Summary
When deciding whether to use a library or a custom solution, several points should be taken into account:
- Client requirements
- The purpose of the application (target, POC, MVP)
- Performance
- Developer experience and general consensus on using the solution
- Reusability of individual parts of the application
- Scalability and the intended size of the application
- Popularity of technologies (downloads, GitHub stars, community)
After considering the above points, the solution may become evident on its own. :) And if you need some advice or want to discuss your application, just write to us: projects@scalac.io.
Together, we’ll build something awesome.