Consider the following scenario where a UI component in your web app depends on a certain resource on the back-end, but the data that makes up this resource must be aggregated from several different sources — perhaps from some micro-services through asynchronous calls.
In this scenario, let’s say that we expect the UI to display an initial loading screen, which will then turn into a real-time progress status to report what’s going on in the back-end. If everything goes well, it finally renders the desired component when the aggregated data is received.
My SignalR/.NET Core-based library, dotNetify, provides me with the means to set up an asynchronous, real-time communication with a React component that this scenario requires; however, in lieu of conventional imperative style, I want to make it declarative and reusable; something similar to a Promise object — but a component instead — that in effect schedules the display of another component for a value that has not been received.
Here’s the initial design of the component:
<Promise viewModel="MyComponentVM">{resp => <MyComponent {...resp} /></Promise>
By using this declarative style, the intent can be expressed succintly: when the Promise component is rendered, it will initiate a request to the back-end to get the data required to render MyComponent
and to render it when the response is received.
With dotNetify, hydrating the component is done through establishing a real-time connection with a rich view model on the back-end. We can make the view model stateful so it can keep track on those multiple asynchronous calls needed to build the response payload, and send it to our front-end when it’s complete.
To allow for progress reporting, we’ll make the component to accept more properties:
<Promise viewModel="MyComponentVM"isResolved={resp => resp.Result}while={resp => <ProgressComponent steps={resp.Steps} />}{resp => <MyComponent {...resp} /></Promise>
The isResolved
property receives a function used for deciding when to render MyComponent
(when the response contains Result
), and the while
property receives a function that renders ProgressComponent
. The fact that I can just pass a component to another component like this is a really cool feature. With React, your components are first-class citizens.
To summarize: when it’s rendered, the Promise component will establish a real-time connection with MyComponentVM
view model, which will, over a period of time, send multiple response payloads consisting of completed progress steps and finally, the result. The component will render the passed-inProgressComponent
to show the completed steps so far while waiting for the final payload. And when it’s received, it switches to render MyComponent
and fulfill its promise.
The back-end view model is written in C# and runs on multi-platform ASP.NET Core. It inherits from dotNetify’s base class, BaseVM
which gives it the API to designate which property has changed, and the API to push the changed properties to the front-end. For example, the snippet below will push the value of Result
property to the front-end component:
Changed(nameof(Result));PushUpdates();
We will use ReactiveX technology to combine multiple asynchronous data requests into a single data stream that the view model can subscribe to. The provided API allows us to receive completed requests one by one (OnNext
), and gets notified when all responses are received (OnCompleted
).
Here‘s the rudimentary implementation of the Promise component:
You can easily continue to make this more robust, by adding mechanism to show an error message component when there’s network error, or problem with the back-end requests themselves.
Check out a working demo of this at https://github.com/dsuryd/dotnetify-react-demo, under Promise
folder.
Even though this Promise component is not chainable, the need for chaining, which usually involves data aggregation or transformation, is effectively negated by having that logic done inside the back-end view model.
Fetching data from multiple, asynchronous sources for your web app is always a challenge and can bloat your front-end code with concerns that are really not UI-specific. The programming technique shown here brings significant advantages: