The start of a greenfield project is a time when crucial decisions must be made. This can be quite the challenging task, especially if you are not that familiar with a certain technology. Therefore, this blog post intends to help you make the right choice when it comes to utilizing ReactJS for your UI.
At the beginning of one of my recent projects, my team and I had to agree on the right technology stack as this step is usually the key to the success of such mission. This included doing extensive research and finding the best options available to suit the pace of development, required by the client, and ensure future extensibility of the solution. As architectural choices are critical to the future of your application, I will start with a general picture of available options, covering their main benefits and drawbacks.
At this first stage, we set up an initial project based on one of the popular boilerplate templates, on which we could later build up whatever we found fit best. What we were looking for were a few important features:
- Hot reloading – improved developer performance
- Test ready – code quality
- Linter – a tool intended to keep track of coding convention to be honored
- Minimal configuration required on our side – in order to optimize the setup time
- An option to gain full control of the configuration if needed
- Long-term support of the template
After some research we managed to narrow down our choice down to 2 of them:
- CRA (Create React App) – created by Facebook
- RS (React Slingshot) – provided by Cory House, Principal reactjsconsulting.com, Pluralsight Author, Software Architect
To be honest, both templates were satisfying our needs but after some careful consideration, we decided on going with the CRA, mainly because of its long-term support. It’s also quite easy to set up and start development. The fact that Facebook keeps the template up-to-date and gives access to the latest features that ReactJS has to offer is also not to be ignored. This is especially true as updates come out quite often and useful improvements are constantly introduced.
ReactJS by itself is just a library that is responsible for the displaying of data. Now we had to dive into the possible architectural options that we could go with. The three main choices that we could make based on our previous experience and research were the following:
3. React Context API
If you do a little bit of research, these are the ones that will pop up immediately as options to combine with ReactJS and even with one another. There is a big community behind all of them, so any issues that you may encounter are quickly solved, while functionalities are constantly improved and launched. Best practices, guides, use cases and good examples are available everywhere, so even with a little less experience you should be able to achieve your goals.
In our specific case, we had experience with Redux and were confident we could deliver a better solution using an approach we’re competent with. Why didn’t we go for GraphQL? Despite our obvious attraction to the new and interesting, in this particular case, we chose to stick with the familiar world of Redux and ensure quality with effective time management. I will, though, give my modest thoughts and recommendations on how to choose what’s best for your instance.
Firstly, Redux can be used with ReactJS but it’s not limited to that – it’s a universal pattern, based on FLUX, that could also be used with Angular or other front-end frameworks. Part of the job that Redux does is being a state management container or simply put – there’s an object tree called a “store”, which allows you to share data across all components in your application. You only have a single “store” or a “single source of truth”, which has some benefits. But Redux doesn’t simply solve a state management problem as React already has a solution for that. It rather introduces this unidirectional workflow that makes it easier to debug and inspect the application’s state.
- Reusability of data – A lot of components could reuse the same data sets in different modules of your application. For example, there could be fixed data sets like languages, countries, categories which rarely change or components could be retendered only if a change is available.
- Unidirectional flow – Using Redux enforces the same unidirectional flow for all the data in our application, thus making it more predictable.
- Smarter data structuring – Sharing all the data across our application, Redux forces us to structure our data store carefully and be smarter about how we reuse that data.
- Logging and data persistence – The “single source of truth” introduces advantages like easier logging of data and events or persisting data across the app. The data in the store is immutable and could only be updated by pure functions (actions).
- Time Travel Debugging – It allows you to record an execution of your process running, then replay it later – both forwards and backwards. Time Travel Debugging (TTD) helps us debug issues easier by letting us “rewind” the debugger session, instead of having to reproduce the issue until we find the bug. This is easily achieved by extensions available for the major browsers.
- Separation of concerns – Redux also splits our application logic into actions, reducers and store. This helps us stick to the Single responsibility principle.
- Learning curve – It’s tricky to say what the learning curve for Redux is since it depends on the programmer’s previous experience. The concept is not that difficult to grasp but as everything else in this industry, after learning the theoretical part you must get your fingers typing to really get a taste of what you’re dealing with. So, I believe that it could be a challenge to deal with Redux quickly, especially when you’re striving for quality.
- Complexity – You might end up bringing in a library that adds more complexity and that would require more preparation when developing a new feature. A lot of teams are including Redux without really needing it. It is a rather complex state machine that is useful only in certain cases.
Scaling properly with Redux isn’t a problem as long as the best coding practices in any language are applied to all parts of the Redux pattern – action, creators, reducers and store (structure). The bottom line is that you don’t have to worry so much about scalability rather than making your code clean and maintainable, which itself will take care of scalability.
QL in the name stands for query language. GraphQL is an abstraction that stands between the client and the server (REST API). It’s composed of two parts: an implementation on the client that generates a query, which is later translated by GraphQL’s server side implementation to understandable requests, according to where the data will be fetched from (.NET REST API, MS SQL Server, etc.).
The main idea is that the client (a ReactJS one in our case) wouldn’t need to be acquainted with the API endpoints at all. Instead, a schema of the data is exposed to it. You can then run queries against the schema to retrieve only the data that you need. This means that the API we’d be hiding behind GraphQL should be well designed. Now, if you don’t have experience designing such APIs or you are new to GraphQL, with a limited time to start your official project, this might not be the preferred option. It’s always better to have some kind of a dummy/test project that would allow you to make a few mistakes and learn from them.
- Fixing common REST API issues – You can easily take care of endpoints such as /users/12/friends/workspace/colleagues and simplify them to a data schema.
- Network performance optimization – Usually, when retrieving data from an API multiple https requests would be required to collect all the data you need. Each of these requests has some latency until a response is received, but with GraphQL you could have nested requests, which are executed as a batch and return only the necessary data in the response – nothing more, nothing less.
- Strongly-typed data schema – GraphQL validates the type of parameters that you’re passing and would skip invalid request. This might not seem as much of an advantage, but it’s one less incorrect request stressing the server.
- Loose coupling between client and server – You could replace both the client or the server without either of them being aware of that.
- Development optimization – When using Redux, you have to create modified actions, reducers, then filter and transform only the data that you need from the request. Whereas, with GraphQL you just create a query that perfectly fits what you need as structured data and don’t have to go all around the solution, adding and modifying different files.
- Backwards compatibility – In the case of having a new app version with different endpoints, you’ll have no problem retrieving data requested from your old queries.
- Https caching is not an option – REST API has been designed to benefit from the https protocol, while GraphQL skips some of these perks, such as the caching of GET requests.
- Possibility of a bloated server – A GraphQL server could easily get bloated, if not maintained properly. This, however, can happen in the regular case as well.
- Boilerplate code – When added without really having the need for Redux, it introduces a lot of boilerplate code.
ReactJS with GraphQL is probably the newest and fanciest stack you can get to work with in the ReactJS realm. If you are aiming for project scalability and maintainability combined with faster development on the client side, GraphQL would be the way to go. There’s no need for you to know the API documentation, just that exact data you need according to the request schema, nothing more, nothing less. GraphQL could combine in itself multiple GraphQL APIs, which would noticeably reduce the hassle for front-end developers. Last but not least, GraphQL has a big open-source environment with quite the community, that is steadily growing.
3. React context API
Context provides a way to pass data through the component tree without having to pass props manually at every level. Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme or preferred language. React context is what you get as an advantage from the ReactJS API. In recent versions of ReactJS the API context is updated.
- Accessibility – Context is primarily used when data has to be accessible by many components at different nesting levels. This is what you partially employ Redux for.
- No additional dependencies – It neatly solves your problems as it stands out with less weight and higher standard of performance.
- Easy to implement – It is out-of-the-box and easy-to-test solution for managing and sharing global state across multiple components.
- Limited functionalities – As it’s the simplest building block when it comes to data management, its API and capabilities are sometimes considered limited compared to, let’s say, Redux.
- Difficult component reuse – Apply it sparingly because it makes component reuse more difficult and overusing could make the code messy because of the global state.
- No tooling like Redux – Following state changes is easier with Redux, because of the cumbersome and unidirectional flow and tools, which is not the case here.
React context is not a replacement for Redux. It does handle the problem with the global state, but that’s not the only thing Redux is about. Also, they’re not mutually exclusive. Go to the next level with Redux or GraphQL, if what you are developing becomes unmaintainable with this approach.
If you are developing a small application with narrow focus, a specific purpose and no plans for expansion, a clean ReactJS would most probably suit your needs without introducing needless dependencies and complexity. For mid-tier projects that require a bit more structuring and are likely to share large volumes of data across the application – Redux has been a reliable option for quite some time now as it’s also supported by its big community.
One of the biggest functions of the Redux code is to handle new API calls and shape the new data in a specific way. However, you probably don’t need half this code. In most client-side apps, GraphQL replaces the need for state management libraries as it lessens complexity, scales the amount of code down and shapes the data to fit your UI.
On the other hand, the “cutting-edge” approach of GraphQL brings a number of strong points – impressive speed, scalability for bigger applications and notable convenience in regards to data collection. Naturally, this is true if the API you are consuming meets the requirements to use GraphQL. Ultimately, you can still use Redux alongside GraphQL, especially when state management is overly complex.
Context is also not to be ignored, particularly in cases of small applications or open-source components you can reuse. Take advantage of it whenever you feel irritated at the length of your data and simplifying it is your only hope.
What I’d suggest when making your decision in terms of architecture – just KISS (Keep it simple, stupid)!