Starting a Project With React

Starting a Project With React

Decisions, Questions and Challenges That Comes With It

Introduction

It’s important to make the right technical decisions at the beginning of a project. When working with long-term clients, it gets even more important. In fact, when you reach a certain point, it will become too hard or too time-consuming to refactor. You also want current and future developers to be attracted to the codebase, its patterns and its modern libraries.

This article is meant to provide tracks to explore and consider so you can better reflect on the first decisions you’ll have to make and the first challenges you’ll have to face when starting a project with React.

I purposely don't go deep into the topics covered and recommend you do more research on the matters.

Why React?

React is the most popular front-end framework in 2022. Its flexibility, satisfying developer experience and massive community makes it the most favorite among developers. As per Stack Overflow Annual Developer Surveys, React has been the most wanted web framework for the last 5 years.

Should you use Typescript?

For most developers, the choice is simple: Once you try Typescript, there is no going back. Most newly started React projects led by experienced React developers will use Typescript. While it might be a bit of a learning curve and the code might take longer to write, the static type checking makes a project more robust, more reliable and easier to maintain. It’s also worth noting that Typescript provides better code completion in your IDE.

Should you write tests?

This is a controversial subject. Sometimes, you just want to build a prototype and you will find yourself not having time to write tests. In this case, you should at least test the critical paths of your application with an E2E solution like Cypress. However, when a project reaches the production phase, it's recommended to write more E2E tests.

When you find yourself writing complex functions or complex React components that might behave differently based on certain parameters, it’s a good idea to test those behaviors. For plain JS code, such tests can be written with a library called Jest. When it comes to React component testing, Jest can be coupled with react-testing-library. A good rule of thumb is to at least watch for console errors and do snapshot testing for each component.

The more tests you write, the more secure you will feel making changes in your codebase, whether you are developing a new feature, editing an existing component or refactoring code. The fear of breaking something will drastically decrease as you'll catch newly introduced bugs immediatly instead of during QA phase, or worse, after your next production release.

It’s also worth noting that writing tests is a form of documentation for your application.

What styling solution should you use?

There are three ways to do styling in React: CSS modules, CSS-in-JS libraries and JSS. The latter shouldn’t be considered as doing styling with JS objects is not convenient and enjoyable compared to other options. It has its limitations and feels a lot less familiar than actual CSS.

Therefore, this leaves us with two great solutions.

CSS modules allow to write actual CSS files that are scoped to certain components, while a CSS-in-JS library like styled-components will allow you to attach styles to a single component by writing CSS synthax inside of your JS file. The CSS can be injected with any JavaScript code. Therefore, it's easy to apply styles based on component props or just any JavaScript variable.

What React boilerplate should you use?

While most introductions to React are made with Create React App, there are better built and more maintained projects. One of them is Next.js.

Next.js is a production-ready framework built on top of React. It combines decades of web development innovation and learning into a single package, while keeping things simple. Over the years, most of the innovation they did was aimed towards improving and simplifying the developer experience.

For the tools it offers, the amazing developer experience, the out-of-the-box performance, the flexibility and the scalability, Next.js is the go-to of many React developers. It has a strong community, it’s well-funded and maintainers are dedicated to the project.

The team at Next.js actively maintains their examples section. These examples are very useful as they can be the foundation of your new project, as well as an excellent reference to properly integrate a library to your existing project.

Should you use a UI library?

Even if you don’t plan on using a lot of library components, using a UI library will give you a good structure to build around. It will not only give you common components like buttons, inputs, modals, checkboxes, rows and columns, but it will also give you practical utility functions to help you develop your features.

If you fear that a UI library will have you restricted in terms of UI identity, you can be reassured. In the past years, UI libraries focused a lot on the customization aspect. For instance, libraries like MUI and Mantine offer exhaustive theming customization, from colors to fonts, sizes, border radius, animations, media query breakpoints and more.

They also handle a lot of the web accessibility for you, which is often underestimated and will become more and more important in the years to come as companies are required to meet accessbility standards.

Moreover, mainstream libraries have a comprehensive documentation supported by relevant examples, as well as a big community around them that makes them easy to use.

With such popularity also comes interesting tools. For instance, popular UI libraries have their own Figma UI kit, which drastically facilitate collaboration between developers and designers.

Considering what existing UI libraries offer and the excessive workload that comes with building your own, it goes without saying that making your own is simply reinventing the wheel and should almost never be done.

Enforcing good code patterns and consistency

A linter and a formatter are the first steps to writing clean and consistent code. The most common solution is using ESLint for linting and Prettier for formatting. You can use your favorite ESLint config (i.e. eslint-config-airbnb) and disable its formatting rules that would conflict with Prettier with eslint-config-prettier. You can then setup auto format on save in your IDE.

The reason why ESLint on it's own is not sufficient is because it doesn’t handle formatting well. For instance, it cannot auto-fix the maximum length of a line. Only Prettier can handle it.

IDEs

While most developers use VS Code, an IDE should remain a personal preference. However, because some project configurations and extensions are different from one IDE to another, it is more convenient to have everyone in a same project use the same IDE.

A good practice is to commit IDE config files (e.g. .vscode and .idea) to your repository so that other developers can benefit from it.

Package managers

You have two options: npm and yarn. While they are both great and somewhat equivalent, yarn installs dependencies significantly faster due to its caching strategy. It also performs better in terms of security.

Both package managers will generate a lock file describing your dependency tree. While it might be tempting to not commit this file to Git, keeping it in the repository ensures that every install generates the exact same node_modules across all machines, thus eliminating the risk of inconsistencies from one computer to another.

They also have their respective commands that provide an inferface to update outdated packages. It should be run as often as possible to maintain libraries up to date. When doing so, keep in mind that you have to look at changelogs to adapt your code if needed.

Choosing packages on the npm registry

Be careful with libraries you are choosing from the npm registry. Take the time to make a deliberate choice as this could save you a lot of time in the long run. Keep in mind that yourself or other developers will have to deal with those packages years from now.

You want to go for a library that has high downloads on npm, but is also maintained rigorously. Look at comparison articles and make sure you analyzed the possibilities before proceeding. There is nothing wrong with experimenting a couple hours with some libraries if you are unsure.

Form libraries

Forms are a big part of an application. Therefore, you must make thoughtful technical decisions with them. A good way to start is to use a library like React Hook Form, which can be integrated on top of UI libraries like MUI and Chakra UI. It offers good performance by not causing unnecessarily re-renders like the popular Formik library does, which will result in performance issues quickly if you have large forms. You can also easily integrate a schema validation library like Yup or Zod to enforce rules on your fields.

Keep in mind that some UI libraries like Mantine and Ant Design have their own form utilities.

Date libraries

As soon as you start playing with dates, whether it's for calculation or formatting, you must use a date library. The infamous moment.js library is now a legacy project, so it's not an ideal option. There are lots of easy-to-use and lightweight date libraries out there and Day.js is one of them. The nice thing about it is that its API is strongly inspired from moment.js, so most developers will feel at home with it.

Fetching libraries

API calls will most likely be all over your application. Therefore, using a library to facilitate API requests is important. React Query and SWR are two excellent libraries offering the ability to make an API call with one line of code, while having the flexibility to pass options based on your needs. The built-in features allow you to create better user experiences without having to reinvent the wheel. Things like on-demand refetching, refetching on interval, request deduplication, data dependency, revalidation on focus, local mutation, error handling and smart error retry are all easy to manage and customize to fit desired behaviours.

If you decide to use Redux Toolkit, consider using its own fetching tool called RTK Query.

Should you use a state management library?

While having a state management library like Redux is often considered as a necessity, React Context and hooks are changing that. This topic is a bit controversial, so please make additional research on the matter.

In the vast majority of cases, a global state is used as a way of synchronizing your client with your server and caching data. Turns out you don't need a global state for that. In the last years, when one wanted to fetch data from an API and cache it to reuse it across its app, Redux was the first thing that came in mind. However, nowadays, there are fetching libraries like React Query and SWR to achieve this exact behaviour.

The reality is most applications have a relatively small and simple client state. Most of the time, a fetching library will be more than enough to fetch data and access it at any time throughout your app without actually calling the API on each instance, thanks to their customizable request deduplication interval.

If your app ever needs a global state that is unrelated to API data, React Context will do the job perfectly fine in most cases.

Again, there are a lot of nuances to this matter. My recommendation would be to go without a state management library, and if you encounter issues related to your global state, then you can consider one.

It's also worth mentionning that Redux is not the only state manager out there. However, if you choose Redux, I highly suggest you go with Redux Toolkit as it drastically simplifies the Redux implementation.

Re-renders

You shouldn't worry too much about React re-renders. Having a component re-render a couple times is not as bad as some portray it. React is smart and subsequent re-renders usually won’t affect your app performance at all.

If you ever encounter an actual performance problem, then it makes sense to address it and use the React tools at your disposal to fix it. Otherwise, it’s not worth it to fight the framework and try to prematurely optimize your code.

When it comes to rerenders, one thing that's worth enforcing early in your project is the separation of your components. That on its own will increase readability, but also performances due to the fact you won't have multiple states at the same level triggering re-renders that wouldn't have happened otherwise.

Unnecessary useEffects and useStates

If you have a list of items in a React state, you don't need to do a useEffect and set a new state to do manipulations on the list. You can simply transform the list at the top of your component and your transformation will be re-run automatically once your initial list of items changes.

Same thing applies for user events. If you want to do an API call once a user click on a button and you happen to set a state in this event handler, you shouldn't do the API call on a useEffect that listens to this state. You can simply do it in the event handler.

In the same vein, when a variable can be calculated from existing props or state, don't put it in a state. You can simply calculate it during rendering.

While these problematic patterns might work in your application, avoiding them will not only make your code faster by reducing rerenders, but more importantly, it will make it more readable, easier to maintain and less error-prone.

Bonus tips

  • Use functional components, not class-based components.
  • Learn to make custom hooks to DRY your code.
  • Use fragments when divs are not needed.
  • Maintain a thoughtful and consistent folder structure.
  • Avoid passing down the same props at multiple levels due to requirements in the final level of the component tree, also known as props drilling.

Conclusion

I hope I could provide you with some interesting insights that can lead you in the right direction on your journey to start a React project. As this article is strongly opiniated, don't hesitate to share your thoughts and challenge my ideas!