byte by byte

Using Middleware with Dojo Processes

Rene Rubalcava | July 13, 2019

We previously looked at how you can manage state in Dojo with processes and stores. This isn't only a flexible way you can manage state in your applications, but it provides some hooks for you to be able to stick your nose into your applications business.

Maybe you want to manage some state in your application when you begin to fetch data and when you have completed fetching data. You could try to manage this in the widget itself or maybe in the process. This is kind of tricky though. Each process can update the store, and you could even run multiple processes at the same time, but it's treated as a single transaction. So you can't really start a process, change the loading state and change it back when you're done in a way that would update your widget in that single transaction.

// src/processes/starwarsProcesses.ts
const fetchItems = commandFactory<Item>(async ({ path }) => {
  // where do we change the starting state?
  const response = await fetch("https://swapi.co/api/");
  const json = await response.json();
  const items: Item[] = Object.keys(json).map(key => {
    return {
      label: key,
      value: json[key]
    };
  });
  // this workflow doesn't work, the widget never gets the 'true' state
  // this is a single transaction
  return [
    replace(path("loading"), true),
    replace(path("items"), items),
    replace(path("loading"), false)
  ];
});

Middleware

But fear not! Dojo has a way for you to run some middleware on your processes to do all sorts of cool things! There is some more detailed information here.

What kind of tasks can you do with middleware?

  • Transform the fetched result of your process.
  • Validate arguments passed to your process.
  • Define a loading state.
  • Add logging and telemetry.
  • Runtime caching.

And I'm sure you can think of many more uses!

The middleware API allows you to provide after and before methods. So in my use case above, We can update the loading state of the process before and after it begins.

To update some loading state, that could look like this!

// src/processes/starWarsProcess.ts
const progress: ProcessCallback = () => ({
  before(payload, { apply, path }) {
    // update the app store before the process is run
    apply([replace(path("loading"), true)], true);
  },
  after(error, { apply, path }) {
    // update the app store when process is finished
    apply([replace(path("loading"), false)], true);
  }
});

In the middleware methods, we're given an apply and a path. The apply lets us apply an operation to the store, and the path lets us pick the property that we want to act on. In this case, we can use the replace operation to update an existing property. This state update will propagate to any other parts of the application that are using this store.

Now we can update the process to use this middleware!

// src/processes/starWarsProcess.ts
export const fetchItemsProcess = createProcess(
  "fetch-items", // process name
  [fetchItems],  // command
  [progress]     // middleware
);

With the middleware in place, we can update the widget so that it can recognize when data is being fetched.

// src/widgets/APIExplorer.tsx
export class APIExplorer extends WidgetBase<ExplorerProperties> {
  ...
  render() {
    return (
      ...
      <TextArea
       rows={25}
       theme={theme}
       value={loading ? "Loading..." : result}
      />
      ...
    );
  }
  ...
}

Now in the widget, if the store loading state is true, we can show some loading text, if it's not loading, we can show the result!

You can see what this looks like in this code sandbox!

Summary

The ability to have the after/before middleware in our application state is not only practical, but incredibly flexible. We've just scratched the surface of what you can do with Dojo middleware, but I'm excited about the possibilities and I'm sure you are too!