PageRenderTime 2ms CodeModel.GetById 3ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

Plain Text | 1095 lines | 895 code | 200 blank | 0 comment | 0 complexity | 885c8b28bc069796ff39d43628e84049 MD5 | raw file
   1## Writing React apps using Bridge.NET - The Dan Way (from first principles)
   3*(This is part one of a three part series, each post is longer than the last so strap yourself in if you're thinking of playing along - hopefully you'll think that it was worth it by the end! :)*
   5I've had a request from someone to write about how I think that someone from a .net background should write a web-based application. The short answer is that I strongly believe that using Bridge.NET with React, using a Flux-like architecture is the way forward. I think that React changed the game by trying to introduce a way to write applications that addressed the big challenges, rather than what many of the other frameworks did, which was to try to make some aspects of development easier but without tackling the underlying problems. We're going to be using these technologies to create some big applications where I work and some resources as to how best to do this will be valuable. I'm going to try to roll this all together into a short series of posts about creating Bridge / React / Flux apps, where I'll try to start from the simplest approach at each point and only introduce something new when I can explain why it's valuable. So, initially, Flux will be nowhere to be seen - but, hopefully, when it *is* introduced, it will be clear why.
   7(I'm not going to expel any effort on convincing you that writing C# in Visual Studio is incredibly powerful, and that it's fantastic to be able to do this while writing browser-based applications, nor am I going to try to sell you any more on React - if you're not on board with these ideas already then there's a *chance* that these posts will sell you on it, but that's not going to be my main focus).
   9### From the very start
  11I'm going to begin from a completely fresh project, so if you've got any experience with Bridge then these steps will be familiar. But I'll go through them quickly and then start building the application itself. It's going to be extremely simple but will illustrate how to work with React and how to deal with user input and Virtual DOM re-rendering, how and where to implement validation and how to make *all the things* asynchronous so that async is not only used for scary edge cases and can be seen as a valuable tool to decouple code (and, in doing so, async will become a not-at-all-scary thing).
  13All that the application will do will be to allow the user to write a message, entering  Title and Content strings, and to save this message. There will be a "Message API", which will emulate reading and writing to a remote endpoint, for data persistence, but the implementation will all be kept in-memory / in the browser, just to make things simple. It will look something like this:
  15<img alt="The proposed example app" src="/Content/Images/Posts/ReactTutorial1.png" class="NoBorder AlwaysFullWidth" />
  17As more messages are written, more entries will appear in the "Message History". Seems simple.
  19### React components
  21Before getting into any React-with-Bridge specifics, I want to talk a little about React components; how to arrange them into a hierarchy and how they should and shouldn't talk to each other.
  23Almost all of the time, components that you use will be "[controlled components](" -
  25> A Controlled component does not maintain its own internal state; the component renders purely based on props.
  27This means that when you render a text input, you give it a "value" property and an "onChange" property - when the user tries to change what's in the text box (whether by pressing a number or letter, or by pressing backspace or by pasting something in) then the "onChange" callback is executed. The text box is *not* updated automatically, all that happens is that a callback is made that indicates that the user has done something that means that the text input probably *should* be updated.
  29This seems odd at the very start since you may be used to events being driven by html elements and the updates being broadcast elsewhere; with React, events arise from components that describe the desire for a change to be made, but the change does not happen automatically. This is what is meant in the quote above when it says that a controlled component "does not maintain its own internal state".
  31This hints at one of the key aims of React - to make code explicit and easy to reason about. If a component *only* varies based upon its props, then it's very easy to reason about; given this props data, draw in this manner. (If user-entered changes to a text input were automatically reflected in the text box then the component would *not* solely vary by its props, it would vary according to its props and whatever else the user has done to it).
  33The only way for a component to update / re-render itself (and any child components that it may have) is for it to changes its "state". This is a special concept in React - if "SetState" is called then the component will re-render, but now it may have to consider both its props *and* its new state. If we really wanted to have a text input that would automatically update its own value as well as raise a change event, we could write a component to do so -
  35*(Note: if you're coming into this fresh, don't worry about how to compile this C# code into a React application, I'll be getting to that after I've finished giving my view on React components).*
  37    public class TextInput : Component<TextInput.Props, TextInput.State>
  38    {
  39      public TextInput(Props props) : base(props) { }
  41      public override ReactElement Render()
  42      {
  43        return DOM.Input(new InputAttributes
  44        {
  45          Type = InputType.Text,
  46          Value = (state == null) ? props.InitialValue : state.Value,
  47          OnChange = ev =>
  48          {
  49            var newValue = ev.CurrentTarget.Value;
  50            SetState(new State { Value = newValue });
  51            props.OnChange(newValue);
  52          }
  53        });
  54      }
  56      public class Props
  57      {
  58        public string InitialValue;
  59        public Action<string> OnChange;
  60      }
  62      public class State
  63      {
  64        public string Value;
  65      }
  66    }
  68The problem here is that now the component depends upon two things whenever it has to render - its props *and* its state. It can change its own state but it can't change its props (React demands that a components props be considered to be immutable).
  70*This* means that the component becomes more difficult to reason about, it was much easier when it didn't have to worry about state. (Granted, there may have been some question as to who would receive that OnChange callback to get the component to re-render, but we're going to get to that shortly).
  72Partly for this reason, it's strongly recommended that the vast majority of components be stateless - meaning that they render according to their props and nothing else.
  74Another reason that it is strongly recommended that components not update themselves (meaning that they are stateless, since the only way for a component to update itself is to change its state) is that it makes the handling of events much clearer. In the example application that I'm going to refer to in this series, the "Title" value that is entered by the user is reflected in the fieldset legend -
  76<img alt="Fieldset legend mirros the Title input value" src="/Content/Images/Posts/ReactTutorial2.png" class="NoBorder AlwaysFullWidth" />
  78If the "Title" input box was to maintain its own state and update itself when its contents change, there still needs to be something listening for changes in order to update the fieldset legend text. If it was common for components to maintain their own state then things would quickly get out of hand as more and more components have to listen for (and react to) changes in other components. Just in the example here, there is a validation message that needs to be hidden if the "Title" input has a non-blank value, so that component would need to listen for the change event on the input. (Alternatively, the **TextInput** component could be provided with validation logic and *it* would be responsible for showing or hiding the validation message - which would complicate the **TextInput** class). On top of this, there is the "Save" button which should be disabled if either of the "Title" or "Content" input boxes have no value - so the Save button component would need to listen to change events from both text inputs and decide whether or not it should be enabled based upon their states. Maybe the input form itself wants to add an "invalid" class to itself for styling purposes if either of the inputs are invalid - now the form component has to listen to changes to the text inputs and add or remove this class. This way lies madness.
  80In summary, most components should *not* try to update themselves and so do not need state. The React bindings make it easy to write components that don't use state (again, I'll talk about using these bindings more shortly, I just wanted to point out now that the distinction between stateful and stateless components is an important one and that the bindings reflect this) -
  82    public class TextInput : StatelessComponent<TextInput.Props>
  83    {
  84      public TextInput(Props props) : base(props) { }
  86      public override ReactElement Render()
  87      {
  88        return DOM.Input(new InputAttributes
  89        {
  90          Type = InputType.Text,
  91          Value = props.Value,
  92          OnChange = ev => props.OnChange(ev.CurrentTarget.Value)
  93        });
  94      }
  96      public class Props
  97      {
  98        public string Value;
  99        public Action<string> OnChange;
 100      }
 101    }
 103The component code is much more succinct this way, as well as helping us avoid the nightmare scenario described above.
 105It does leave one big question, though.. if these components don't update themselves, *then what does?*
 107Answer: There should be a top-level "Container Component" that maintains state for the application. This should be the only stateful component, all components further down the hierarchy should be stateless.
 109In the sample application here -
 111<img alt="The proposed example app" src="/Content/Images/Posts/ReactTutorial1.png" class="NoBorder AlwaysFullWidth" />
 113The component hierarchy will look something like this:
 115    AppContainer
 116      MessageEditor
 117        Span ("Title")
 118        ValidatedTextInput
 119          Input
 120        Span ("Content")
 121        ValidatedTextInput
 122          Input
 123        Button
 124      MessageHistory
 125        Div
 126          Span (Message Title)
 127          Span (Message Content)
 128        Div
 129          Span (Message Title)
 130          Span (Message Content)
 132The **MessageHistory** will be a read-only component tree (it just shows saved messages) and so is very simple (there are no callbacks to handle). The **MessageEditor** will render Span labels ("Title" and "Content"), two **ValidatedTextInput** components and a "Save" button. The **ValidatedTextInput** has props for a current text input value, an on-change callback and an optional validation message.
 134When an input component's on-change callback is executed, it is an action with a single argument; the html element. In the **TextInput** example class above, the new value is extracted from that element ("ev.CurrentTarget.Value") and then passed into the on-change callback of the **TextInput**, which is an action with a simple string argument. **ValidatedTextInput** will be very similar (it will wrap the **Action&lt;InputElement&gt;** callback that the input raises in a simpler **Action&lt;string&gt;**). The only difference between it and the **TextInput** example class earlier is that it  will also be responsible for rendering a validation message element if its props value has a non-blank validation message to show (and it may apply an "invalid" class name to its wrapper if there is a validation message to show).
 136When the Title or Content **ValidatedTextInput** raise an on-change event, the **MessageEditor** will execute some code that translates this callback further. The **MessageEditor** will have an on-change props value whose single argument is a **MessageDetails** - this will have have values for the current "Title" and "Content". Just as an on-change from an input element resulted in an on-change being raised by a **ValidatedTextInput**, an on-change by a **ValidatedTextInput** will result in an on-change from the **MessageEditor**. Each on-change event changes the type of value that the on-change describes (from an input element to a string to a **MessageDetails**). The **MessageEditor**'s on-change will be received by the **AppContainer** Component, which is where the change will result in the component tree being re-rendered.
 138The **AppContainer** component will re-render by calling "SetState" and creating a new state reference for itself that include the new **MessageDetails** reference (that was passed up in the on-change callback from the **MessageEditor**). The call to "SetState" will result in the component being re-rendered, which will result in it rendering a new version of the **MessageEditor**. When the **MessageEditor** is rendered, the current "Title" value will be used to populate the text input *and* to set the text in the legend of the fieldset that wraps the editor's input boxes. This is how the "nightmare scenario" described earlier is avoided - instead of having lots of components listen out to events from lots of *other* components, all components just pass their events up to the top and then the entire UI is re-rendered in React's Virtual DOM.
 140I'm going to repeat that part about event-handling because it's important; events are passed *up* from where they occur, up to the top-level component. This will trigger a re-render, which works all the way *down* through the component tree, so that the requested change is then reflected in the UI.
 142The Virtual DOM determines what (if anything) needs to change in the browser's DOM and applies those changes - this works well because the Virtual DOM is very fast (and so we can do these "full Virtual DOM re-renders" frequently) and it minimises changes to the browser DOM (which is much slower).
 144*(The Facebook tutorial [Thinking in React]( talks about how to mentally break down a UI into components and talks about passing state up the tree, but I wanted to try to really drive home how components should be put together and how they should communicate before fobbing you off with a Facebook link)*.
 146I have some more recommendations on how to decide what to put into props and what into state when creating stateful container components, but I'll cover that ground after some more practical work.
 148### Let's start coding then!
 150Open up Visual Studio (the version isn't too important, but if you're using 2015 then bear in mind that Bridge.NET doesn't yet support C# 6 syntax). Create a new "Class Library" project. Using NuGet, add the "Bridge" and the "Bridge.React" packages. This will bring in bindings for React as well as pulling in Bridge itself - the Bridge package removes the usual System, System.Collections, etc.. references and replaces them with a single "Bridge" reference, which re-implements those framework methods in code that has JavaScript translations.
 152The Bridge package also adds some README files and a bridge.json file (under the Bridge folder in the project), which instructs Bridge how to compile your C# code into JavaScript. Change bridge.json's content to:
 154    {
 155	  "output": "Bridge/output",
 156	  "combineScripts":  true
 157    }
 159This will tell it create a single JavaScript file when translating, including the Bridge library content and the React bindings and JavaScript generated from code that you write. The name of the file that it generates is based upon the name of your project. I named mine "BridgeReactTutorial" and so the Bridge compiler will generate "BridgeReactTutorial.js" and "BridgeReactTutorial.min.js" files in the "Bridge/output" folder on each build of the project.
 161Now change demo.html (which is another file that the Bridge NuGet package created, it will be in the Brige/www folder) to the following:
 163    <!DOCTYPE html>
 164    <html lang="en" xmlns="">
 165    <head>
 166      <meta charset="utf-8" />
 167      <title>Bridge.React Tutorial</title>
 168      <link rel="Stylesheet" type="text/css" href="styles.css" media="screen" />
 169    </head>
 170    <body>
 171      <noscript>JavaScript is required</noscript>
 172      <div id="main" class="loading">Loading..</div>
 173      <script src=""></script>
 174      <script src=""></script>
 175      <script src="../output/BridgeReactTutorial.js"></script>
 176    </body>
 177    </html>
 179*(If you called your project something other than "BridgeReactTutorial" then you might have to change the filename in that last script tag).*
 181This file will load in the latest (0.14.7, as of March 2016) version of the React library along with the Bridge / React-bindings / your-code bundle. All we need to do now is write some "your-code" content.
 183When you created the class library project, a Class1.cs file will have been added to the project. Change its contents -
 185    using System.Linq;
 186    using Bridge.Html5;
 187    using Bridge.React;
 189    namespace BridgeReactTutorial
 190    {
 191      public class Class1
 192      {
 193        [Ready]
 194        public static void Main()
 195        {
 196          var container = Document.GetElementById("main");
 197          container.ClassName = string.Join(
 198		    " ",
 199			container.ClassName.Split().Where(c => c != "loading")
 200		  );
 201          React.Render(
 202            DOM.Div(new Attributes { ClassName = "welcome" }, "Hi!"),
 203            container
 204          );
 205        }
 206      }
 207    }
 209Build the solution and then right-click on the "demo.html" file in the project and click on "View in Browser". You should see a happy little "Hi!" welcome message, rendered using React by JavaScript that was translated from C# - an excellent start!
 211There are some subtle touches here, such as the "JavaScript is required" message that is displayed if the browser has JavaScript disabled (just in case you ever turn it off and forget!) and a "loading" message that is displayed while the JavaScript sorts itself out (usually this will be a barely-perceptibe amount of time but if the CDN host that the React library is coming from is being slow then it may not be instantaneous). The "main" div initially has a "loading" class on it, which is removed when the code above executes. Note that the [Ready] attribute on the "Main" function is a Bridge attribute, indicating code that should be called when the page has loaded (similar in principle to on-DOM-ready, frequently used by jQuery code).
 213To take advantage of the "loading" class' presence / absence, it would be a nice touch to have the "loading" text quite pale initially (it's reassuring to know that the app is, in fact, loading, but it doesn't need to be right in your face). To do so, add a file "styles.css" alongside the "demo.html" file. It's already referenced by the markup we've pasted into "demo.html", so it will be picked up when you refresh the page. Since we're creating a stylesheet, it makes sense to include some style resets (my go-to for this is by Eric Meyer) -
 215	/* v2.0b1 | 201101 NOTE: WORK IN PROGRESS
 217	html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote,
 218	pre,a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s,
 219	samp, small, strike, strong, sub, sup, tt, var,b, u, i, center, dl, dt, dd, ol, ul,
 220	li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td,
 221	article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu,
 222	nav, section, summary, time, mark, audio, video
 223	{
 224	  margin: 0;
 225	  padding: 0;
 226	  border: 0;
 227	  outline: 0;
 228	  font-size: 100%;
 229	  font: inherit;
 230	  vertical-align: baseline;
 231	}
 232	/* HTML5 display-role reset for older browsers */
 233	article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav,
 234	section { display: block; }
 235	body { line-height: 1; }
 236	ol, ul { list-style: none; }
 237	blockquote, q { quotes: none; }
 238	blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; }
 239	/* remember to highlight inserts somehow! */ ins { text-decoration: none; }
 240	del { text-decoration: line-through; }
 241	table { border-collapse: collapse; border-spacing: 0; }
 243	div#main.loading { color: #f1f1f1; }
 245At this point, I also tend to remove the "App_Readme" folder that the Bridge package adds to my project - if I'm going to write some code and check it into source control somewhere then I don't think there's a lot of point in storing a copy of the Bridge README and LICENSE each time.
 247### Creating the Message Editor
 249That's the theory and the project scaffolding out of the way. Now to create a form that actually does something.
 251We've already seen how a **TextInput** component is helpful for wrapping a text input and simplifying the "OnChange" callback. So create a "Components" folder with a "TextInput.cs" file and paste in the following content -
 253	using System;
 254	using Bridge.Html5;
 255	using Bridge.React;
 257	namespace BridgeReactTutorial.Components
 258	{
 259	  public class TextInput : StatelessComponent<TextInput.Props>
 260	  {
 261		public TextInput(Props props) : base(props) { }
 263		public override ReactElement Render()
 264		{
 265		  return DOM.Input(new InputAttributes
 266		  {
 267			Type = InputType.Text,
 268			ClassName = props.ClassName,
 269			Value = props.Content,
 270			OnChange = e => props.OnChange(e.CurrentTarget.Value)
 271		  });
 272		}
 274		public class Props
 275		{
 276		  public string ClassName;
 277		  public string Content;
 278		  public Action<string> OnChange;
 279		}
 280	  }
 281	}
 283*(Note: When adding a new ".cs" file to a project, sometimes "System" will sneak back into the list of references in the project - this can confuse Bridge, so ensure that you remove the reference again if it gets added).*
 285Now create another folder in the root of the project called "ViewModels". Add a new file to it; "MessageDetails.cs" and paste in the following content -
 287	namespace BridgeReactTutorial.ViewModels
 288	{
 289	  public class MessageDetails
 290	  {
 291        public string Title;
 292        public string Content;
 293	  }
 294    }
 296Now add another file to the "Components" folder; "MessageEditor.cs" and paste in this:
 298	using System;
 299	using Bridge.React;
 300	using BridgeReactTutorial.ViewModels;
 302	namespace BridgeReactTutorial.Components
 303	{
 304	  public class MessageEditor : StatelessComponent<MessageEditor.Props>
 305	  {
 306		public MessageEditor(Props props) : base(props) { }
 308		public override ReactElement Render()
 309		{
 310		  return DOM.FieldSet(new FieldSetAttributes { ClassName = props.ClassName },
 311			DOM.Legend(null, string.IsNullOrWhiteSpace(props.Title) ? "Untitled" : props.Title),
 312			DOM.Span(new Attributes { ClassName = "label" }, "Title"),
 313			new TextInput(new TextInput.Props
 314			{
 315			  ClassName = "title",
 316			  Content = props.Title,
 317			  OnChange = newTitle => props.OnChange(new MessageDetails
 318			  {
 319				Title = newTitle,
 320				Content = props.Content
 321			  })
 322			}),
 323			DOM.Span(new Attributes { ClassName = "label" }, "Content"),
 324			new TextInput(new TextInput.Props
 325			{
 326			  ClassName = "content",
 327			  Content = props.Content,
 328			  OnChange = newContent => props.OnChange(new MessageDetails
 329			  {
 330				Title = props.Title,
 331				Content = newContent
 332			  })
 333			})
 334		  );
 335		}
 337		public class Props
 338		{
 339		  public string ClassName;
 340		  public string Title;
 341		  public string Content;
 342		  public Action<MessageDetails> OnChange;
 343		}
 344	  }
 345	}
 347Now things are getting interesting!
 349This is still a stateless component and so what is rendered depends solely and reliably upon its props data. When it renders, the "Title" value from its props is used to populate both the legend of the fieldset that it renders (unless "Title" is null, blank or white-space-only, in which case the legend text will be "Untitled) and it's used to populate the "Title" **TextInput**. When either of its **TextInput**s raises an on-change event, the **MessageEditor** raises its on-change events with a new **MessageDetails** instance.
 351Note that there's no validation yet. We'll get this rough version working first and then add that later.
 353There are still a few more steps until we have an application, though. We need a container component to render the form in the first place and to deal with on-change events that bubble up. Create another class file within the "Components" folder named "AppContainer.cs" -
 355	using Bridge.Html5;
 356	using Bridge.React;
 357	using BridgeReactTutorial.ViewModels;
 359	namespace BridgeReactTutorial.Components
 360	{
 361	  public class AppContainer : Component<object, AppContainer.State>
 362	  {
 363		public AppContainer() : base(null) { }
 365		protected override State GetInitialState()
 366		{
 367		  return new State
 368		  {
 369			Message = new MessageDetails { Title = "", Content = "" }
 370		  };
 371		}
 373		public override ReactElement Render()
 374		{
 375		  return new MessageEditor(new MessageEditor.Props
 376		  {
 377			ClassName = "message",
 378			Title = state.Message.Title,
 379			Content = state.Message.Content,
 380			OnChange = newMessage => SetState(new State { Message = newMessage })
 381		  });
 382		}
 384		public class State
 385		{
 386		  public MessageDetails Message;
 387		}
 388	  }
 389	}
 391This is the *stateful* component that will trigger re-renders when required. It doesn't actually require any props data at this time, so the "TProps" type parameter specified on the **Component&lt;TProps, TState&gt;** base class is just "object".
 393When the **MessageEditor** raises an on-change event, the **AppContainer** will call SetState to replace its current **MessageDetails** instance with the new one. This will trigger a re-render of the **MessageEditor**, which will be given the new **MessageDetails** instance as part of a new props value. It might seem a bit silly to have the **MessageEditor** pass up a new **MessageDetails** instance and then to just pass this back down into another **MessageEditor**, but the idea is to consider the first **MessageEditor** to be dead now and for the new **MessageEditor** (with the new **MessageDetails**) to exist in its place. And each time a stateless component is rendered, it renders simply from its props - there is no data shared between the new instance and the instance it replaces. This, again, makes the components very easy to reason about. And code that is easy to reason about is easy to write and easy to maintain.
 395*Note: If you're au fait with React then you might know that components written as ES6 classes - which seems to be the way that is encouraged at the moment - don't support "GetInitialState" and, instead, specify initial state in the constructor. In the Bridge React bindings, "GetInitialState" should be used and the constructor should NOT be used - the way that the components are initialised by React means that constructors on component classes are not actually executed, so it is important that the constructor ONLY be used to pass the props and/or state to the base class.*
 397The penultimate step is to change "Class1.cs" to render the **AppContainer** instead of just rendering a "Hi!" div. While we're editing it, let's give it a more official-sounding name. I like the starting point of my application to be called "App" -
 399	using System.Linq;
 400	using Bridge.Html5;
 401	using Bridge.React;
 402	using BridgeReactTutorial.Components;
 404	namespace BridgeReactTutorial
 405	{
 406	  public class App
 407	  {
 408		[Ready]
 409		public static void Go()
 410		{
 411		  var container = Document.GetElementById("main");
 412          container.ClassName = string.Join(
 413		    " ",
 414			container.ClassName.Split().Where(c => c != "loading")
 415		  );
 416		  React.Render(new AppContainer(), container);
 417		}
 418	  }
 419	}
 421All that's required now is to make it look a little nicer when you view "demo.html", so add the following to "styles.css" -
 423	body
 424	{
 425	  font-family: 'Segoe UI';
 426	  padding: 8px;
 427	}
 429	fieldset
 430	{
 431	  padding: 8px;
 432	  border: 1px solid #f1f1f1;
 433	  border-radius: 4px;
 434	}
 435	fieldset legend
 436	{
 437	  color: blue;
 438	  padding: 0 8px;
 439	}
 440	fieldset.message span.label { padding: 0 8px; }
 442That's the first major milestone reached! A very basic framework for constructing component hierarchies has been demonstrated, along with a way to handle events and re-render as required. There's nothing very radical, it's just what was described earlier; but it's good to see the theory executed in practice.
 444I'm far from finished for today, though - I want to add a way to persist messages, a message history component and some validation. Best get cracking!
 446### Message persistence
 448While I want to simulate a server-based API, where read / write requests aren't instantaneous and we need to think about how to deal with async calls, I don't want the overhead of needing an endpoint to be configured somewhere. So we'll go with a simple interface that will be implemented in an entirely client-side class, that introduces artifical delays to mimic server-calling time.
 450Create a new folder in the project root called "API" and add a new .cs file "IReadAndWriteMessages.cs", the contents of which should be:
 452	using System.Threading.Tasks;
 453	using BridgeReactTutorial.ViewModels;
 455	namespace BridgeReactTutorial.API
 456	{
 457	  public interface IReadAndWriteMessages
 458	  {
 459		Task SaveMessage(MessageDetails message);
 460	  }
 461	}
 463We'll be using dependency injection to provide the **AppContainer** with an API implementation. In order to enable unit testing (which will come later) we need to be able to work against an interface. For now, the interface only has a "SaveMessage" method, we'll work on reading message history data later.
 465Add another file into the "API" folder, "MessageApi.cs" -
 467	using System;
 468	using System.Threading.Tasks;
 469	using Bridge.Html5;
 470	using BridgeReactTutorial.ViewModels;
 472	namespace BridgeReactTutorial.API
 473	{
 474	  public class MessageApi : IReadAndWriteMessages
 475	  {
 476		public Task SaveMessage(MessageDetails message)
 477		{
 478		  if (message == null)
 479			throw new ArgumentNullException("message");
 480		  if (string.IsNullOrWhiteSpace(message.Title))
 481			throw new ArgumentException("A title value must be provided");
 482		  if (string.IsNullOrWhiteSpace(message.Content))
 483			throw new ArgumentException("A content value must be provided");
 485		  var task = new Task<object>(null);
 486		  Window.SetTimeout(
 487			() => task.Complete(),
 488			1000 // Simulate a roundtrip to the server
 489		  );
 490		  return task;
 491		}
 492	  }
 493	}
 495Bridge supports the C# "async" keyword and provides its own implementation of Tasks, which are used above to pretend that this class is communicating with a server when a save is requested.
 497In order to enable saving, the **MessageEditor** needs a "Save" button and it needs an "on-save" callback to be specified on its props. While saving, the form should be disabled, so the **MessageEditor** props need a "Disabled" flag as well.
 499*When designing an SPA like this, you need to think about whether you will support "optimistic updates", where clicking Save clears the form and acts as if the save action was instanteously accepted - but brings it to the user's attention somehow if the save failed or was rejected. I'm going to go for a simpler "pessimistic update" flow, where the form is disabled until the save is acknowledged, at which point the form will be cleared and re-enabled so that a further entry may be written and then saved.*
 501The **MessageEditor** should now looks like this:
 503	using System;
 504	using Bridge.React;
 505	using BridgeReactTutorial.ViewModels;
 507	namespace BridgeReactTutorial.Components
 508	{
 509	  public class MessageEditor : StatelessComponent<MessageEditor.Props>
 510	  {
 511		public MessageEditor(Props props) : base(props) { }
 513		public override ReactElement Render()
 514		{
 515		  return DOM.FieldSet(new FieldSetAttributes { ClassName = props.ClassName },
 516			DOM.Legend(null, string.IsNullOrWhiteSpace(props.Title) ? "Untitled" : props.Title),
 517			DOM.Span(new Attributes { ClassName = "label" }, "Title"),
 518			new TextInput(new TextInput.Props
 519			{
 520			  ClassName = "title",
 521			  Disabled = props.Disabled,
 522			  Content = props.Title,
 523			  OnChange = newTitle => props.OnChange(new MessageDetails
 524			  {
 525				Title = newTitle,
 526				Content = props.Content
 527			  })
 528			}),
 529			DOM.Span(new Attributes { ClassName = "label" }, "Content"),
 530			new TextInput(new TextInput.Props
 531			{
 532			  ClassName = "content",
 533			  Disabled = props.Disabled,
 534			  Content = props.Content,
 535			  OnChange = newContent => props.OnChange(new MessageDetails
 536			  {
 537				Title = props.Title,
 538				Content = newContent
 539			  })
 540			}),
 541			DOM.Button(
 542			  new ButtonAttributes { Disabled = props.Disabled, OnClick = e => props.OnSave() },
 543			  "Save"
 544			)
 545		  );
 546		}
 548		public class Props
 549		{
 550		  public string ClassName;
 551		  public string Title;
 552		  public string Content;
 553		  public Action<MessageDetails> OnChange;
 554		  public Action OnSave;
 555		  public bool Disabled;
 556		}
 557	  }
 558	}
 560The "Disabled" flag needs to be able to be applied to the **TextInput** components, so **TextInput** needs to look like this:
 562	using System;
 563	using Bridge.Html5;
 564	using Bridge.React;
 566	namespace BridgeReactTutorial.Components
 567	{
 568	  public class TextInput : StatelessComponent<TextInput.Props>
 569	  {
 570		public TextInput(Props props) : base(props) { }
 572		public override ReactElement Render()
 573		{
 574		  return DOM.Input(new InputAttributes
 575		  {
 576			Type = InputType.Text,
 577			ClassName = props.ClassName,
 578			Disabled = props.Disabled,
 579			Value = props.Content,
 580			OnChange = e => props.OnChange(e.CurrentTarget.Value)
 581		  });
 582		}
 584		public class Props
 585		{
 586		  public string ClassName;
 587		  public bool Disabled;
 588		  public string Content;
 589		  public Action<string> OnChange;
 590		}
 591	  }
 592	}
 594This enables the **MessageEditor** to initiate a save request and for a "Message API" to process the request. Now the **AppContainer** needs to tie these two aspects together.
 596*Note that the OnSave action on the **MessageEditor** doesn't provide a new **MessageDetails** instance - that is because the Title and Content value that are rendered in the **MessageEditor** could not have been changed since the component was rendered, otherwise an OnChange callback would have been made before OnSave.*
 598Now, the **AppContainer** gets a bit more interesting because it requires props *and* state. Its props will be external dependencies that it requires access to, while its state will be a copy of all data that is required to render the form. This is a good time to introduce my React (stateful) component guidelines -
 6001. A stateful component's "props" data should *only* consist of references to external dependencies
 6011. A stateful component's "state" data should include *everything* required to render the component tree, though the props may be required to deal with child components' events
 603At this point, these rules are going to seem very straight-forward. Later, however, things will get a little more nuanced and I'll re-visit them at that point.
 605The **AppContainer** will now become the following -
 607	using Bridge.React;
 608	using BridgeReactTutorial.API;
 609	using BridgeReactTutorial.ViewModels;
 611	namespace BridgeReactTutorial.Components
 612	{
 613	  public class AppContainer : Component<AppContainer.Props, AppContainer.State>
 614	  {
 615		public AppContainer(AppContainer.Props props) : base(props) { }
 617		protected override State GetInitialState()
 618		{
 619		  return new State
 620		  {
 621		    Message = new MessageDetails { Title = "", Content = "" },
 622		    IsSaveInProgress = false
 623		  };
 624		}
 626		public override ReactElement Render()
 627		{
 628		  return new MessageEditor(new MessageEditor.Props
 629		  {
 630			ClassName = "message",
 631			Title = state.Message.Title,
 632			Content = state.Message.Content,
 633			OnChange = newMessage => SetState(new State
 634			{
 635			  Message = newMessage,
 636			  IsSaveInProgress = state.IsSaveInProgress
 637			}),
 638			OnSave = async () =>
 639			{
 640			  SetState(new State { Message = state.Message, IsSaveInProgress = true });
 641			  await props.MessageApi.SaveMessage(state.Message);
 642			  SetState(new State
 643			  {
 644				Message = new MessageDetails { Title = "", Content = "" },
 645				IsSaveInProgress = false
 646			  });
 647			},			
 648			Disabled = state.IsSaveInProgress
 649		  });
 650		}
 652		public class Props
 653		{
 654		  public IReadAndWriteMessages MessageApi;
 655		}
 657		public class State
 658		{
 659		  public MessageDetails Message;
 660		  public bool IsSaveInProgress;
 661		}
 662	  }
 663	}
 665You will need to update App.cs to pass a props reference with a **MessageApi** instance to the **AppContainer** constructor -
 667	using System.Linq;
 668	using Bridge.Html5;
 669	using Bridge.React;
 670	using BridgeReactTutorial.API;
 671	using BridgeReactTutorial.Components;
 673	namespace BridgeReactTutorial
 674	{
 675	  public class App
 676	  {
 677		[Ready]
 678		public static void Go()
 679		{
 680		  var container = Document.GetElementById("main");
 681		  container.ClassName = string.Join(
 682			" ",
 683			container.ClassName.Split().Where(c => c != "loading")
 684		  );
 685		  React.Render(
 686			new AppContainer(new AppContainer.Props { MessageApi = new MessageApi() }),
 687			container
 688		  );
 689		}
 690	  }
 691	}
 693With this final piece, we have the outline of a fully functioning application! Granted, its functionality is not particular magnificent, but it *has* illustrated some important principles. We've seen how a component hierarchy should have a top-level *stateful* component, with a component tree beneath it of state*less* components (note that there are no guidelines required regarding what to put into props and what to put into state when writing a stateless component because props is your only option - another reason why stateless components are so much simpler!). We've also seen how we can deal with dependency injection for these top level components, which are the only point at which more complicated logic appears such as "a save request involves disabling the form, calling a method on the API, waiting for the result and then re-enabling the form". It's worth noting that in the next post, this logic will be moved out of the top-level component in a quest to make components as dumb as possible - but that's jumping ahead, and I want the format of these posts to be that we start simple and then get more complicated only as the benefits of doing so can be made clear.
 695At this point, however, we have something of a problem. If the "Title" and "Content" text inputs do not both have values, then an exception will be raised by the **MessageApi** when a save is attempted. To avoid this, we need some..
 697### Validation
 699I mentioned in the "React components" section that there would be a **ValidatedTextInput**, but no code had been presented yet. So here we go, nothing in it should be particularly surprising -
 701	using System;
 702	using Bridge.React;
 704	namespace BridgeReactTutorial.Components
 705	{
 706	  public class ValidatedTextInput : StatelessComponent<ValidatedTextInput.Props>
 707	  {
 708		public ValidatedTextInput(Props props) : base(props) { }
 710		public override ReactElement Render()
 711		{
 712		  var className = props.ClassName;
 713		  if (!string.IsNullOrWhiteSpace(props.ValidationMessage))
 714			className = (className + " invalid").Trim();
 716		  return DOM.Span(new Attributes { ClassName = className },
 717			new TextInput(new TextInput.Props
 718			{
 719			  ClassName = props.ClassName,
 720			  Disabled = props.Disabled,
 721			  Content = props.Content,
 722			  OnChange = props.OnChange
 723			}),
 724			string.IsNullOrWhiteSpace(props.ValidationMessage)
 725			  ? null
 726			  : DOM.Span(
 727			    new Attributes { ClassName = "validation-message" },
 728			    props.ValidationMessage
 729			  )
 730		  );
 731		}
 733		public class Props
 734		{
 735		  public string ClassName;
 736		  public bool Disabled;
 737		  public string Content;
 738		  public Action<string> OnChange;
 739		  public string ValidationMessage;
 740		}
 741	  }
 742	}
 744This allows the **MessageEditor** to be changed to use these **ValidatedTextInput**s instead of regular **TextInput**s, setting the "ValidationMessage" values according to whether the "Content" string has a value -
 746	using System;
 747	using Bridge.React;
 748	using BridgeReactTutorial.ViewModels;
 750	namespace BridgeReactTutorial.Components
 751	{
 752	  public class MessageEditor : StatelessComponent<MessageEditor.Props>
 753	  {
 754		public MessageEditor(Props props) : base(props) { }
 756		public override ReactElement Render()
 757		{
 758		  var formIsInvalid =
 759			string.IsNullOrWhiteSpace(props.Title) ||
 760			string.IsNullOrWhiteSpace(props.Content);
 762		  return DOM.FieldSet(new FieldSetAttributes { ClassName = props.ClassName },
 763			DOM.Legend(null, string.IsNullOrWhiteSpace(props.Title) ? "Untitled" : props.Title),
 764			DOM.Span(new Attributes { ClassName = "label" }, "Title"),
 765			new ValidatedTextInput(new ValidatedTextInput.Props
 766			{
 767			  ClassName = "title",
 768			  Disabled = props.Disabled,
 769			  Content = props.Title,
 770			  OnChange = newTitle => props.OnChange(new MessageDetails
 771			  {
 772				Title = newTitle,
 773				Content = props.Content
 774			  }),
 775			  ValidationMessage = string.IsNullOrWhiteSpace(props.Title)
 776				? "Must enter a title"
 777				: null
 778			}),
 779			DOM.Span(new Attributes { ClassName = "label" }, "Content"),
 780			new ValidatedTextInput(new ValidatedTextInput.Props
 781			{
 782			  ClassName = "content",
 783			  Disabled = props.Disabled,
 784			  Content = props.Content,
 785			  OnChange = newContent => props.OnChange(new MessageDetails
 786			  {
 787				Title = props.Title,
 788				Content = newContent
 789			  }),
 790			  ValidationMessage = string.IsNullOrWhiteSpace(props.Content)
 791				? "Must enter message content"
 792				: null
 793			}),
 794			DOM.Button(
 795			  new ButtonAttributes
 796			  {
 797			    Disabled = props.Disabled || formIsInvalid,
 798			    OnClick = e => props.OnSave()
 799			  },
 800			  "Save"
 801			)
 802		  );
 803		}
 805		public class Props
 806		{
 807		  public string ClassName;
 808		  public string Title;
 809		  public string Content;
 810		  public Action<MessageDetails> OnChange;
 811		  public Action OnSave;
 812		  public bool Disabled;
 813		}
 814	  }
 815	}
 817Now, the "Save" button is disabled if the **MessageEditor** is disabled (according to its props flag) *or* if the form entry is invalid. Now, it's not possible for the user to attempt a save that we will know will fail!
 819*(Moving validation logic out of the components is another thing that will come in the move towards dumb-as-possible components, but that's for part two).*
 821To keep things looking pretty, adding the following to "styles.css" -
 823	fieldset.message span.title, fieldset.message span.content { position: relative; }
 824	fieldset.message span.validation-message
 825	{
 826	  position: absolute;
 827	  top: -6px;
 828	  right: 2px;
 829	  padding: 2px 4px;
 830	  font-size: 70%;
 831	  background: #FFF9D8;
 832	  border: 1px solid #EFE9CB;
 833	  border-radius: 2px;
 834	  color: #A8A390;
 835	}
 836	fieldset.message button { margin-left: 8px; }
 838### Message History
 840What's the point in saving messages if we can't read them back out again? To enable this, the **IReadAndWriteMessages** needs a "GetMessages" method to accompany "SaveMessage" -
 842	using System;
 843	using System.Collections.Generic;
 844	using System.Threading.Tasks;
 845	using BridgeReactTutorial.ViewModels;
 847	namespace BridgeReactTutorial.API
 848	{
 849	  public interface IReadAndWriteMessages
 850	  {
 851		Task SaveMessage(MessageDetails message);
 852		Task<IEnumerable<Tuple<int, MessageDetails>>> GetMessages();
 853	  }
 854    }
 856This needs implementing in **MessageApi** -
 858	using System;
 859	using System.Collections.Generic;
 860	using System.Threading.Tasks;
 861	using Bridge.Html5;
 862	using BridgeReactTutorial.ViewModels;
 864	namespace BridgeReactTutorial.API
 865	{
 866	  public class MessageApi : IReadAndWriteMessages
 867	  {
 868		private readonly List<Tuple<int, MessageDetails>> _messages;
 869		public MessageApi()
 870		{
 871		  _messages = new List<Tuple<int, MessageDetails>>();
 872		}
 874		public Task SaveMessage(MessageDetails message)
 875		{
 876		  if (message == null)
 877			throw new ArgumentNullException("message");
 878		  if (string.IsNullOrWhiteSpace(message.Title))
 879			throw new ArgumentException("A title value must be provided");
 880		  if (string.IsNullOrWhiteSpace(message.Content))
 881			throw new ArgumentException("A content value must be provided");
 883		  var task = new Task<object>(null);
 884		  Window.SetTimeout(
 885			() =>
 886			{
 887			  _messages.Add(Tuple.Create(_messages.Count, message));
 888			  task.Complete();
 889			},
 890			1000 // Simulate a roundtrip to the server
 891		  );
 892		  return task;
 893		}
 895		public Task<IEnumerable<Tuple<int, MessageDetails>>> GetMessages()
 896		{
 897		  // ToArray is used to return a clone of the message set - otherwise, the caller would
 898		  // end up with a list that is updated when the internal reference within this class
 899		  // is updated (which sounds convenient but it's not the behaviour that would be
 900		  // exhibited if this was really persisting messages to a server somewhere)
 901		  var task = new Task<IEnumerable<Tuple<int, MessageDetails>>>(null);
 902		  Window.SetTimeout(
 903			() => task.Complete(_messages.ToArray()),
 904			1000 // Simulate a roundtrip to the server
 905		  );
 906		  return task;
 907		}
 908	  }
 909	}
 911Now, we'll need a way to render this information -
 913	using System;
 914	using System.Collections.Generic;
 915	using System.Linq;
 916	using Bridge.React;
 917	using BridgeReactTutorial.ViewModels;
 919	namespace BridgeReactTutorial.Components
 920	{
 921	  public class MessageHistory : StatelessComponent<MessageHistory.Props>
 922	  {
 923		public MessageHistory(Props props) : base(props) { }
 925		public override ReactElement Render()
 926		{
 927		  var className = props.ClassName;
 928		  if (!props.Messages.Any())
 929			className = (className + " zero-messages").Trim();
 931		  // Any time a set of child components is dynamically-created (meaning that the
 932		  // numbers of items may vary from one render to another), each must have a unique
 933		  // "Key" property set (this may be a int or a string). Here, this is simple as
 934		  // each message tuple is a unique id and the contents of that message (and the
 935		  // unique id is ideal for use as a unique "Key" property).
 936		  var messageElements = props.Messages
 937			.Select(idAndMessage => DOM.Div(new Attributes { Key = idAndMessage.Item1 },
 938			  DOM.Span(new Attributes { ClassName = "title" }, idAndMessage.Item2.Title),
 939			  DOM.Span(new Attributes { ClassName = "content" }, idAndMessage.Item2.Content)
 940			));
 942		  // When child components are specified (as they are through the second argument of
 943		  // DOM.Div), the argument is of type Any<ReactElement, string>[] (meaning that each
 944		  // element may be another component or it may be a simple text value)
 945		  // - The React  bindings have an extension method that transforms an IEnumerable
 946		  //   set of components (such as "messageElements" above) into an
 947		  //   Any<ReactElement, string>[]
 948		  return DOM.FieldSet(new FieldSetAttributes { ClassName = className },
 949			DOM.Legend(null, "Message History"),
 950			DOM.Div(null, messageElements.ToChildComponentArray())
 951		  );
 952		}
 954		public class Props
 955		{
 956		  public string ClassName;
 957		  public IEnumerable<Tuple<int, MessageDetails>> Messages;
 958		}
 959	  }
 960	}
 962This highlights an important React principle - where there are sets of dynamic child components, each must be provided a unique key. In the component above, we take "props.Messages" and map the data onto a set of Div elements. It's very possible that different messages will be rendered each time and so this is precisely what is meant by "dynamic child components".
 964There are two reasons why it's important to provide unique keys - the first is performance; the task of React's Virtual DOM is to take the last component tree and the new component tree and work out what changed, so that the minimum changes may be applied to the browser DOM. In order to do this, it is very helpful for React to be able to track components as they move around within a dynamic set - it can allow it to reuse data internally instead of having to throw away representations of components and recreate them:
 966> When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
 968The quote above is from [Facebook's docs about Dynamic Children]( - and so "clobbered" must be an official term!
 970The second reason why it's important is that component state can only be tracked with a component if the component itself can be tracked by React when dynamic elements move around. I'm not going to dwell too long on this because it's only applicable if you are relying on dynamic components having state, which you shouldn't be since only the top-level component should be stateful (and any component that may be created as a dynamic child component should be stateless).
 972For our purposes here, providing a unique key for each **MessageHistory** row is easy because the "GetMessages" method in the API returns a set of tuples, where each pair is a combination of id for the message and the message itself. This was easy to implement with the in-memory message store that we're using for this sample app, but it's also often easy when persisting by sending the data over the wire to a database somewhere; it's common for the database to generate unique ids for each record, and this would be returned in the data from "GetMessages".
 974Now we have to return to the **AppContainer** to tie everything together; we need to add the message history data to the **AppContainer**'s state, we need to read the message history after we save and we need to render the message history -
 976	using System;
 977	using System.Collections.Generic;
 978	using Bridge.React;
 979	using BridgeReactTutorial.API;
 980	using BridgeReactTutorial.ViewModels;
 982	namespace BridgeReactTutorial.Components
 983	{
 984	  public class AppContainer : Component<AppContainer.Props, AppContainer.State>
 985	  {
 986		public AppContainer(AppContainer.Props props) : base(props) { }
 988		protected override State GetInitialState()
 989		{
 990		  return new State
 991		  {
 992			Message = new MessageDetails { Title = "", Content = "" },
 993			IsSaveInProgress = false,
 994			MessageHistory = new Tuple<int, MessageDetails>[0]
 995		  };
 996		}
 998		public override ReactElement Render()
 999		{
1000		  return DOM.Div(null,
1001			new MessageEditor(new MessageEditor.Props
1002			{
1003			  ClassName = "message",
1004			  Title = state.Message.Title,
1005			  Content = state.Message.Content,
1006			  OnChange = newMessage => SetState(new State
1007			  {
1008				Message = newMessage,
1009				IsSaveInProgress = state.IsSaveInProgress,
1010				MessageHistory = state.MessageHistory
1011			  }),
1012			  OnSave = async () =>
1013			  {
1014				// Set SaveInProgress to true while the save operation is requested
1015				SetState(new State {
1016				  Message = state.Message,
1017				  IsSaveInProgress = true,
1018				  MessageHistory = state.MessageHistory
1019				});
1020				await props.MessageApi.SaveMessage(state.Message);
1022				// After the save has completed, clear the message entry form and reset
1023				// SaveInProgress to false
1024				SetState(new State {
1025				  Message = new MessageDetails { Title = "", Content = "" },
1026				  IsSaveInProgress = false,
1027				  MessageHistory = state.MessageHistory
1028				});
1030				// Then re-load the message history state and re-render when that data arrives
1031				var allMessages = await props.MessageApi.GetMessages();
1032				SetState(new State {
1033				  Message = state.Message,
1034				  IsSaveInProgress = state.IsSaveInProgress,
1035				  MessageHistory = allMessages
1036				});
1037			  },
1038			  Disabled = state.IsSaveInProgress
1039			}),
1040			new MessageHistory(new MessageHistory.Props
1041			{
1042			  ClassName = "history",
1043			  Messages = state.MessageHistory
1044			})
1045		  );
1046		}
1048		public class Props
1049		{
1050		  public IReadAndWriteMessages MessageApi;
1051		}
1053		public class State
1054		{
1055		  public MessageDetails Message;
1056		  public bool IsSaveInProgress;
1057		  public IEnumerable<Tuple<int, MessageDetails>> MessageHistory;
1058		}
1059	  }
1060	}
1062The logic in this component is getting more complicated now, which is down to the event-handling; what needs to happen when this happens and then this happens and then this happens (eg. the user clicks save, we initiate a save request, the API completes the request, we update the UI to clear the form and then start loading the updated message history, then we update the UI with the new message content).
1064One of the goals going forward will be to separate out this logic, away from the React components. The reason that I've made a couple of mentions of moving towards "dumb components" is that it makes unit testing much easier - everything outside of the React components will be simple C# / JavaScript code, which is always easier to test than UI elements, even when the UI is created using the React library. Another benefit of separating application logic from the UI components is that it makes both sides easier to reason about - and this is another theme that I'll be re-visiting during this mini-series.
1066It's worth mentioning that, even though it's gotten more complicated, the **AppContainer** (the only stateful component in the application) still adheres to the stateful component guidelines:
10681. A stateful component's "props" data should *only* consist of references to external dependencies
10691. A stateful component's "state" data should include *everything* required to render the component tree, though the props may be required to deal with child components' events
1071*All* of the data required to render the UI is present in the state. The props data is only required within "Render" in order to process some of the callbacks from the child components. Any changes that must then be reflected in the UI come through a call to SetState - at the point of the SetState-triggered re-render, all of the data required to generate the child components will, once again, be present entirely within the state data.
1073To keep things look nice, add the following to "styles.css" -
1075	fieldset.history
1076	{
1077	  opacity: 1;
1078	  transition: opacity .5s ease-in-out;
1079	}
1080 { opacity: 0; }
1081	fieldset.history span.title
1082	{
1083	  padding: 0 8px;
1084	  font-weight: bold;
1085	}
1087This will have the **MessageHistory** invisible to begin with, fading in when the first message is available to display.
1089### Coming in Part Two..
1091I think this makes a good point at which to draw the first part of this series to a close. To be honest, we haven't got very close at all yet to the "The Dan Way" of writing React applications - so far, it's been fairly straight-forward and in-line with the basic React guidelines from Facebook.
1093Which isn't to save that we haven't covered a lot of good ground! This will serve as a good base from which we can improve things. But we haven't seen the "Flux architecture" at all yet, and have only hinted at why we would want it. I'm not happy with how many of the properties on the various props, state and other data types are presented - one of my pet peeves with APIs is not knowing what can and can't be null; on the **TextInput**'s **Props** class, the "ClassName" string *may* be null but the "OnChange" callback must not be. These facts are not clear from just looking at the class. Similarly, it would be nice to know whether or not there are any guarantees about the "Title" and "Content" strings on the **MessageDetails** class (is it ever really acceptable for them to be null?). Finally, the reading and writing of messages through the **MessageApi** implementation we have here works fine for one person doing all the writing, but how could we deal with it if the **MessageApi** simulated a server-based API that received new messages from *other* uses, either through some sort of polling or through a push mechanism? This is an important question for systems that have to support multiple users.
1095All of these questions will be answered in later posts, along with further advice to try to help you do what I think React does best - write code that is easier to reason about, and thus easier to read, maintain and extend.