rsrc logo.

Introducing rsrc

Why we built a new open source tool to manage fetch state in React.

At Signal Sciences, we’ve been using React in production for over four years. If you’re like us, you’ve probably spent a fair amount of time writing code to interact with APIs and manage fetch state. It can be tedious work and hard to maintain as your team and product grows. To help solve this problem, we’re excited to introduce rsrc (pronounced “resource”), a collection of open source components designed to simplify async fetch state in React.

With rsrc, all the information required to describe an endpoint is contained in a single place, and the state is returned in a predictable shape. This means you don’t need much boilerplate to quickly get started writing presentational components.

With rsrc, the middleware and boilerplate often used to manage and persist fetch state is abstracted into a declarative and composable interface:

// src/resources/Dashboard.js

import React from "react";
import { Resource } from "rsrc";

export default props => {
  const { dashboardId, children } = props;

  return (
    <Resource
      url={`/api/dashboards/${dashboardId}`}
      maxAge={3600}
      actions={{
        update: data => ({
          options: {
            body: JSON.stringify(data),
            method: "PATCH"
          },
          invalidates: [`/api/dashboards/${dashboardId}`, "/api/dashboards"]
        })
      }}
    >
      {children}
    </Resource>
  );
};

Once the resource has been described, you can move on and get to work on the things that really matter: shipping UI.

// src/pages/Dashboard.js

import React from "react";
import { Loading, Error } from "../components";
import { Dashboard } from "../resources/Dashboard";

export default props => {
  const { dashboardId } = props.match.params;

  return (
    <Dashboard dashboardId={dashboardId}>
      {resource => {
        const { state } = resource;

        if (state.pending) {
          return <Loading />;
        }

        if (state.rejected) {
          return <Error reason={state.reason} />;
        }

        return <pre>{JSON.stringify(state.value, null, 2)}</pre>;
      }}
    </Dashboard>
  );
};

That’s all there is to it. No connecting. No middleware, actions, reducers, or selectors to wire up. The first time the resource is rendered in the UI, the component fetches fresh data from the server and caches it for later reuse. Subsequent instances or renders piggyback on the original promise until it becomes invalidated or stale.

Why we built rsrc

Our feature engineering teams are made up of full-stack engineers responsible for both API and UI development. The pace of front-end technologies can be dizzying for those not accustomed to working in it on a regular basis. At the same time, we strive to keep the codebase as current as possible, and that requires maintenance and often times migrations. This creates tension. It’s not realistic or scalable to expect every engineer to stay current with the shifting libraries, patterns, or implementation nuance of a modern front-end codebase. Instead, we need our teams to feel empowered to quickly and safely execute on product goals, and this ultimately means building out reliable, iterable interfaces (API and UI).

We built rsrc to minimize the developer friction when wiring up these interfaces - with the added benefit of improving the general consistency, maintainability, and portability of the UI codebase.

So far, we’ve been using rsrc in production for about a year and the reception and productivity from developers working with it has been great. The quality, composability, testability, and maintainability of the code that we’re now producing is dramatically simpler and our hope is that others who may be struggling with similar issues might find this as useful as we have.

The evolution of the problem

Early on, we adopted some patterns for managing fetch state that mirrored many example app structures found in the Redux ecosystem at the time. This meant top-level folders for actions, reducers, containers, and some middleware, which would be responsible for normalizing API data, centralizing async fetch logic, and other application-level side effects.

src/
├── actions/
├── components/
├── containers/
├── middleware/
├── reducers/
├── store/
└── index.js

When the team and codebase were small, this worked quite well. But as the team grew and our feature engineering skill set became more diverse, concerns arose about the safety and maintainability of housing these opinions at such a high structural level in the codebase. The top-level flows were often difficult for new hires to follow, and began to interfere with our flexibility to experiment with new code and design patterns.

As more hands began working in the codebase, we knew we wanted a more granular approach that would ease coupling and the potential for conflicts. This moved us toward a modular structure where each feature or domain model could be treated as an independent mini-redux application, exporting its corresponding actions, reducers, components, etc.

src/modules/dashboards
├── actionTypes.js
├── actions.js
├── components/
├── constants.js
├── index.js
├── reducer.js
└── selectors.js

Even though this meant greater flexibility to experiment, it also introduced some predictable issues: a proliferation of boilerplate, a user experience that had the potential to become increasingly fragmented, and maintenance headaches. Building a new feature under this pattern required repeated implementation of nearly identical logic that was once all centralized. This was problematic for a few reasons:

  • We were duplicating code at an unmaintainable rate
  • The code often had subtle differences, making it difficult to understand what the “correct” pattern actually was
  • We needed to write more tests to cover each snowflake implementation

The search for a solution…

Redux established some great patterns for managing async state changes in React, and many of these same ideas shaped rsrc’s direction.

In looking for solutions to this problem, we were particularly inspired by heroku/react-refetch. This library made the interesting decision to diverge from traditional flux/redux strategies for managing fetch state. Instead, it offered a HoC (higher order component) approach which connected consuming views to a locally managed wrapper state. It also provided an API that allowed tangential operations to be attached to the base fetch that gets passed to the connected view. (If you haven’t played with it, give it a look!)

Unfortunately, this solution didn’t cover two pain points we were hoping to resolve:

  1. People were generally good at understanding and writing JSX code. The concept of “connecting components” (which both react-refetch and Redux use) was often a point of confusion.
  2. We needed a way to persist and share fetch state across components. This meant we would need to implement a caching solution. While this is technically possible, it’s somewhat discouraged as an “advanced feature” that might “conflict with […] current or future functionality.”

While react-refetch came very close, we ultimately weren’t able to find a good fit in the existing ecosystem that would meet our specific goals.

Around this same time, we were experimenting with a library called Formik, which is a great utility for managing form state. Formik made use of a somewhat controversial pattern that was beginning to emerge in React-land: “Function as Child Component” (FaCC), or “render props”. In addition, the new React context APIs were launching with accompanying example code written using the FaCC style. For us, the syntax turned out to feel very natural.

The module pattern provided us the flexibility to experiment with these emerging patterns and turned out to be the ideal playground to inform the interfaces and use cases we were most commonly looking to support. The next step for us was to build our own and open source it. To learn more about rsrc and how it works, please check out the docs and let us know what you think!

The future of rsrc

Even though rsrc has been well received internally, we’re always thinking about ways to improve it. Some ideas we are currently exploring:

  • An implementation using the React hooks API
  • Support for SSR (server-side rendering)

We’re also working on another fun open source project––a data visualization library––which should pair nicely with rsrc. Look out for a launch later in the year or early 2020.

We’d love to hear your thoughts on how you’re using rsrc. Feel free to reach out on Github.