Share on facebookShare on linkedinShare on twitterShare by email

Application development is often reactive. We see the need, we deliver the solution as fast as possible. During this fast software cycle, we gather requirements and implement them as soon as they appear. I’m not talking about quick and dirty. I’m referring to using the best RAD practices – rapid application development.

The RAD cycle is as follows: you implement great core features (MVP-style), relying on years of experience to create maintainable code. But over time, several things occur: requirements change, more code gets written, and the codebase starts to rebel against your intuitively brilliant but perhaps not fully robust architecture. So you start refactoring. Also, you discover that technology changes, offering new ways to make your code simpler, cleaner, and more powerful.

Enter game changer React Hooks. And, a fast growing business that requires you to rewrite your application with loads of new features.

Rewrite – from scratch. Life offers a second opportunity.

How React Hooks saved our administration application

Application development can also be pro(Re)active. Our administration application is data-intensive. Previously, many separate (and competing) components had managed their data independently – connecting, formatting, displaying, updating, etc..

The requirements of an Admin application

An Admin application is a good candidate for centralizing data handling. Administrators need to see the data as is, so the onscreen views usually match the structure of the underlying data. So, while our client-facing dashboard presents functional views for business users, an administrator needs to see user or client subscription information in a consistent and straightforward manner.

What we needed was a more scalable solution. Since we pull data from multiple sources – all accessible via one API with many endpoints – we wanted to centralize the common aspects of data handling. This not only gave us immediate benefits (better testing, caching, syncing, standard typing), it facilitated and simplified future data integrations.

A customized hook

We implemented a custom React hook called useData, which manages and therefore centralizes all data-retrieval API calls, data exchanges, type checking, caching, and other such data-based functionality. The caching alone enhanced user-facing speed enormously.

Equally important, the speed and centralization enabled our front-end developers to reuse their components and UI elements in different parts of the interface. Such reusability created a feature-rich, user-friendly UI/UX without front-end developers needing to maintain unique state information within each component. Lastly, under the hood, data reusability enabled a coherence in the models that drove the front-end functionality.

We will discuss front-end benefits of React hooks in future articles; this article is about how we served the front-end with a reliable and scalable layer of data handling.

How our useData hook centralized the process

We use different data sources, some more complex than others but all following the same JsonAPI specification. Additionally, they all have the same needs – a means to:

  • Retrieve data
  • Deserialize and format it
  • Validate its format
  • Perform error handling (data quality, network)
  • Synchronize with app refreshes and other data/workflows
  • Cache the data and keep it up to date

Enough talking, here’s our useData hook code:

As you can see, this hook takes three parameters that, when combined, give us all the following functionalities:

  • A “builder” function that transforms and enhances the data for use by our components
  • The URL of the API endpoint that retrieves the data
  • Optional parameters. For example, to ignore cache or wait for some other data to be ready before calling the API

The result is that our components no longer need to manage all that. We’ve abstracted and encapsulated the complexity.

The useData hook returns some values we can use in our components:

  • Some states: loading and errors (if any)
  • The data (if any)
  • Meta information (if present – pagination information, for example)
  • A refresh function (to refresh the data by calling the API again)

Building the data

Let’s take a deeper look at what this code does and how we use it.

Schema validation with Zod

Getting the data is one thing. Ensuring that the data is correctly structured, or typed, is another. Complex data types require validation tools like yup or zod that enforce efficient and clean methods, and offer tools and error handling runtime errors based on faulty types. Our front end relies on strongly-typed data sets, so the validation stage is crucial for us.

We use zod. Zod is used to build a model of the data. For example, here’s what the model for our Application could look like:

Then, to construct our builder function, we use in-house-built generic helpers on top of the zod model.This helper takes two parameters:

  • The model of our data (Application in our example above)
  • A transformer function that is used to enrich that model.

In our case, that transformer would look like this:

Another example of enrichment is if a model has a date: we usually want it to expose a javascript date rather than a string date.

We have 2 versions of that helper function (one for objects and one for arrays). Below is the first one:

The typed output by zod is very clean and looks like a typescript type that we would have written ourselves, with the addition that zod parses the JSON using our model. For safety, we use the safeParse method from zod, which allows us to send back the JSON “as is” in case of an error during the parsing step. We would also receive an error on our error tracking tool, Sentry.

With our example, our builder function would look like:

Calling the API

Internally, we use another custom hook useApi (less than 200 lines of code) to handle the GET/POST/PATCH/DELETE. In this hook, we use axios to call the backend API and perform all typical CRUD functionality. For example, on the read side, Axios deserializes the data we receive before it’s converted from the JSON API spec to a more classic JSON, and switching from snake_case to camelCase. It also handles any meta information we receive.

Also, from a process point of view, it manages request canceling and errors when calling the API.

Caching the data

At this point, we can summarize: the useApi hook gets the data, which is then passed through the builder to be validated and enriched; and the resulting data is cached using react-query.

We implemented react-query for caching the data on the front end, using the API endpoint URL as the cache key. React-query uses the useApi hook mentioned above to fetch, synchronize, update, and cache remote data, allowing us to leverage all these functionalities with a very small codebase.

All we have to do on top of that is implement react-query’s provider. To do so, we’ve constructed a small react component:

Most importantly, it manages our caching. We have many components that need the same data, so we wanted to avoid unnecessary network traffic to retrieve the same information. Performance is always key. And so is limiting potential errors performing unnecessary network calls. Now, with caching, if one component asks for data, our cache will store that data and give it to other components that ask for the same information. In the background, React-query of course ensures that the data in the cache is kept up to date.

To sum up, here’s an example of a component built using this useData hook and our Application model as defined above:

As you can see, our useData hook lets us standardize the loading and errors states, encouraging us to write reusable components that handle those states. For example, we have reusable StateCard and StateContainer components. With the data now easily available, we can go about integrating those reusable components and focus exclusively on building a great front end experience – cleanly, fully-featured, and scalable.

Share on facebookShare on linkedinShare on twitterShare by email
About the author

Loading amazing content…

Subscribe to the blog updates
Thank you for subscribing, stay tuned!