I highly recommend using TypeScript↗ for any JavaScript project. The benefits it offers are so significant that it’s hard to argue against it these days. Some key advantages of using TypeScript include:
- clear understanding of what each variable contains
- ability to easily find all references to a variable or function
- simplifies refactoring and code changing
- helps prevent data type related issues
While it may take some time to get used to it and might require writing additional code, I believe the advantages outweigh these minor inconveniences.
In addition to the official documentation, here are some other helpful resources:
The router is one of the most crucial parts of an application, as it typically orchestrates its various functionalities. Modern routers are very powerful and handle tasks such as data loading, error handling, page rendering, code splitting, and much more. It’s worth dedicating time to understand how routers work and how to use them effectively.
In React, the two primary routing solutions are React Router↗ and TanStack Router↗. Both are feature-rich, and the choice between them is often a matter of personal preference, as each has its own strengths and weaknesses.
For this application, I have opted for TanStack Router due to its extensive features and higher level of customizability. However, you can replace it with React Router if you prefer.
Here are the key application modules that demonstrate the router usage:
Most applications need to fetch and work with data from a backend. Unfortunately, React doesn’t provide a built-in mechanism to handle this interaction. While useEffect
can be a solution, it comes with several limitations and potential issues. Fortunately, there are libraries specifically designed to address these limitations. The libraries offer features such as:
- notifications about the fetch status (e.g., pending, error, or success)
- built-in data caching support
- mechanisms for refreshing data
TanStack Query↗ is one of the most popular solutions, though alternatives like SWR↗ are also available. In the shopping list application, I’ve chosen to use TanStack Query along with Axios↗. Below are key modules to help you understand how the system is integrated:
- Axios configuration
- TanStack Query configuration
- API calls to the backend
- Query options for the API calls
- Implementation within a page
If you are interested in learning more about the architectural decisions, check out the following resources:
- Extracting API logic from components↗
- Best practices for TanStack Query↗
- Effective use of TanStack Query keys↗
In React, we typically manage state using the useState
hook. However, there are cases where we need to share this state across multiple components or make it accessible globally. In such scenarios, we need a different solution. React provides the Context API↗, but there are also state management libraries like Zustand↗, Jotai↗, and Redux Toolkit↗.
Personally, I prefer Zustand. It’s lightweight, has a clean and straightforward API, works outside of React components, and has consistently provided a great experience. In this application, I used Zustand to store and expose session data. Below is an example of its usage:
Forms are a common feature on almost every website. While it's possible to manage forms in React manually, using a library makes the process much easier—especially for complex forms or those spanning multiple components. Popular form management solutions include React Hook Form↗ and Formik↗.
I recommend using React Hook Form as it is the most popular and well-maintained option. Although it has a few quirks, it can save you a significant amount of time. Here's an example of a form implemented with this library:
To reach a broader audience, it’s important to translate your application’s content into multiple languages. The React ecosystem offers several tools to facilitate this. I chose React Intl↗ because of my familiarity with it and the positive experiences I’ve had using it. Here’s how it was integrated into the project:
There are many options and approaches for handling CSS in modern applications. Here are some of the most popular ones:
- Tailwind CSS↗ - provides utility classes that can be used directly in the markup
- Bootstrap↗ - one of the oldest CSS libraries for quickly building projects
- Styled Components↗ - enables styling components directly within JavaScript
- CSS Modules↗ - ensures CSS isolation by preventing style conflicts between components
- SASS↗ - extends the CSS language with additional features
Personally, I’m not a fan of Tailwind CSS because it abstracts the CSS language behind custom classes. Bootstrap feels unnecessary for most projects unless you’re building a quick prototype. I also avoid CSS-in-JS solutions like Styled Components, as they break the separation of concerns principle. Instead, I typically use CSS Modules along with the SASS preprocessor in my projects.
Additionally, I’ve implemented a custom CSS variable system inspired by the lightweight library Open Props↗. Using a variable system ensures a consistent UI and provides a central place to control styles. By adjusting variables like font sizes, spacing, or colors, you can easily modify the look and feel of the entire application.
In the shopping list project, I’ve included such a variable system. You can find it here and explore any component CSS to see how it’s used.
Current statistics show that most web traffic comes from mobile devices. Therefore, I believe it’s essential for any application to provide a mobile-friendly version. Fortunately, advancements in CSS over the past few years have made this increasingly easier to achieve.
For the shopping list application, I implemented the mobile version using two essential utilities. The first is the useMatchMediaQuery
hook, which allows toggling component parts based on device size. The second is the standard CSS media query rule. A good example of this approach can be seen in the privateLayout
component.