PageRenderTime 10ms CodeModel.GetById 4ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 0ms

/Blog/App_Data/Posts/96,2016,3,15,23,55,0,0,Bridge,React.txt

https://bitbucket.org/DanRoberts/blog
Plain Text | 1095 lines | 895 code | 200 blank | 0 comment | 0 complexity | 885c8b28bc069796ff39d43628e84049 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1## Writing React apps using Bridge.NET - The Dan Way (from first principles)
  2
  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! :)*
  4
  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.
  6
  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).
  8
  9### From the very start
 10
 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).
 12
 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:
 14
 15<img alt="The proposed example app" src="/Content/Images/Posts/ReactTutorial1.png" class="NoBorder AlwaysFullWidth" />
 16
 17As more messages are written, more entries will appear in the "Message History". Seems simple.
 18
 19### React components
 20
 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.
 22
 23Almost all of the time, components that you use will be "[controlled components](https://facebook.github.io/react/docs/forms.html#controlled-components)" -
 24
 25> A Controlled component does not maintain its own internal state; the component renders purely based on props.
 26
 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.
 28
 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".
 30
 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).
 32
 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 -
 34
 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).*
 36
 37    public class TextInput : Component<TextInput.Props, TextInput.State>
 38    {
 39      public TextInput(Props props) : base(props) { }
 40
 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      }
 55
 56      public class Props
 57      {
 58        public string InitialValue;
 59        public Action<string> OnChange;
 60      }
 61
 62      public class State
 63      {
 64        public string Value;
 65      }
 66    }
 67
 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).
 69
 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).
 71
 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.
 73
 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 -
 75
 76<img alt="Fieldset legend mirros the Title input value" src="/Content/Images/Posts/ReactTutorial2.png" class="NoBorder AlwaysFullWidth" />
 77
 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.
 79
 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) -
 81
 82    public class TextInput : StatelessComponent<TextInput.Props>
 83    {
 84      public TextInput(Props props) : base(props) { }
 85
 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      }
 95
 96      public class Props
 97      {
 98        public string Value;
 99        public Action<string> OnChange;
100      }
101    }
102
103The component code is much more succinct this way, as well as helping us avoid the nightmare scenario described above.
104
105It does leave one big question, though.. if these components don't update themselves, *then what does?*
106
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.
108
109In the sample application here -
110
111<img alt="The proposed example app" src="/Content/Images/Posts/ReactTutorial1.png" class="NoBorder AlwaysFullWidth" />
112
113The component hierarchy will look something like this:
114
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)
131
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.
133
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).
135
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.
137
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.
139
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.
141
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).
143
144*(The Facebook tutorial [Thinking in React](https://facebook.github.io/react/docs/thinking-in-react.html) 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)*.
145
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.
147
148### Let's start coding then!
149
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.
151
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:
153
154    {
155	  "output": "Bridge/output",
156	  "combineScripts":  true
157    }
158
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.
160
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:
162
163    <!DOCTYPE html>
164    <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
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="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.js"></script>
174      <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js"></script>
175      <script src="../output/BridgeReactTutorial.js"></script>
176    </body>
177    </html>
178
179*(If you called your project something other than "BridgeReactTutorial" then you might have to change the filename in that last script tag).*
180
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.
182
183When you created the class library project, a Class1.cs file will have been added to the project. Change its contents -
184
185    using System.Linq;
186    using Bridge.Html5;
187    using Bridge.React;
188
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    }
208
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!
210
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).
212
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) -
214
215	/* http://meyerweb.com/eric/tools/css/reset/ v2.0b1 | 201101 NOTE: WORK IN PROGRESS
216	 USE WITH CAUTION AND TEST WITH ABANDON */
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; }
242
243	div#main.loading { color: #f1f1f1; }
244
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.
246
247### Creating the Message Editor
248
249That's the theory and the project scaffolding out of the way. Now to create a form that actually does something.
250
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 -
252
253	using System;
254	using Bridge.Html5;
255	using Bridge.React;
256
257	namespace BridgeReactTutorial.Components
258	{
259	  public class TextInput : StatelessComponent<TextInput.Props>
260	  {
261		public TextInput(Props props) : base(props) { }
262
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		}
273
274		public class Props
275		{
276		  public string ClassName;
277		  public string Content;
278		  public Action<string> OnChange;
279		}
280	  }
281	}
282
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).*
284
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 -
286
287	namespace BridgeReactTutorial.ViewModels
288	{
289	  public class MessageDetails
290	  {
291        public string Title;
292        public string Content;
293	  }
294    }
295
296Now add another file to the "Components" folder; "MessageEditor.cs" and paste in this:
297
298	using System;
299	using Bridge.React;
300	using BridgeReactTutorial.ViewModels;
301
302	namespace BridgeReactTutorial.Components
303	{
304	  public class MessageEditor : StatelessComponent<MessageEditor.Props>
305	  {
306		public MessageEditor(Props props) : base(props) { }
307
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		}
336
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	}
346
347Now things are getting interesting!
348
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.
350
351Note that there's no validation yet. We'll get this rough version working first and then add that later.
352
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" -
354
355	using Bridge.Html5;
356	using Bridge.React;
357	using BridgeReactTutorial.ViewModels;
358
359	namespace BridgeReactTutorial.Components
360	{
361	  public class AppContainer : Component<object, AppContainer.State>
362	  {
363		public AppContainer() : base(null) { }
364
365		protected override State GetInitialState()
366		{
367		  return new State
368		  {
369			Message = new MessageDetails { Title = "", Content = "" }
370		  };
371		}
372
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		}
383
384		public class State
385		{
386		  public MessageDetails Message;
387		}
388	  }
389	}
390
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".
392
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.
394
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.*
396
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" -
398
399	using System.Linq;
400	using Bridge.Html5;
401	using Bridge.React;
402	using BridgeReactTutorial.Components;
403
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	}
420	
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" -
422
423	body
424	{
425	  font-family: 'Segoe UI';
426	  padding: 8px;
427	}
428
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; }
441
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.
443
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!
445
446### Message persistence
447
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.
449
450Create a new folder in the project root called "API" and add a new .cs file "IReadAndWriteMessages.cs", the contents of which should be:
451
452	using System.Threading.Tasks;
453	using BridgeReactTutorial.ViewModels;
454
455	namespace BridgeReactTutorial.API
456	{
457	  public interface IReadAndWriteMessages
458	  {
459		Task SaveMessage(MessageDetails message);
460	  }
461	}
462
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.
464
465Add another file into the "API" folder, "MessageApi.cs" -
466
467	using System;
468	using System.Threading.Tasks;
469	using Bridge.Html5;
470	using BridgeReactTutorial.ViewModels;
471
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");
484
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	}
494
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.
496
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.
498
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.*
500
501The **MessageEditor** should now looks like this:
502
503	using System;
504	using Bridge.React;
505	using BridgeReactTutorial.ViewModels;
506
507	namespace BridgeReactTutorial.Components
508	{
509	  public class MessageEditor : StatelessComponent<MessageEditor.Props>
510	  {
511		public MessageEditor(Props props) : base(props) { }
512
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		}
547
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	}
559
560The "Disabled" flag needs to be able to be applied to the **TextInput** components, so **TextInput** needs to look like this:
561
562	using System;
563	using Bridge.Html5;
564	using Bridge.React;
565
566	namespace BridgeReactTutorial.Components
567	{
568	  public class TextInput : StatelessComponent<TextInput.Props>
569	  {
570		public TextInput(Props props) : base(props) { }
571
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		}
583
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	}
593
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.
595
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.*
597
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 -
599
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
602
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.
604
605The **AppContainer** will now become the following -
606
607	using Bridge.React;
608	using BridgeReactTutorial.API;
609	using BridgeReactTutorial.ViewModels;
610
611	namespace BridgeReactTutorial.Components
612	{
613	  public class AppContainer : Component<AppContainer.Props, AppContainer.State>
614	  {
615		public AppContainer(AppContainer.Props props) : base(props) { }
616
617		protected override State GetInitialState()
618		{
619		  return new State
620		  {
621		    Message = new MessageDetails { Title = "", Content = "" },
622		    IsSaveInProgress = false
623		  };
624		}
625
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		}
651
652		public class Props
653		{
654		  public IReadAndWriteMessages MessageApi;
655		}
656
657		public class State
658		{
659		  public MessageDetails Message;
660		  public bool IsSaveInProgress;
661		}
662	  }
663	}
664	
665You will need to update App.cs to pass a props reference with a **MessageApi** instance to the **AppContainer** constructor -
666
667	using System.Linq;
668	using Bridge.Html5;
669	using Bridge.React;
670	using BridgeReactTutorial.API;
671	using BridgeReactTutorial.Components;
672
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	}
692
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.
694
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..
696
697### Validation
698
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 -
700
701	using System;
702	using Bridge.React;
703
704	namespace BridgeReactTutorial.Components
705	{
706	  public class ValidatedTextInput : StatelessComponent<ValidatedTextInput.Props>
707	  {
708		public ValidatedTextInput(Props props) : base(props) { }
709
710		public override ReactElement Render()
711		{
712		  var className = props.ClassName;
713		  if (!string.IsNullOrWhiteSpace(props.ValidationMessage))
714			className = (className + " invalid").Trim();
715
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		}
732
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	}
743
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 -
745
746	using System;
747	using Bridge.React;
748	using BridgeReactTutorial.ViewModels;
749
750	namespace BridgeReactTutorial.Components
751	{
752	  public class MessageEditor : StatelessComponent<MessageEditor.Props>
753	  {
754		public MessageEditor(Props props) : base(props) { }
755
756		public override ReactElement Render()
757		{
758		  var formIsInvalid =
759			string.IsNullOrWhiteSpace(props.Title) ||
760			string.IsNullOrWhiteSpace(props.Content);
761
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		}
804
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	}
816
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!
818
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).*
820
821To keep things looking pretty, adding the following to "styles.css" -
822
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; }
837
838### Message History
839
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" -
841
842	using System;
843	using System.Collections.Generic;
844	using System.Threading.Tasks;
845	using BridgeReactTutorial.ViewModels;
846
847	namespace BridgeReactTutorial.API
848	{
849	  public interface IReadAndWriteMessages
850	  {
851		Task SaveMessage(MessageDetails message);
852		Task<IEnumerable<Tuple<int, MessageDetails>>> GetMessages();
853	  }
854    }
855
856This needs implementing in **MessageApi** -
857
858	using System;
859	using System.Collections.Generic;
860	using System.Threading.Tasks;
861	using Bridge.Html5;
862	using BridgeReactTutorial.ViewModels;
863
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		}
873
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");
882
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		}
894
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	}
910
911Now, we'll need a way to render this information -
912
913	using System;
914	using System.Collections.Generic;
915	using System.Linq;
916	using Bridge.React;
917	using BridgeReactTutorial.ViewModels;
918
919	namespace BridgeReactTutorial.Components
920	{
921	  public class MessageHistory : StatelessComponent<MessageHistory.Props>
922	  {
923		public MessageHistory(Props props) : base(props) { }
924
925		public override ReactElement Render()
926		{
927		  var className = props.ClassName;
928		  if (!props.Messages.Any())
929			className = (className + " zero-messages").Trim();
930
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			));
941
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		}
953
954		public class Props
955		{
956		  public string ClassName;
957		  public IEnumerable<Tuple<int, MessageDetails>> Messages;
958		}
959	  }
960	}
961
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".
963
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:
965
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).
967
968The quote above is from [Facebook's docs about Dynamic Children](https://facebook.github.io/react/docs/multiple-components.html#dynamic-children) - and so "clobbered" must be an official term!
969
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).
971
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 …

Large files files are truncated, but you can click here to view the full file