The Practical Client
Processing Data with ASP.NET MVC, JSX+React and TypeScript
JSX+React provides a flexible way for you to structure your client-side code to two things you want: dynamically transform your page in response to your user's input, and to integrate with ASP.NET MVC action methods.
Obviously, I think the combination of JSX, React and TypeScript is cool. I showed how to create a simple "Hello, World" Typescript/JSX+React Web page in a previous column (that included showing how to configure an ASP.NET MVC project with TypeScript and React). In my last column, I went one step further and showed how to integrate a read-only form with ASP.NET MVC. This column builds on that to show how to both accept data in that form and how to make AJAX calls back to an Action method in that MVC application.
One caveat before I start, though: This simple application hardly represents JSX+React best practices. To begin with, my simple form consists of a single component (my CustomerForm class and its related element). JSX+React is designed to allow you to work with cooperating components so a real application would consist of multiple components working together. The second issue is that, as you'll see, I've incorporated my update code into my component. Recommended practice is to put all of your update code in one place and pass it (or functions from it) to the various components that make up your application. Because I have a form consisting of just two elements, I'm not going to apply either of those best practices. I'm also going to keep this code as simple and obvious as possible and, again, not everything I'll be doing is necessarily the best way to build a large application. For more on React best practices, you should consider adopting the Flux application architecture.
Managing the Data
The JSX+React application I built in the last column consists of a TypeScript class that extends a React Component. As I discussed in my last column, to use a React Component, I must first define an interface that specifies what properties/attributes that my custom component will have. For this column, I'm going to create a component that has just one property that will accept my TypeScript CustomerDTO object (that object holds the information gathered in my ASP.NET MVC Controller and passed to my View -- see last month's column). That interface looks like this:
interface propsCustomer
{
Customer: CustomerDTO
}
The client-side code that kick-starts my custom component accepts that JSON object and calls the ReactDOM render method to instantiate my CustomerForm component (the custom element I pass to the ReactDOM tells it what class to instantiate and what properties to set). As part of that process, React also calls my component's render method (which generates my page's HTML) and shoves the results into an element on my page:
function DisplayCustomer(cDTO: CustomerDTO) {
ReactDOM.render(
<CustomerForm Customer={cDTO}/>,
$("#divDisplayCustomer").get(0));
}
By passing the full CustomerDTO to my component, I have (within my component) access to all of my CustomerDTO object's properties through React's props object. For example, to retrieve my CustomerD FirstName property, I'd use code like this inside my component:
var name: string;
name = this.props.Customer.FirstName;
So much for my review -- on to the new stuff!
Switching States: Display and Edit
JSX+React is driven by the concept of state. In fact, each component has a built-in state object that holds whatever data you need to represent the current state of the component. For example, my page from last month's column can be considered to be in a state that displays data. I now want to add functionality that will allow the user to switch the page into a state that will accept updates. To support that, I'm going to keep track of two pieces of state information: what mode I'm in (update or not) and whether the user has made changes (is the page dirty or not).
With JSX+React in TypeScript, I use an interface to customize React's state object by specifying what properties I need. In my case, that interface looks like this:
interface stateCustomer
{
Update: boolean;
Dirty: boolean;
}
To use this interface with the React state object, I pass my interface to the base Component class as the second type it should use (the first type I pass to the Component class defines my element's properties). The base Component class will then use my interface to create a state object that I can access through this.state. Here's my CustomerForm class' declaration:
class CustomerForm extends React.Component<propsCustomer, stateCustomer>
{
In the constructor for my class, I should initialize the properties on that state object. In the constructor, as with any derived class in TypeScript, I also have to call the constructor on the base Component class. My constructor looks like this:
constructor(props: propsCustomer)
{
super(props);
this.state = { Update: false, Dirty: false };
}
Now, in my component's render method, I check my state and produce the appropriate HTML. In my case, that means generating text boxes in edit mode. I also need to include buttons in both states that the user can click to switch between modes and to send the data back to the server for processing. With those changes, my render method looks like Listing 1.
Listing 1: Sample JSX+React Render Method Supporting Edit and Update Modes
render()
{
if (this.state.Update)
{
return <p>
First Name: <input type="text" name="FirstName"
defaultValue={this.props.Customer.FirstName} />
<br/>
Last Name: <input type="text" name="LastName"
defaulValue={this.props.Customer.LastName} />
<br/>
<input type="button" value="Display" />
<input type="button" value="Update" />
</p>
}
else
{
return <p>
First Name: {this.props.Customer.FirstName}
<br/>
Last Name: {this.props.Customer.LastName}
<br/>
<input type="button" value="Edit" />
</p>
}
Initially, this might look like an inefficient way to dynamically update my form because I'm replacing my whole page on each change. There are two things to remember here: The first is that a real JSX+React page would consist of multiple cooperating components so replacing my whole form, as I do here, isn't the typical case; the second is that JSX+React is smart enough not to replace everything. React actually does a diff on the existing page and the new page, replacing or changing only what's necessary.
Wiring up Events
Now it's time to add some code. Like the Model-View-ViewModel (MVVM) pattern, a JSX+React Component holds both data and behavior. Therefore, to handle updating my interface, I add a method to my component that changes my state. Provided I make that change by calling the base Component's setState method, React will take care of calling my render method whenever my state changes. The code to retrieve the state object, toggle the Update property and pass it to React's setState method looks like this:
public changeMode()
{
var custState: stateCustomer;
custState = this.state;
custState.Update = !custState.Update;
this.setState(custState);
}
To wire that method up to my buttons, I add a JSX onClick attribute to my button's element, using an arrow expression to tie my component's method to the event:
<input type="button" value="Display" onClick={ e => this.changeMode() } />
My next method supports sending the data from the page back to the server for updates, switching out of edit mode, and marking my page as no longer dirty. I'll send my whole CustomerDTO object back to the server so that method looks like this:
public UpdateCustomer()
{
if (this.state.Dirty)
{
$.post("/Home/CustomerUpdate",
this.props.Customer,
function (data)
{
this.setState({ Update: false, Dirty: false });
});
}}
Tying that method to my update button looks like this:
<input type="button" value="Update" onClick={ e => this.UpdateCustomer() } />
Switching back to the server, I now need to write the Action method in my Home controller that will accept that request. That method needs to accept the ModelView object that I passed to the View that initially generated the page. My Action method is very simple (if, of course, I leave out all of the actual data processing):
[HttpPost]
public ActionResult CustomerUpdate(CustomerDTO custDTO)
{
if (ModelState.IsValid)
{
...update logic...
}
return Json(custDTO);
}
Data Binding
Of course, sending my props.Customer object back to the server for updating is only going to be useful if the changes that the user has made in my page have been transferred to the properties on the CustomerDTO object. I'll start this process by wiring up onChange events on my textboxes to another method, but this time, I'll pass the React object that's associated with the event to the method. The revised textbox elements look like this:
<input type="text" name="FirstName" defaultValue={this.props.Customer.FirstName}
onChange={ e => this.handleChange(e) }/>
<input type="text" name="LastName" defaultValue={this.props.Customer.LastName}
onChange={ e => this.handleChange(e) }/>
My handleChange method will accept that event and use its target property to access the name and the value of the element that fired the event. Here's one solution to moving data from the element into my CustomerDTO object held in the base Component's props property:
public handleChange(e: any)
{
switch (e.target.name)
{
case "FirstName":
this.props.Customer.FirstName = e.target.value;
case "LastName":
this.props.Customer.FirstName = e.target.value;
}
this.setState({ Dirty: true, Update: true });
}
As you can see, I also grasp the opportunity to set my state object's Dirty property while staying in Update mode.
There is, of course, much more to do with JSX+React than I've outlined in these three columns. But now that you've seen how TypeScript works with JSX+React, from this point on, exploiting this technology is all about learning JSX+React.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.