What about the backend?
Remember when we discussed how React.js is a UI library and that your business logic goes elsewhere?
Well the time has come to focus on that business logic.
For this we’ll actually use the same project we’re already in (“MyTweets” in my case) but there’s absolutely no reason the code we’re about to write couldn’t be moved into its own project and then shared between different UI projects.
The requirement
So we need to return a list of tweets when the UI asks for it.
The UI will make a request (“please give me a list of tweets”) and our application will respond accordingly.
This Request/Response pattern is repeated in virtually all web applications and enables you to:
a) Retrieve data (query) e.g. retrieve a list of tweets
b) Tell your application to do something (command) e.g. post a new tweet
We need a way to represent this Request/Response pattern in our application and, if we get this right, we can re-use the same application logic whether we opt for React.js, Blazor, Angular or anything else for our UI.
Retrieve a list of tweets using MediatR
There are several ways we can go about implementing this application logic but my preferred one is to use a small library by Jimmy Bogard, called MediatR.
Start by adding this NuGet package to your application; either in Visual Studio via the Package Manager console:
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
Or via a terminal/command line:
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
This will install both MediatR and another package for integrating MediatR with the built-in Microsoft Dependency Injection framework.
Now we need a way to represent our Request and Response in code.
Here’s how I’d do it.
Add a new Features Directory to your project.
In there create a new directory called Tweets.
Now create a List.cs class in our new Tweets folder.
This is where we’re going to represent our Request (query) and Response (model). We’ll start by replacing the contents of List.cs with the following:
using MediatR;
using System.Collections.Generic;
namespace MyTweets.Features.Tweets
{
public class List
{
public class Query : IRequest<Model>
{
}
public class Model
{
public List<string> Tweets { get; set; }
}
}
}
Our Query
class has no properties because we’re not yet thinking about things like search (in which case we’d need to include a property for SearchTerm
here), rather we’re just saying we want all tweets.
The Model
class represents the shape of the data we want to return (in this case, a list of strings which represents our tweets).
Now all we need is a way to handle this request and return our model.
Add a QueryHandler
class to the end of your List
class (directly below Model
).
{
// existing Model class
public class Model
{
public List<string> Tweets { get; set; }
}
// add this nested class...
public class QueryHandler : IRequestHandler<Query, Model>
{
public async Task<Model> Handle(Query request, CancellationToken cancellationToken)
{
return new Model
{
Tweets = new List<string>
{
"One from the server",
"Two from the server",
"Three from the server",
"Four"
}
};
}
}
}
Make sure you’ve added these using statements and you’re good to go.
using MediatR;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
That’s all we need (for now). A simple hardcoded list of tweets.
Hooking it all up
There’s just a couple more things to do if we’re going to invoke this request from our React frontend.
First we need to tell our application we’re using MediatR (so it can find and wire up all our handlers, like the one we just created above).
Head over to Startup.cs and add this anywhere in ConfigureServices
:
services.AddMediatR(Assembly.GetExecutingAssembly());
This instructs MediatR to look for any handlers in the currently executing assembly (or .NET Core API/React project).
You’ll also need to add two using statements to the top of startup.cs:
using MediatR;
using System.Reflection;
Now we have a handy query to run, which will return a list of tweets, but we have no way of running it yet.
We need a way to invoke this query from our React application, take the results and put them in our React component’s state
.
For this we can employ ASP.NET Core Web API.
Add a new controller to the Features/Tweets folder and call it anything you like (I called mine TweetController.cs).
Replace the contents with the following:
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using MyTweets.Features.Tweets;
[Route("[controller]")]
[ApiController]
public class TweetController : ControllerBase
{
private readonly IMediator mediator;
public TweetController(IMediator mediator)
{
this.mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> List()
{
var model = await mediator.Send(new List.Query());
return Ok(model);
}
}
This is how you can invoke any MediatR request, using the Send
method.
In this case we instantiate a new instance of our List.Query
class.
When we “send” this via MediatR, it will locate and execute our handler and return the resulting data (our list of “tweets”).
Call your API from React
Now we can return tweets from our API you can actually test this in the browser.
Head over to https://<your-app-here>/tweet
and you should see the raw JSON tweets data displayed in your browser.
But remember, we have a shiny new React.js component that we want to use to show these tweets, let’s put that to work.
Head over to the ClientApp/src/components/Tweets/List.js component.
Remember this code?
state = {
tweets: [
"One tweet",
"Two tweets",
"Three tweets",
"Four"
]
}
We need to replace this hardcoded state with the data from a call to our API.
For this we can use the native HTTP Fetch API built into javascript (and supported by most modern browsers).
Add a componentDidMount
method below the state declaration in List.js (and above the ‘Render’ method) as follows:
async componentDidMount() {
const response = await fetch('tweet');
const data = await response.json();
this.setState({tweets: data.tweets});
}
But, run this now and you may well get a nasty looking error!
So what gives?
Handling nulls
Our code in the render method is attempting to loop over the tweets array…
{this.state.tweets.map(tweet => <Tweet text={tweet}/>) }
The error occurs because, until the network fetch returns data, this.state.tweets
is not set to anything.
In this case state.tweets
is literally null.
So when we try to call map
on it, javascript complains that it can’t call map
on something which is null.
We know that state.tweets
will be set to an array of tweets one the network call finishes, but by that time React has given up waiting and already shouted its loud, angry looking error at us!
We can easily avoid this however if we simply provide some default state for the list of tweets…
export default class List extends React.Component {
state = { tweets:[] }
async componentDidMount() {
const response = await fetch('tweet');
const data = await response.json();
this.setState({tweets: data.tweets});
}
render() {
return (
<>
<h3>Tweets</h3>
{this.state.tweets.map(tweet => <Tweet text={tweet}/>) }
</>);
}
}
Now React will happily render our component (initially with an empty list of tweets), then update the UI once the network call completes.
Give it a spin
Run your application now; your React component will make a network call to the API, retrieve a list of tweets and render them in the browser!
Nice!
Making progress
It’s worth taking a moment to reflect on what we’ve achieved up to this point.
We now have a clean architecture which enables us to build our application’s logic (queries and commands) in a way which doesn’t box us in to any specific UI framework.
Let’s say we suddenly needed to rewrite our UI (or a part of it) using Blazor Server.
No problem; our MediatR request, response and handlers all stay the same, we would would just invoke them from our Blazor Server components directly (instead of going via an API controller).
Pretty neat huh?
Next we’ll look at introducing a lightweight data store for testing so we can make our application handle new tweets.