30 March 2016
In parts One and Two, I described how to create a simple application using React and a Flux-like architecture, written in Bridge.NET - it covered where and how to deal with validation, how to integrate with a persistence layer, how to deal with asynchronous interactions (and how they don't need to be scary) and how the approach made the code easy to test and easy to reason about.
The components created using the React / Bridge bindings have their requirements / dependencies described in a strongly-typed manner since each component's "props" reference is a nested class with properties for each value or reference that will be needed in order for it to render.
This combination of technologies has the potential to be really powerful for writing client-side / browser-based applications, particularly with the ability to leverage C#'s proven strength in allowing the writing of large and reliable systems. However, I'm not happy with the example application yet. Although the way that it's written makes a lot of it easy to understand and, hopefully, makes the intent of the code clear, it still could be even easier to understand and the intent and the requirements even clearer.
People often like to talk as if a language is dynamically-typed (or "non-typed" or "uni-typed", depending upon the language, their vocabulary and their knowledge and opinion of the language) or statically-typed; as if it is a binary choice. Really, it is a sliding scale. C# definitely sits on the "statically-typed side", but the way that you write C# dictates how far along the scale that your C# is.
(C# can also be written to act as a dynamically-typed language, particularly if you use the "dynamic" keyword - but it's principally a statically-typed language).
I'm going to describe some ways to improve the example application and, in doing so, extrapolate some rules as to how to make the code clearer (and, again, easier to reason about, write and maintain - since these are extremely important qualities of code to me, that I strive for when developing and that appealed to me when I first encountered React). These will be my opinions (based upon my experiences) and you might disagree with them - but this is where we really get into "The Dan Way" of working with Bridge and React. If you do choose to disagree, then hopefully parts One and Two will continue to be useful (but I'm afraid we can never be friends*).
* (Only joking**)
** (I'm not)
Let's jump straight in with a simple example. We have a TextInput component that renders a text input element and passes up any requests by the user that the input's content be changed. The main primary purpose of this class is to provide a simple interface. Many of the html attributes that may be applied to a text input are not relevant (this exposes only the basics, such as "ClassName"). Similarly, the "OnChange" that a text input raises has a relatively complicated event reference (it allows you to identify the element that changed and then get the requested "new value" from it, but I want a TextInput's "OnChange" event to simply provide the new string and nothing else).
using System;
using Bridge.Html5;
using Bridge.React;
namespace BridgeReactTutorial.Components
{
public class TextInput : StatelessComponent<TextInput.Props>
{
public TextInput(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
Type = InputType.Text,
ClassName = props.ClassName,
Disabled = props.Disabled,
Value = props.Content,
OnChange = e => props.OnChange(e.CurrentTarget.Value)
});
}
public class Props
{
public string ClassName;
public bool Disabled;
public string Content;
public Action<string> OnChange;
}
}
}
In the context of this small class, looking at the code, I would say that it's fairly clear what each line of code does and what each of the properties of the Props class is required for and how it will be used. However, even within such a small class, there are several implicit assumptions that are being made - eg.
First off, I don't like the should-or-shouldn't-be-allowed-to-be-null confusion around the "ClassName" and "Content" values. Secondly, I don't like the fact that, as it stands, you need to read (or already be familiar with) the code inside the TextInput component. One way to try to address these issues would be to consider using summary comments on the Props class - eg.
public class Props
{
/// <summary>
/// This is optional and so may be null (if non-null, then it should be a valid class
/// name - ie. non-blank)
/// </summary>
public string ClassName;
public bool Disabled;
/// <summary>
/// An input may not always have a value and so this may be blank (but it should never
/// be null)
/// </summary>
public string Content;
/// <summary>
/// This is mandatory and may never be null
/// </summary>
public Action<string> OnChange;
}
The problem with this approach is that, if the comments are ignored, runtime problems will occur at some point and it may not be very easy to trace them back to where they originated. If the "OnChange" value is null, for example, then the problem won't be noticed until the user interacts with the input box - and the code that raises the exception (the "props.OnChange" call with TextInput's "Render" method) will be completely removed from the code that incorrectly set the null value (the code that instantiated and populated Props instance).
So another alternative would be to combine these comments with some validation - eg.
public class Props
{
private string _className, _content;
private Action<string> _onChange;
/// <summary>
/// This is optional and so may be null (if non-null, then it should be a valid class
/// name - ie. non-blank)
/// </summary>
public string ClassName
{
get { return _className; }
set
{
if (value == "")
throw new ArgumentException("ClassName should not be set to a blank string");
_className = value;
}
}
public bool Disabled { get; set; }
/// <summary>
/// An input may not always have a value and so this may be blank (but it should never
/// be null)
/// </summary>
public string Content
{
get { return _content; }
set
{
if (value == null)
throw new ArgumentNullException("Content should not be set to null");
_content = value;
}
}
/// <summary>
/// This is mandatory and may never be null
/// </summary>
public Action<string> OnChange
{
get { return _onChange; }
set
{
if (value == null)
throw new ArgumentNullException("OnChange should not be set to null");
_onChange = value;
}
}
}
This way, it would not be possible to explicitly set "OnChange" to null - if this was attempted then an exception would be thrown immediately, right at the point in the code that was attempting to assign this invalid value. This is much better than it failing later on, at some point that depends upon how the user does or doesn't interact with the UI component. This is potentially the sort of mistake that goes unnoticed for some time. For cases that are clearly a "programmer error" bug like this, I much prefer to "fail fast".
There's still a problem, though, because the initial state of the Props class is invalid, since "OnChange" will start as null. If the initialisation code explicitly tries to set it to null then it will fail fast, but if it doesn't set it at all then it remain null and we'll be back to where we started in terms of where and when the exception is raised compared to where the programming mistake was made.
Attempt three could be to set appropriate defaults - eg.
public class Props
{
private string _className = null;
private string _content = "";
private Action<string> _onChange = newValue => { };
/// <summary>
/// This is optional and so may be null (if non-null, then it should be a valid class
/// name - ie. non-blank)
/// </summary>
public string ClassName
{
get { return _className; }
set
{
if (value == "")
throw new ArgumentException("ClassName should not be set to a blank string");
_className = value;
}
}
public bool Disabled { get; set; }
/// <summary>
/// An input may not always have a value and so this may be blank (but it should never
/// be null)
/// </summary>
public string Content
{
get { return _content; }
set
{
if (value == null)
throw new ArgumentNullException("Content should not be set to null");
_content = value;
}
}
/// <summary>
/// This is mandatory and may never be null
/// </summary>
public Action<string> OnChange
{
get { return _onChange; }
set
{
if (value == null)
throw new ArgumentNullException("OnChange should not be set to null");
_onChange = value;
}
}
}
Now it's not possible for "OnChange" to be null, so a null reference exception can not be thrown when the user tries to interact with the component.
This still isn't fantastic, though. Is it really likely that there's ever a time where no "OnChange" value should have been set? Changes are that, in that case, there is still a programmer error (a TextInput is useless without an "OnChange" callback and so one should be set).. but now the error is being silently swallowed.
So, maybe most properties should have to be specified in order to get a new Props instance. Since values have to be provided at the point of initialisation, they may as well be validated at that point. This is a very good argument for making the Props class immutable - eg.
public class Props
{
public Props(string className, string content, bool disabled, Action<string> onChange)
{
if (className == "")
throw new ArgumentException("className should not be set to a blank string");
if (content == null)
throw new ArgumentNullException("content");
if (onChange == null)
throw new ArgumentNullException("onChange");
ClassName = className;
Content = content;
Disabled = disabled;
OnChange = onChange;
}
/// <summary>
/// This is optional and so may be null (if non-null, then it will never be blank)
/// </summary>
public string ClassName { get; }
public bool Disabled { get; }
/// <summary>
/// An input may not always have a value and so this may be blank (but it should never
/// be null)
/// </summary>
public string Content { get; }
/// <summary>
/// This is mandatory and will never be null
/// </summary>
public Action<string> OnChange { get; }
}
Two nice benefits arise from this. Firstly, the comments may be tightened up - so "OnChange" is no longer described as
This is mandatory and should never be null
now it is
This is mandatory and will never be null
It's a seemingly small change, but I'm looking for confidence in the code and this is a positive step from "hopefully this won't happen" to "this can not happen (because an ArgumentNullException would have been instantly thrown if an attempt was made to create a Props instance in this manner)".
The second benefit is that the Props class now communicates one of the React guidelines - the React documentation states that props data should be considered immutable; once a component has a props reference, it should not try to change its data, nor should it expect any other code to be able to change it. Now, that commandment is baked into the code - this is a great example of what I mean when I talk about using a "richer type system", there's more information that may be encoded than just "this class has a property that is of type string".
One final tweak to this sort of approach is to enable optional values to truly be optional. In this example, I'm talking about "className". The constructor may be changed from:
public Props(string className, string content, bool disabled, Action<string> onChange)
{
if (className == "")
throw new ArgumentException("className should not be set to a blank string");
if (content == null)
throw new ArgumentNullException("content");
if (onChange == null)
throw new ArgumentNullException("onChange");
ClassName = className;
Content = content;
Disabled = disabled;
OnChange = onChange;
}
to:
public Props(string content, bool disabled, Action<string> onChange, string className = "")
{
if (content == null)
throw new ArgumentNullException("content");
if (onChange == null)
throw new ArgumentNullException("onChange");
if (className == "")
throw new ArgumentException("className should not be set to a blank string");
Content = content;
Disabled = disabled;
OnChange = onChange;
ClassName = className;
}
This means that an instance of TextInput.Props may be created, if no class name is required, like this:
new TextInput.Props(title, isSaveInProgress, onTitleChange)
Or, if a class name is required:
new TextInput.Props(title, isSaveInProgress, onTitleChange, "title")
Personally, I like to use named constructor arguments when creating Props instances, so I would probably write:
new TextInput.Props(
className: "title",
value: title,
disabled: isSaveInProgress,
onChange: onTitleChange
)
I think that this makes the code easier to read (I don't need to resort to looking at the Props constructor to see that "title" is a class name and not an ID or any other use for a string) and means that it doesn't matter that the "className" argument moved from the start of the constructor argument list to the end, since the position of arguments doesn't matter when their names are used. (As an added benefit, this makes the code slightly more similar to the React component initialisation code that you might see in non-Bridge/C# projects, where JSON objects are used to set properties - but, here, we have the added benefit that's it all typed).
This is a big step forward in terms of including additional information in the type system (and in terms of catching errors quickly - and as close to the root cause of the error as possible), meaning that I can more reliably use a component without having to know everything about how it works (which, in a lot of ways, is like the idea of coding against an interface - you want to know about how to communicate with an interface to get the desired result without having to know all of the details of its implementation).
I'm not completely happy with the code at this point, though. It feels like the Props class has ballooned considerably from:
public class Props
{
public string ClassName;
public bool Disabled;
public string Content;
public Action<string> OnChange;
}
to:
public class Props
{
public Props(
string content,
bool disabled,
Action<string> onChange,
string className = "")
{
if (content == null)
throw new ArgumentNullException("content");
if (onChange == null)
throw new ArgumentNullException("onChange");
if (className == "")
throw new ArgumentException("className should not be set to a blank string");
Content = content;
Disabled = disabled;
OnChange = onChange;
ClassName = className;
}
/// <summary>
/// This is optional and so may be null (if non-null, then it will never be blank)
/// </summary>
public string ClassName { get; }
public bool Disabled { get; }
/// <summary>
/// An input may not always have a value and so this may be blank (but it should never
/// be null)
/// </summary>
public string Content { get; }
/// <summary>
/// This is mandatory and will never be null
/// </summary>
public Action<string> OnChange { get; }
}
While I am willing to make a certain trade-off between the cost of writing the code to begin with against the long term benefits of it being easier to quickly understand and then reason about*, I don't want to have to write any more of this monotonous form of code than absolutely necessary - in particular, I think I would get bored of writing "This is mandatory and will never be null" over and over again on different properties on different classes**.
* (Since "Code is read much more often than it is written, so plan accordingly", I strongly believe that a little extra writing effort is worth it to reduce the more-often-incurred reading effort).
** (I have personally written a lot of code that uses immutable, always-valid types and that was littered with these sorts of comments - while I definitely think it was worth it, I definitely HAVE gotten bored with writing "This will never be null" time after time after time).
But what alternative is there?
Since this whole series is about "The Dan Way", I think that it is entirely reasonable to introduce a library that I've written at this point. It's a NuGet package for Bridge that makes it easier to write immutable types, such as the Props class above: "ProductiveRage.Immutable".
If a class implements an empty interface IAmImmutable then it may access extension methods that make the constructor easier to write. Something along the lines of:
public class Props : IAmImmutable
{
public Props(string content, bool disabled, Action<string> onChange, string className = "")
{
this.CtorSet(_ => _.Content, content);
this.CtorSet(_ => _.Disabled, disabled);
this.CtorSet(_ => _.OnChange, onChange);
this.CtorSet(_ => _.ClassName, className);
}
public string ClassName { get; }
public bool Disabled { get; }
public string Content { get; }
public Action<string> OnChange { get; }
}
The extension method is "CtorSet" and it takes a lambda that specifies a property on the class and it takes a new value for that property. The type of the property and the type of the new value must be consistent - so, although there's apparently a little magic involved, we're not sacrificing any type safety.
One interesting feature of "CtorSet" is that it will never allow a null value. This means the comments from the Props class along the lines of "This will never be null" are unnecessary because an IAmImmutable-implementing class that sets all of its properties in its constructor will never have any null property values.
This actually doesn't work for the Props class we're looking at here since we want "ClassName" to be allowed to be null. To enable that, the library comes with a new struct - Optional<T>. Any time that you want to have a constructor argument be null, you have to wrap its type in this struct - ie.
public class Props : IAmImmutable
{
public Props(
string content,
bool disabled,
Action<string> onChange,
Optional<string> className = new Optional<string>())
{
this.CtorSet(_ => _.Content, content);
this.CtorSet(_ => _.Disabled, disabled);
this.CtorSet(_ => _.OnChange, onChange);
this.CtorSet(_ => _.ClassName, className);
}
public Optional<string> ClassName { get; }
public bool Disabled { get; }
public string Content { get; }
public Action<string> OnChange { get; }
}
Once again, we've made a step forward to encoding additional information in the class itself. In terms of being able to more easily reason about the code, this is a great win - classes such as these will never have null values to worry about; any property that may or may not have a value will be of type Optional<T>, which has properties "IsDefined" (a boolean indicating whether or not it has a value) and "Value" (which is the value itself, so long as "IsDefined" is true).
If you were in an argumentative mood, then you might say that Optional<T> can't save you from nulls in all cases since any code that deals with them could choose not to check "IsDefined" and to just try to access "className.Value" in all cases. This is true, but this faulty style of calling code had to be explicitly written to "work around" the Optional<T> wrapper. If the person who wrote it had sufficiently little interest to try to understand why Optional<T> was used then they may need some help in getting back on to the right path in their programming. This wrapper type acts as a sign post at each point where a null may be encountered, if the sign posts are ignored then that's unfortunate (but the benefit remains that there is a sign post for every potentially-null value whereas, in normal C# code, you need to be wary that nulls may leap out at you at any point, without warning).
This change doesn't affect how you call the constructor if you used the named arguments approach from above, so the following works fine:
new TextInput.Props(
className: "title",
value: title,
disabled: isSaveInProgress,
onChange: onTitleChange
)
as does:
new TextInput.Props(
value: title,
disabled: isSaveInProgress,
onChange: onTitleChange
)
If you wanted to really deliberately indicate that a TextInput should have no title then you could use any of the following three -
// The explicit way
new TextInput.Props(
className: Optional<string>.Missing,
value: title,
disabled: isSaveInProgress,
onChange: onTitleChange
)
// The implicit way (there is an implicit cast from T to Optional<T>, which is why passing
// the string "title" in the earlier example works, because the "title" string is implicitly
// cast to an Optional<string> with value "title" - similarly null is implicitly cast to
// an Optional<string> with value null, which is the same as Optional<string>.Missing)
new TextInput.Props(
className: null,
value: title,
disabled: isSaveInProgress,
onChange: onTitleChange
)
// Using the struct's constructor - the most verbose and least-prefered way. Note that this
// is the only way that a "Missing" value may be specified as a constructor argument's
// default value, as may be seen in the Props constructor above (this is because default
// argument values must be compile time constants, which a new instance of a struct is but
// the static "Missing" property is not. Null can't be used as a constructor argument's
// default value for an Optional as the implicit cast is a runtime operation and so is
// not available at compile time).
new TextInput.Props(
className: new Optional<string>(),
value: title,
disabled: isSaveInProgress,
onChange: onTitleChange
)
I'm still not happy with this, though. I talked earlier about the ambiguity between null and blank string - for the "Value" property, this is solved since it can never be null now. It's valid for it to be a blank string (if there is no content in the input box) but it's not valid for it to be null (an ArgumentNullException will be raised in the constructor for a null "value"). Problem solved. However, the "ClassName" property can be "Optional<string>.Missing" (which is similar to null) or it can be a value that is a blank string. It would be much better to say that "ClassName" is "Missing" (meaning that it has no value and that the DOM element should have a "class" attribute at all) or that it has a value that is not blank.
One way to try to address this would be with another type -
using System;
namespace BridgeReactTutorial.API
{
public class NonBlankTrimmedString
{
public NonBlankTrimmedString(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Null, blank or whitespace-only value specified");
Value = value.Trim();
}
/// <summary>
/// This will never be null, blank or have any leading or trailing whitespace
/// </summary>
public string Value { get; }
/// <summary>
/// It's convenient to be able to pass a NonBlankTrimmedString instance as any argument
/// that requires a string
/// </summary>
public static implicit operator string(NonBlankTrimmedString value)
{
if (value == null)
throw new ArgumentNullException("value");
return value.Value;
}
}
}
I said that I wasn't keen on writing out more of this type of "This will never be null.." summary comment if I could avoid it, but the idea with this class it that it will be applicable in multiple places. So I have had to type "This will never be null, blank or have any leading or trailing whitespace" once again but I will take advantage of this one comment over and over again throughout my code.
Now the TextInput.Props class becomes:
public class Props : IAmImmutable
{
public Props(
string content,
bool disabled,
Action<string> onChange,
Optional<NonBlankTrimmedString> className = new Optional<NonBlankTrimmedString>())
{
this.CtorSet(_ => _.Content, content);
this.CtorSet(_ => _.Disabled, disabled);
this.CtorSet(_ => _.OnChange, onChange);
this.CtorSet(_ => _.ClassName, className);
}
public Optional<NonBlankTrimmedString> ClassName { get; }
public bool Disabled { get; }
public string Content { get; }
public Action<string> OnChange { get; }
}
If you wanted to instantiate one then you would need to change the calling code slightly - eg.
new TextInput.Props(
className: new NonBlankTrimmedString("title"),
value: title,
disabled: isSaveInProgress,
onChange: onTitleChange
)
This does make this code a little more verbose. But we have the benefit that the Props class contains more information about what is and isn't acceptable for its property values. Also, making the calling code more explicit like this forces the writer to consider whether an appropriate value will always be passed to it - they should be careful to pass null or "Optional<NonBlankTrimmedString>.Missing" if they don't want to set a class name and to provide a populated, non-blank string if they do want a class name.
At this point, I'm finally satisfied with the TextInput.Props class!
Note: This is probably the most controversial part of my recommended approach - if you're happy to consider making your classes immutable like this, for the reasons I outlined above (which, by the way, can be applied to all classes in your code, not just props types for React components) and you're willing to consider the benefits and trade-offs of building up a more detailed type system (such as using Optional<NonBlankOrTrimmedString> instead of just "string" and only using "string" to mean "non-nullable string") then I think that you'll enjoy the rest of what I've got to say.
I want to extend this ostracising of nulls to the TextInput class itself, though. At the end of Part Two, the component looked like this:
using System;
using Bridge.Html5;
using Bridge.React;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class TextInput : StatelessComponent<TextInput.Props>
{
public TextInput(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
Type = InputType.Text,
ClassName = props.ClassName,
Disabled = props.Disabled,
Value = props.Content,
OnChange = e => props.OnChange(e.CurrentTarget.Value)
});
}
public class Props : IAmImmutable
{
public string ClassName;
public bool Disabled;
public string Content;
public Action<string> OnChange;
}
}
}
My problem with this is that it's possible to call the constructor with a null "props" value - but, if you do so, then the "Render" method will throw a null reference exception. Unfortunately, it's not possible to check for a null "props" value in the component's constructor due to the way that the Bridge / React bindings work with the React library; the constructor is never actually executed and so a null-check in there would never run. What I suggest is that the Props constructor arguments be repeated in the component's constructor -
using System;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.API;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class TextInput : StatelessComponent<TextInput.Props>
{
public TextInput(
string content,
bool disabled,
Action<string> onChange,
Optional<NonBlankTrimmedString> className = new Optional<NonBlankTrimmedString>())
: base(new Props(content, disabled, onChange, className)) { }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
Type = InputType.Text,
ClassName = props.ClassName.IsDefined ? props.ClassName.Value : null,
Disabled = props.Disabled,
Value = props.Content,
OnChange = e => props.OnChange(e.CurrentTarget.Value)
});
}
public class Props : IAmImmutable
{
public Props(
string content,
bool disabled,
Action<string> onChange,
Optional<NonBlankTrimmedString> className)
{
this.CtorSet(_ => _.Content, content);
this.CtorSet(_ => _.Disabled, disabled);
this.CtorSet(_ => _.OnChange, onChange);
this.CtorSet(_ => _.ClassName, className);
}
public Optional<NonBlankTrimmedString> ClassName { get; }
public bool Disabled { get; }
public string Content { get; }
public Action<string> OnChange { get; }
}
}
}
Again, this has expanded the amount of code that is required within the component class. But the real effect is magnified by the line-wrapping that I use on my blog post code samples - in Visual Studio, I would likely have the constructor arguments all on one line.
If we can get past the cost of additional code within the component then we get two benefits. The first is that it's no longer possible for the TextInput to ever have a null "props" reference as a Props reference is no longer passed in, but is created in the call to the base constructor by using the individual arguments passed to the TextInput constructor. The second benefit is more marginal, but still nice (especially since it partially offsets the additional code added above) - the way that a new TextInput was declared previously required duplication of the word "TextInput" (with "new TextInput" and "new TextInput.Props") -
new TextInput(new TextInput.Props
{
ClassName = "title"
Disabled = props.Disabled,
Content = props.Content,
OnChange = props.OnChange
})
With the updated TextInput implementation, this duplication is avoided -
new TextInput(
className: new NonBlankTrimmedString("title"),
disabled: props.Disabled,
content: props.Content,
onChange: props.OnChange
)
Even without this (admittedly minor) second benefit, I would still be much happier with the new version of TextInput. The additional code (the final version is definitely somewhat longer than the previous version) pays for itself in what it communicates to someone who wishes to consume that component. However, one of the themes that I've been pushing in this series is that components should be dumb and that the real logic of the application should be outside of any UI classes; in the application code that will deal with the complications of the business logic and how to deal with user interactions.. if there is a way to move some of the syntactic noise around component creation away from the complicated library code and into the dumb components, then that seems a sensible trade-off. And that's what has been done here!
There's actually a third benefit to using this "IAmImmutable style" for writing these data types, when it comes to passing events from the simple components all the way up to the top of the component tree, where each "OnChange" (or whatever) adds increasing detail on the way up - but I'll come to that later on, first I want to address a burning question:
Trick question! I am convinced that it does make sense to use IAmImmutable for data types throughout the application and to make null arguments and properties unacceptable in all places.
One obvious example is in the "SaveMessage" method in the MessageApi class. Currently, it starts like this:
private RequestId SaveMessage(MessageDetails message, Action optionalSaveCompletedCallback)
{
if (message == null)
throw new ArgumentNullException("message");
if (string.IsNullOrWhiteSpace(message.Title))
throw new ArgumentException("A title value must be provided");
if (string.IsNullOrWhiteSpace(message.Content))
throw new ArgumentException("A content value must be provided");
Not accepting a null MessageDetails instance is good - if someone tried to call this method with a null "message" argument then it should be rejected immediately. However, it feels wrong that it's necessary to then check the "Title" and "Content" properties - should there ever be a case where a MessageDetails instance exists without these values being populated? In this application, the answer is no - the MessageEditor component only allows a new message to be saved if both its Title and Content have values.
This is another opportunity to encode this additional information into the type system. Instead of MessageDetails being implemented like this:
namespace BridgeReactTutorial.ViewModels
{
public class MessageDetails
{
public string Title;
public string Content;
}
}
It should be rewritten thusly:
using BridgeReactTutorial.API;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.ViewModels
{
public class MessageDetails : IAmImmutable
{
public MessageDetails(NonBlankTrimmedString title, NonBlankTrimmedString content)
{
this.CtorSet(_ => _.Title, title);
this.CtorSet(_ => _.Content, content);
}
public NonBlankTrimmedString Title { get; }
public NonBlankTrimmedString Content { get; }
}
}
Now, the "SaveMessage" validation is much simpler - all that is required is:
private RequestId SaveMessage(MessageDetails message, Action optionalSaveCompletedCallback)
{
if (message == null)
throw new ArgumentNullException("message");
Since a MessageDetails instance may no longer exist with missing Title or Content values, the property validation in "SaveMessage" is unnecessary. This has the benefit that there is less code at the point at which data is retrieved from a MessageDetails instance, which goes some way to offsetting the additional code required in defining the type. The real benefit, though, to removing that code is not just reducing line count but in removing potential duplication (the property validation code may have appeared elsewhere in the application if there were other methods that processed MessageDetails instances) and baking assumptions into the type system, rather than leaving them be implicit. Before, it was probably safe to assume that Title and Content would always have values since the code that would create a MessageDetails instance would always give them values - however, that was only an assumption and you would have had to have read all of the code that created MessageDetails instances to be confident. With this arrangement, you know that a MessageDetails has both Title and Content values at all times, since it's not possible for an instance to be created that doesn't!
When I talk about code being easy to reason about, it's not usually in terms of the dozen or so lines of code that are directly in front of you, it's how the objects and methods that you're dealing with may interact with the rest of the system and what assumptions are being made. Knowing that a MessageDetails instance will always be valid is extremely helpful. Knowing that any code that attempts to create a MessageDetails with invalid data will fail immediately, rather than cause an error later on (when the instance is presumed to be valid but turns out not to be) is extremely helpful. Knowing that a type is immutable and that it won't be changed "behind the scenes" when you pass it off to another method is extremely helpful - when types are mutable and you pass an instance to another method to read, you can't be sure whether the method will only read it or whether it will manipulate it; there's no way to tell from the method signature. Making mutations explicit by making types immutable is another big win for being able to reason about code.
Speaking of dealing with mutation brings me smoothly onto the third benefit of IAmImmutable that I hinted at earlier. Currently, the MessageEditor component in our example app looks like this:
using System;
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class MessageEditor : StatelessComponent<MessageEditor.Props>
{
public MessageEditor(Props props) : base(props) { }
public override ReactElement Render()
{
var formIsInvalid =
!string.IsNullOrWhiteSpace(props.Message.Title.ValidationError) ||
!string.IsNullOrWhiteSpace(props.Message.Content.ValidationError);
return DOM.FieldSet(new FieldSetAttributes { ClassName = props.ClassName },
DOM.Legend(null, props.Message.Caption),
DOM.Span(new Attributes { ClassName = "label" }, "Title"),
new ValidatedTextInput(new ValidatedTextInput.Props
{
ClassName = "title",
Disabled = props.Message.IsSaveInProgress,
Content = props.Message.Title.Text,
OnChange = newTitle => props.OnChange(new MessageEditState
{
Title = new TextEditState { Text = newTitle },
Content = props.Message.Content
}),
ValidationMessage = props.Message.Title.ValidationError
}),
DOM.Span(new Attributes { ClassName = "label" }, "Content"),
new ValidatedTextInput(new ValidatedTextInput.Props
{
ClassName = "content",
Disabled = props.Message.IsSaveInProgress,
Content = props.Message.Content.Text,
OnChange = newContent => props.OnChange(new MessageEditState
{
Title = props.Message.Title,
Content = new TextEditState { Text = newContent },
}),
ValidationMessage = props.Message.Content.ValidationError
}),
DOM.Button(
new ButtonAttributes
{
Disabled = formIsInvalid || props.Message.IsSaveInProgress,
OnClick = e => props.OnSave()
},
"Save"
)
);
}
public class Props
{
public string ClassName;
public MessageEditState Message;
public Action<MessageEditState> OnChange;
public Action OnSave;
}
}
}
The first thing I'm going to do is change the Props type and the component's constructor in the same way as I did for the TextInput -
using System;
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class MessageEditor : StatelessComponent<MessageEditor.Props>
{
public MessageEditor(
MessageEditState message,
Action<MessageEditState> onChange,
Action onSave,
Optional<NonBlankTrimmedString> className = new Optional<NonBlankTrimmedString>())
: base(new Props(className, message, onChange, onSave)) { }
public override ReactElement Render()
{
var formIsInvalid =
!string.IsNullOrWhiteSpace(props.Message.Title.ValidationError) ||
!string.IsNullOrWhiteSpace(props.Message.Content.ValidationError);
return DOM.FieldSet(
new FieldSetAttributes {
ClassName = props.ClassName.IsDefined ? props.ClassName.Value : null
},
DOM.Legend(null, props.Message.Caption),
DOM.Span(new Attributes { ClassName = "label" }, "Title"),
new ValidatedTextInput(new ValidatedTextInput.Props
{
ClassName = "title",
Disabled = props.Message.IsSaveInProgress,
Content = props.Message.Title.Text,
OnChange = newTitle => props.OnChange(new MessageEditState
{
Title = new TextEditState { Text = newTitle },
Content = props.Message.Content
}),
ValidationMessage = props.Message.Title.ValidationError
}),
DOM.Span(new Attributes { ClassName = "label" }, "Content"),
new ValidatedTextInput(new ValidatedTextInput.Props
{
ClassName = "content",
Disabled = props.Message.IsSaveInProgress,
Content = props.Message.Content.Text,
OnChange = newContent => props.OnChange(new MessageEditState
{
Title = props.Message.Title,
Content = new TextEditState { Text = newContent },
}),
ValidationMessage = props.Message.Content.ValidationError
}),
DOM.Button(
new ButtonAttributes
{
Disabled = formIsInvalid || props.Message.IsSaveInProgress,
OnClick = e => props.OnSave()
},
"Save"
)
);
}
public class Props : IAmImmutable
{
public Props(
Optional<NonBlankTrimmedString> className,
MessageEditState message,
Action<MessageEditState> onChange,
Action onSave)
{
this.CtorSet(_ => _.ClassName, className);
this.CtorSet(_ => _.Message, message);
this.CtorSet(_ => _.OnChange, onChange);
this.CtorSet(_ => _.OnSave, onSave);
}
public Optional<NonBlankTrimmedString> ClassName { get; }
public MessageEditState Message { get; }
public Action<MessageEditState> OnChange { get; }
public Action OnSave { get; }
}
}
}
This means that the MessageEditor instantiation code changes slightly from:
new MessageEditor(new MessageEditor.Props
{
ClassName = "message",
Message = state.Message,
OnChange = newState => props.Dispatcher.Dispatch(
UserEditRequested.For(newState)
),
OnSave = () => props.Dispatcher.Dispatch(
SaveRequested.For(
new MessageDetails(
new NonBlankTrimmedString(state.Message.Title.Text),
new NonBlankTrimmedString(state.Message.Content.Text)
)
)
)
}),
to:
new MessageEditor(
className: new NonBlankTrimmedString("message"),
message: state.Message,
onChange: newState => props.Dispatcher.Dispatch(
UserEditRequested.For(newState)
),
onSave: () => props.Dispatcher.Dispatch(
SaveRequested.For(
new MessageDetails(
new NonBlankTrimmedString(state.Message.Title.Text),
new NonBlankTrimmedString(state.Message.Content.Text)
)
)
)
),
There are several steps that need following now until I can reveal my point, so bear with me. I'm going to change the MessageEditState data type, in the same way as I did the MessageDetails - from:
namespace BridgeReactTutorial.ViewModels
{
public class MessageEditState
{
public string Caption;
public TextEditState Title;
public TextEditState Content;
public bool IsSaveInProgress;
}
}
to:
using BridgeReactTutorial.API;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.ViewModels
{
public class MessageEditState : IAmImmutable
{
public MessageEditState(
NonBlankTrimmedString caption,
TextEditState title,
TextEditState content,
bool isSaveInProgress)
{
this.CtorSet(_ => _.Caption, caption);
this.CtorSet(_ => _.Title, title);
this.CtorSet(_ => _.Content, content);
this.CtorSet(_ => _.IsSaveInProgress, isSaveInProgress);
}
public NonBlankTrimmedString Caption { get; }
public TextEditState Title { get; }
public TextEditState Content { get; }
public bool IsSaveInProgress { get; }
}
}
And do the same with TextEditState, from -
namespace BridgeReactTutorial.ViewModels
{
public class TextEditState
{
public string Text;
public string ValidationError;
}
}
to:
using BridgeReactTutorial.API;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.ViewModels
{
public class TextEditState : IAmImmutable
{
public TextEditState(
string text,
Optional<NonBlankTrimmedString> validationError = new Optional<NonBlankTrimmedString>())
{
this.CtorSet(_ => _.Text, text);
this.CtorSet(_ => _.ValidationError, validationError);
}
public string Text { get; }
public Optional<NonBlankTrimmedString> ValidationError { get; }
}
}
I'm going to change the ValidatedTextInput to
using System;
using Bridge.React;
using BridgeReactTutorial.API;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class ValidatedTextInput : StatelessComponent<ValidatedTextInput.Props>
{
public ValidatedTextInput(
bool disabled,
string content,
Action<string> onChange,
Optional<NonBlankTrimmedString> validationMessage,
Optional<NonBlankTrimmedString> className = new Optional<NonBlankTrimmedString>())
: base(new Props(className, disabled, content, onChange, validationMessage)) { }
public override ReactElement Render()
{
var className = props.ClassName;
if (props.ValidationMessage.IsDefined)
className = className.Add(" ", new NonBlankTrimmedString("invalid"));
return DOM.Span(new Attributes { ClassName = className.ToStringIfDefined() },
new TextInput(
className: props.ClassName,
disabled: props.Disabled,
content: props.Content,
onChange: props.OnChange
),
props.ValidationMessage.IsDefined
? DOM.Span(
new Attributes { ClassName = "validation-message" },
props.ValidationMessage.ToStringIfDefined()
)
: null
);
}
public class Props : IAmImmutable
{
public Props(
Optional<NonBlankTrimmedString> className,
bool disabled,
string content,
Action<string> onChange,
Optional<NonBlankTrimmedString> validationMessage)
{
this.CtorSet(_ => _.ClassName, className);
this.CtorSet(_ => _.Disabled, disabled);
this.CtorSet(_ => _.Content, content);
this.CtorSet(_ => _.OnChange, onChange);
this.CtorSet(_ => _.ValidationMessage, validationMessage);
}
public Optional<NonBlankTrimmedString> ClassName { get; }
public bool Disabled { get; }
public string Content { get; }
public Action<string> OnChange { get; }
public Optional<NonBlankTrimmedString> ValidationMessage { get; }
}
}
}
.. which requires a new class be added to the "API" folder with some extension methods to make dealing with Optional<NonBlankTrimmedString> a little nicer -
using System;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.API
{
public static class OptionalNonBlankTrimmedStringExtensions
{
/// <summary>
/// If the Optional NonBlankTrimmedString has a value then it will be unwrapped directly
/// into a string - if not, the null will be returned (this is one of the few places
/// where null will be an acceptable value in the app and it should be only used when
/// integrating with code that expects nulls - such as when setting attributes via
/// React html element factories)
/// </summary>
public static string ToStringIfDefined(this Optional<NonBlankTrimmedString> source)
{
return source.IsDefined ? source.Value : null;
}
/// <summary>
/// This will join two Optional NonBlankTrimmedString with a specified delimiter if
/// they both have values. If only one of them has a value then this will be returned
/// unaltered. If neither of them have a value then a Missing value will be returned.
/// </summary>
public static Optional<NonBlankTrimmedString> Add(
this Optional<NonBlankTrimmedString> source,
string delimiter,
Optional<NonBlankTrimmedString> other)
{
if (delimiter == null)
throw new ArgumentNullException("delimiter");
if (!source.IsDefined && !other.IsDefined)
return Optional<NonBlankTrimmedString>.Missing;
else if (!source.IsDefined)
return other;
else if (!other.IsDefined)
return source;
return new NonBlankTrimmedString(source.Value.Value + delimiter + other.Value.Value);
}
}
}
and a further implicit operator adding to the NonBlankTrimmedString -
/// <summary>
/// It's convenient to be able to pass a NonBlankTrimmedString instance as any argument
/// that requires a ReactElement-or-string, such as for the children array of the React
/// DOM component factories
/// </summary>
public static implicit operator Union<ReactElement, string>(NonBlankTrimmedString value)
{
if (value == null)
throw new ArgumentNullException("value");
return value.Value;
}
Ok, now I'm finally able to demonstrate this mysterious third benefit. The "OnChange" lambdas which were provided as ValidatedTextInput.Props values by the MessageEditor's "Render" method were previously specified like this:
OnChange = newTitle => props.OnChange(new MessageEditState
{
Title = new TextEditState { Text = newTitle },
Content = props.Message.Content
})
OnChange = newContent => props.OnChange(new MessageEditState
{
Title = props.Message.Title,
Content = new TextEditState { Text = newContent },
})
Within each "OnChange", we want to create a new MessageEditState instance with one of the properties changed. However, it get arduous having to repeat all of the property names each time that you want to change a single property - here it's not that bad because there are only two properties ("Title" and "Content"), but on classes with more properties this is annoying and, worse, error-prone.
Now that MessageEditState implements IAmImmutable, we can take advantage of another extension method available; "With". This takes an argument that specifies the property to change and it takes an argument for the new property value. This means that
OnChange = newTitle => props.OnChange(new MessageEditState
{
Title = new TextEditState { Text = newTitle },
Content = props.Message.Content
})
is replaced with
OnChange = newTitle => props.OnChange(
props.Message.With(_ => _.Title, new TextEditState(newTitle))
)
and
OnChange = newContent => props.OnChange(new MessageEditState
{
Title = props.Message.Title,
Content = new TextEditState { Text = newContent }
})
is replaced with
OnChange = newContent => props.OnChange(
props.Message.With(_ => _.Content, new TextEditState(newContent))
)
(Again, I'm only wrapping these lines for the sake of the formatting on my blog - if I was writing this code in Visual Studio then I would make those a single line each).
The "With" function takes an instance of an IAmImmutable-implementing class, clones it but changes the specified property value - unless the new value is the same as the old value, in which case it returns the original instance unaltered.
All of these changes combined mean that the MessageEditor component now becomes:
using System;
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class MessageEditor : StatelessComponent<MessageEditor.Props>
{
public MessageEditor(
MessageEditState message,
Action<MessageEditState> onChange,
Action onSave,
Optional<NonBlankTrimmedString> className = new Optional<NonBlankTrimmedString>())
: base(new Props(className, message, onChange, onSave)) { }
public override ReactElement Render()
{
var formIsInvalid =
props.Message.Title.ValidationError.IsDefined ||
props.Message.Content.ValidationError.IsDefined;
return DOM.FieldSet(
new FieldSetAttributes { ClassName = props.ClassName.ToStringIfDefined() },
DOM.Legend(null, props.Message.Caption),
DOM.Span(new Attributes { ClassName = "label" }, "Title"),
new ValidatedTextInput(
className: new NonBlankTrimmedString("title"),
disabled: props.Message.IsSaveInProgress,
content: props.Message.Title.Text,
onChange: newTitle => props.OnChange(
props.Message.With(_ => _.Title, new TextEditState(newTitle))
),
validationMessage: props.Message.Title.ValidationError
),
DOM.Span(new Attributes { ClassName = "label" }, "Content"),
new ValidatedTextInput(
className: new NonBlankTrimmedString("content"),
disabled: props.Message.IsSaveInProgress,
content: props.Message.Content.Text,
onChange: newContent => props.OnChange(
props.Message.With(_ => _.Content, new TextEditState(newContent))
),
validationMessage: props.Message.Content.ValidationError
),
DOM.Button(
new ButtonAttributes
{
Disabled = formIsInvalid || props.Message.IsSaveInProgress,
OnClick = e => props.OnSave()
},
"Save"
)
);
}
public class Props : IAmImmutable
{
public Props(
Optional<NonBlankTrimmedString> className,
MessageEditState message,
Action<MessageEditState> onChange,
Action onSave)
{
this.CtorSet(_ => _.ClassName, className);
this.CtorSet(_ => _.Message, message);
this.CtorSet(_ => _.OnChange, onChange);
this.CtorSet(_ => _.OnSave, onSave);
}
public Optional<NonBlankTrimmedString> ClassName { get; }
public MessageEditState Message { get; }
public Action<MessageEditState> OnChange { get; }
public Action OnSave { get; }
}
}
}
Before moving on, I want to apply these changes to one more component to really drive the point home.
This is the MessageHistory component as it currently stands:
using System;
using System.Collections.Generic;
using System.Linq;
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class MessageHistory : StatelessComponent<MessageHistory.Props>
{
public MessageHistory(Props props) : base(props) { }
public override ReactElement Render()
{
var className = props.ClassName;
if (!props.Messages.Any())
className = (className + " zero-messages").Trim();
// Any time a set of child components is dynamically-created (meaning that the
// numbers of items may vary from one render to another), each must have a unique
// "Key" property set (this may be a int or a string). Here, this is simple as
// each message tuple is a unique ID and the contents of that message.
var messageElements = props.Messages
.Select(idAndMessage => DOM.Div(new Attributes { Key = idAndMessage.Item1 },
DOM.Span(new Attributes { ClassName = "title" }, idAndMessage.Item2.Title),
DOM.Span(new Attributes { ClassName = "content" }, idAndMessage.Item2.Content)
));
// When child components are specified (as they are through the second argument of
// DOM.Div), the argument is of type Union<ReactElement, string>[] (meaning that each
// element may be another component or it may be a simple text value)
// - The React bindings have an extension method that transforms an IEnumerable set
// of components (such as "messageElements") into an Union<ReactElement, string>[]
return DOM.FieldSet(new FieldSetAttributes { ClassName = className },
DOM.Legend(null, "Message History"),
DOM.Div(null, messageElements)
);
}
public class Props
{
public string ClassName;
public IEnumerable<Tuple<int, MessageDetails>> Messages;
}
}
}
Despite this appearing very simple at first glance, there are various implicit assumptions that you should be aware of. Firstly, it is assumed that "props" will never be null ("Render" will throw an exception if this is not the case). It is also assumed that "props.ClassName" may be null (and, technically, it may also be a blank string, though this is not desirable) while "props.Messages" should not null. Nor should "props.Messages" contain any tuples with a null MessageDetails instance. But these assumptions are neither documented nor enforced.
By this point, we've seen several examples of how to prevent "props" being null (ie. require that the props constructor arguments be passed as the component's constructor arguments) and we've seen how to better represent Props to allow "ClassName" to be optional but for "Messages" to not be ("ClassName" should be an Optional<NonBlankTrimmedString>). But there are two further tricks we can use for the MessageHistory.Props.
Firstly, IEnumerable is too loose for my liking - technically, there are no guarantees that an IEnumerable will report the same information if enumerated multiple times and there are definitely no guarantees that it won't contain any null references. I want consistency and I want a life free from null. The ProductiveRage.Immutable library contains another handy class for this sort of thing; NonNullList<T>. This is essentially an ordered list of items of type "T" that is immutable and that will never contain any null values. If you want a set of items that may or may not have values of type "T" then you need the list type to be NonNullList<Optional<T>>.
The second tweak that I want to make is to replace the Tuple<int, MessageDetails> - in part, again, because there is no guarantee that the second item in the pair will not be null but also because I don't like the "Item1" and "Item2" property names. I think it's just one more thing to mentally translate ("Oh yes, Item1 means Key and Item2 means Message"). So I'm going to extend the type system again.
When considering a simple API, the common actions are "Create", "Read", "Update", "Delete". When creating a new item (like when we save a new message in our example application), we don't have a unique key for the new message - that will be generated by the persistence layer as part of the save process. When we read values (to display existing messages in the MessageHistory, for example), we will have access to unique keys - the persistence layer will be reading data from wherever the data is stored and it will be able to draw the keys out along with the data. When updating an existing record, we should know what its key is, since we will have performed a read action in order to get the currently-persisted state for the record. Similarly, when requesting a delete, we will have the key from a previous read action, in order to know what record to remove.
I've seen object models before which try to have a single data type to use in all of the Create, Read and Update cases. This would be like our MessageDetails having a "Key", "Title" and "Content". However, sometimes the "Key" would be null because it would be unknown (when generating a brand new MessageDetails instance to pass to "SaveMessage", for example). I don't like this. The sometimes-Key-is-null-and-sometimes-it-isn't is an unnecessary complication and it means that there are places where we require a Key but can't guarantee (through the type system) that the reference that we have will have a non-null Key value. I think it's much better to have two data types; one for a record that has been persisted at some point (and thus has a non-null Key) and another type for a record that may or may not have been persisted. Currently, our MessageDetails class (which has only "Title" and "Content" properties) represents a message that may or may not have been persisted - when a new one is passed to "SaveMessage" when the user attempts to save a new message then we know that it hasn't been persisted yet, but it's not difficult to imagine that there could be other code that we add to the application in the future that wants to deal with some message data, but that doesn't care whether it's been persisted or not yet; it only wants access to its "Title" and / or "Content" values, it doesn't need the "Key" for anything.
So, instead of the MessageHistory using the generic Tuple class to represent a MessageDetails-plus-persisted-Key, I'm going to introduce something new. Create a new file under the "API" folder, "Saved.cs" -
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.API
{
public class Saved<TKey, TValue> : IAmImmutable
{
public Saved(TKey key, TValue value)
{
this.CtorSet(_ => _.Key, key);
this.CtorSet(_ => _.Value, value);
}
public TKey Key { get; }
public TValue Value { get; }
}
public static class Saved
{
/// <summary>
/// This generic method makes code that creates generic Saved instances more succinct
/// by relying upon type inference (based upon the key and value argument types), so
/// that the calling code does not have to explicitly declare TKey and TValue
/// </summary>
public static Saved<TKey, TValue> For<TKey, TValue>(TKey key, TValue value)
{
return new Saved<TKey, TValue>(key, value);
}
}
}
The Saved class makes differentiating between record-that-has-a-persistence-id and record-that-may-or-may-not-have-a-persistence-id simple. If the message has been persisted, then it may be represented as a Saved<int, MessageDetails>. If it's just the message data, with no persisted-or-not-persisted state associated with it then it will be simply a MessageDetails.
I'm still not happy, though. I think that Saved<int, MessageDetails> could still be more descriptive. This value represents a message with a unique persistence key for that message. Even if the underlying data store is a database which uses an integer column (in our example app, it's a simple in-browser-memory store, but a database on a server is likely much more common) that doesn't mean that we have to use such a vague term as "an integer" in our application's object model. I recommend strongly-typed ID representations. We need to add a new file "MessageId.cs" to the "API" folder:
namespace BridgeReactTutorial.API
{
public struct MessageId
{
public int Value { get; private set; }
public static explicit operator MessageId(int value)
{
return new MessageId { Value = value };
}
public static implicit operator int(MessageId id)
{
return id.Value;
}
}
}
This is the final step in the move away from IEnumerable<Tuple<int, MessageDetails>>, we will now represent this data with the type NonNullList<Saved<MessageId, MessageDetails>>. This list will never contain any null "Saved" instances and a "Saved" instance will never contain a null message. This list of messages will never vary, which is another way that React's "consider props to be immutable" guidelines is described and enforced in the type system.
Not only do I believe that having strongly-typed IDs makes the code clearer in cases like this but it can also avoid silly mistakes that have a nasty tendency to crop up from time to time - if you're writing code and having a bad day, then it's easy to accidentally pass the wrong ID around. For example, if I have a function:
void RecordMessageAsHavingBeenReadBy(int messageId, int userId)
then it's possible in the calling code to mix up the IDs if you're having a bad day (this isn't too contrived an example, I have done something like this in the past!) - eg.
RecordMessageAsHavingBeenReadBy(user.Id, message.id); // Whoops!
If the IDs were strongly-typed, meaning that the method signature would be..
void RecordMessageAsHavingBeenReadBy(MessageId messageId, UserId userId)
.. then that mishap would result in a compile error, rather than runtime confusion that may not get noticed immediately.
(Note: These changes to how messages are represented will require changes to the MessageApi, which I'll cover shortly - nothing very complicated, though).
These changes lead the MessageHistory component's code to now look like this:
using System.Linq;
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class MessageHistory : StatelessComponent<MessageHistory.Props>
{
public MessageHistory(
NonNullList<Saved<MessageId, MessageDetails>> messages,
Optional<NonBlankTrimmedString> className = new Optional<NonBlankTrimmedString>())
: base(new Props(className, messages)) { }
public override ReactElement Render()
{
var className = props.ClassName;
if (!props.Messages.Any())
className = className.Add(" ", new NonBlankTrimmedString("zero-messages"));
// Any time a set of child components is dynamically-created (meaning that the
// numbers of items may vary from one render to another), each must have a unique
// "Key" property set (this may be a int or a string)
var messageElements = props.Messages
.Select(savedMessage => DOM.Div(new Attributes { Key = (int)savedMessage.Key },
DOM.Span(new Attributes { ClassName = "title" }, savedMessage.Value.Title),
DOM.Span(new Attributes { ClassName = "content" }, savedMessage.Value.Content)
));
// When child components are specified (as they are through the second argument of
// DOM.Div), the argument is of type Union<ReactElement, string>[] (meaning that each
// element may be another component or it may be a simple text value)
// - The React bindings have an extension method that transforms an IEnumerable set
// of components (such as "messageElements") into an Union<ReactElement, string>[]
return DOM.FieldSet(
new FieldSetAttributes { ClassName = className.ToStringIfDefined() },
DOM.Legend(null, "Message History"),
DOM.Div(null, messageElements)
);
}
public class Props : IAmImmutable
{
public Props(
Optional<NonBlankTrimmedString> className,
NonNullList<Saved<MessageId, MessageDetails>> messages)
{
this.CtorSet(_ => _.ClassName, className);
this.CtorSet(_ => _.Messages, messages);
}
public Optional<NonBlankTrimmedString> ClassName { get; }
public NonNullList<Saved<MessageId, MessageDetails>> Messages { get; }
}
}
}
All of those implicit assumptions are now explicitly described in the type system. This makes me feel much better.
I introduced the use of IAmImmutable in terms of a component's Props class. But I subsequently used it to tighten up the MessageDetails class and then again when the Saved class was added.
One option in incorporating IAmImmutable and this no-value-or-property-may-be-null behaviour into applications would be to say that any class that implements IAmImmutable will not allow null anywhere. As I've already hinted, I strongly suggest going further than that, however, and writing all code like this. Frankly, I can see no good reason why any public data type should be mutable. I can imagine that, in some special cases, it may be desirable to have some private mutable data structures for convenience, or maybe performance (in some very specialised cases) but where the data is shared with other classes, data types being immutable makes the code much easier to reason about. Transformations are explicit and do not occur "in place" for any references. Meaning that a reference that describes some data when a function starts will always describe the same data when the function ends.
It's actually worth remembering that JavaScript in the browser is single-threaded. A lot of the time that people talk about the benefits of immutability, they talk about the safety of being able to share references between multiple threads and not having to worry about corruption because one thread can't manipulate data in a way that another thread doesn't expect, with unfortunate (and often non-deterministic) results. Here, we are not concerned about multi-threading, I recommend the use of immutable structures solely because they make the code that accesses them and passes them around easier to reason about.
The largest downside in my eyes, as may have struck you after reading all of the above, is that changing code that doesn't use IAmImmutable into code that does use it requires changes not only to that particular class but, in many cases, to code that accesses or initialises that class and then to code that accesses or initialises that code (the changes to the MessageEditor and MessageHistory components required changes to the MessageDetails and MessageEditState classes and still require more changes to the AppContainer, the MessageWriterStore and the MessageApi). It's much better to bake this in from the start. The big benefit is that, if you do so, you'll rarely have to worry about "could this value be null"* and "could this data be changed if I pass it into another function".
* (There will still be some places where you have to be alert about potential nulls, but these should largely arise from interacting with other libraries - the "ToStringIfDefined" extension method we saw earlier is an example of a place where nulls may be returned, but it is clearly documented as such and the return value is only intended to be passed to a React element factory method).
If you've been following along and creating your own project with the code in this series, you will be all too aware that it doesn't build at the moment. Let's go through and fix everything up. Much of the required alterations will be similar to what is presented above, but there are a few other tips and tricks to consider along the way.
Let's start with the AppContainer. Last we saw it, it looked like this:
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.ViewModels;
using BridgeReactTutorial.Stores;
using BridgeReactTutorial.API;
namespace BridgeReactTutorial.Components
{
public class AppContainer : Component<AppContainer.Props, AppContainer.State>
{
public AppContainer(AppContainer.Props props) : base(props) { }
protected override void ComponentDidMount()
{
props.Store.Change += StoreChanged;
}
protected override void ComponentWillUnmount()
{
props.Store.Change -= StoreChanged;
}
private void StoreChanged()
{
SetState(new State
{
Message = props.Store.Message,
MessageHistory = props.Store.MessageHistory
});
}
public override ReactElement Render()
{
if (state == null)
return null;
return DOM.Div(null,
new MessageEditor(
className: new NonBlankTrimmedString("message"),
message: state.Message,
onChange: newState => props.Dispatcher.Dispatch(
UserEditRequested.For(newState)
),
onSave: () => props.Dispatcher.Dispatch(
SaveRequested.For(
new MessageDetails(
new NonBlankTrimmedString(state.Message.Title.Text),
new NonBlankTrimmedString(state.Message.Content.Text)
)
)
)
),
new MessageHistory(new MessageHistory.Props
{
ClassName = "history",
Messages = state.MessageHistory
})
);
}
public class Props
{
public AppDispatcher Dispatcher;
public MessageWriterStore Store;
}
public class State
{
public MessageEditState Message;
public IEnumerable<Tuple<int, MessageDetails>> MessageHistory;
}
}
}
It's nice and simple since we moved nearly all of the logic that it contained in Part One into the MessageWriterStore in Part Two.
The obvious thing to do, based on what I've been talking about today, is to change its Props and State classes to implement IAmImmutable.
After that, there is one difference between this component and the other components we've already looked at - this is stateful and they were state-less. That means that they only had "props" to think about, while the AppContainer has both "props" and "state". As with the stateless components, it is presumed that the "props" reference may never be null. This can be enforced by using the same trick as we did for the others - mirror the Props constructor arguments in the AppContainer's constructor arguments and generate a Props instance from them. However, the "state" reference may be null some times, as can be seen at the very start of the "Render" method. This means that the "state" type should be Optional<State>, rather than just State.
These changes result in this:
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.API;
using BridgeReactTutorial.Stores;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class AppContainer : Component<AppContainer.Props, Optional<AppContainer.State>>
{
public AppContainer(AppDispatcher dispatcher, MessageWriterStore store)
: base(new Props(dispatcher, store)) { }
protected override void ComponentDidMount()
{
props.Store.Change += StoreChanged;
}
protected override void ComponentWillUnmount()
{
props.Store.Change -= StoreChanged;
}
private void StoreChanged()
{
SetState(new State(
message: props.Store.Message,
messageHistory: props.Store.MessageHistory
));
}
public override ReactElement Render()
{
if (!state.IsDefined)
return null;
return DOM.Div(null,
new MessageEditor(
className: new NonBlankTrimmedString("message"),
message: state.Value.Message,
onChange: newState => props.Dispatcher.Dispatch(
UserEditRequested.For(newState)
),
onSave: () => props.Dispatcher.Dispatch(
SaveRequested.For(
new MessageDetails(
new NonBlankTrimmedString(state.Value.Message.Title.Text),
new NonBlankTrimmedString(state.Value.Message.Content.Text)
)
)
)
),
new MessageHistory(
className: new NonBlankTrimmedString("history"),
messages: state.Value.MessageHistory
)
);
}
public class Props : IAmImmutable
{
public Props(AppDispatcher dispatcher, MessageWriterStore store)
{
this.CtorSet(_ => _.Dispatcher, dispatcher);
this.CtorSet(_ => _.Store, store);
}
public AppDispatcher Dispatcher { get; }
public MessageWriterStore Store { get; }
}
public class State : IAmImmutable
{
public State(
MessageEditState message,
NonNullList<Saved<MessageId, MessageDetails>> messageHistory)
{
this.CtorSet(_ => _.Message, message);
this.CtorSet(_ => _.MessageHistory, messageHistory);
}
public MessageEditState Message { get; }
public NonNullList<Saved<MessageId, MessageDetails>> MessageHistory { get; }
}
}
}
That transformation should have felt quite run-of-the-mill and predictable by this point - seen one component-tightening-up, seen them all. Let's move on to the MessageWriterStore. This is what deals with the events from the application, both from user-initiated events from DOM elements and from new-messages-data-available events from the MessageApi.
At the bare minimum, it will need some changes since it was written when the MessageEditState type was mutable and so the "ValidateMessage" method was able to mutate it (such as setting or clearing validation warning messages) in-place. I've moved away from that so that mutations are always explicit - there will no longer be a method that may or may not mutate a reference, if a method needs to set values on something then it will take the initial reference as an input and return a new one as its return value. But there are more of those sneaky "implicit assumptions" tucked away in the store that we should address. In the last post, we left it implemented like this:
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Stores
{
public class MessageWriterStore
{
private RequestId _saveActionRequestId, _lastDataUpdatedRequestId;
public MessageWriterStore(IReadAndWriteMessages messageApi, AppDispatcher dispatcher)
{
if (messageApi == null)
throw new ArgumentNullException("messageApi");
if (dispatcher == null)
throw new ArgumentNullException("dispatcher");
Message = GetInitialMessageEditState();
MessageHistory = new Tuple<int, MessageDetails>[0];
dispatcher.Receive(a => a
.If<StoreInitialised>(
condition: action => (action.Store == this),
work: action => { }
)
.Else<MessageEditStateChanged>(action =>
{
Message = action.NewState;
ValidateMessage(Message);
})
.Else<MessageSaveRequested>(action =>
{
_saveActionRequestId = messageApi.SaveMessage(action.Message);
Message.IsSaveInProgress = true;
})
.Else<MessageSaveSucceeded>(
condition: action => (action.RequestId == _saveActionRequestId),
work: action =>
{
_saveActionRequestId = null;
Message = GetInitialMessageEditState();
_lastDataUpdatedRequestId = messageApi.GetMessages();
}
)
.Else<MessageHistoryUpdated>(
condition: action =>
action.RequestId.IsEqualToOrComesAfter(_lastDataUpdatedRequestId),
work: action =>
{
_lastDataUpdatedRequestId = action.RequestId;
MessageHistory = action.Messages;
}
)
.IfAnyMatched(OnChange);
);
}
public event Action Change;
public MessageEditState Message;
public IEnumerable<Tuple<int, MessageDetails>> MessageHistory;
private MessageEditState GetInitialMessageEditState()
{
// Note: By using the ValidateMessage here, we don't need to duplicate the "Untitled"
// string that should be used for the Caption value when the UI is first rendered
// or when the user has entered some Title content but then deleted it again.
// Similarly, we avoid having to repeat the validation messages that should be
// displayed when the form is empty, since they will be set by ValidateMessage.
var blankMessage = new MessageEditState
{
Caption = "",
Title = new TextEditState { Text = "" },
Content = new TextEditState { Text = "" },
IsSaveInProgress = false
};
ValidateMessage(blankMessage);
return blankMessage;
}
private void ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
message.Caption = string.IsNullOrWhiteSpace(message.Title.Text)
? "Untitled"
: message.Title.Text.Trim();
message.Title.ValidationError = string.IsNullOrWhiteSpace(message.Title.Text)
? "Must enter a title"
: null;
message.Content.ValidationError = string.IsNullOrWhiteSpace(message.Content.Text)
? "Must enter message content"
: null;
}
private void OnChange()
{
if (Change != null)
Change();
}
}
}
Three things jump out at me here. Firstly, the "_saveActionRequestId" and "_lastDataUpdatedRequestId" references may not always be populated. If there is no save action in progress that we're waiting to complete, for example, then "_saveActionRequestId" won't have a value. Let's explicitly describe this in the type system by changing the type of these two values from RequestId to Optional<RequestId> (even though these values aren't part of a public API of the store class, there's still a benefit to indicating what may and may not have a value, for the sake of code clarity).
The second thing is that the "Message" and "MessageHistory" properties are only intended to be written to internally. They are available for reading by other classes (like the AppContainer component), but not for updating by other classes. It makes sense to change these from being public fields to being properties with public getters and private setters. This wasn't done originally because I wanted to start from the simplest possible implementations and only stray from that when there was a clear benefit. Today, we're dealing with the clear benefit of increased code clarity through the reduction of implicit assumptions. Moving to private-setter properties allows the compiler to enforce what was only presumed to be true before (instead of working on the assumption that no-one would try to update these references, now we can sleep safe that no-one other than the MessageWriteStore itself can change the references).
The third thing is that "Change" is an event and so may be null if no-one has subscribed to it. That's just the way that events work in C#. We could either come up with a new way to represent events or we could accept that a null check is required (and that we can't use an Optional type to represent it). I think that the pragmatic thing to do is to just accept it - this is basically how events have worked in C# from day one and I don't think that there would be any improvement to code clarity by trying to shy away from this accepted practice.
What is really going to be the most interesting part in updating the MessageWriterStore is, I think, how we change the validation / MessageEditState-mutating code -
private void ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
message.Caption = string.IsNullOrWhiteSpace(message.Title.Text)
? "Untitled"
: message.Title.Text.Trim();
message.Title.ValidationError = string.IsNullOrWhiteSpace(message.Title.Text)
? "Must enter a title"
: null;
message.Content.ValidationError = string.IsNullOrWhiteSpace(message.Content.Text)
? "Must enter message content"
: null;
}
Probably the absolute simplest thing that we could do would be to rewrite it like this:
private MessageEditState ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
if (string.IsNullOrWhiteSpace(message.Title.Text))
message = message.With(_ => _.Caption, new NonBlankTrimmedString("Untitled"));
else
message = message.With(_ => _.Caption, new NonBlankTrimmedString(message.Title.Text));
if (string.IsNullOrWhiteSpace(message.Title.Text))
message = message.With(_ => _.Title, SetValidationMessage(message.Title, new NonBlankTrimmedString("Must enter a title")));
else
message = message.With(_ => _.Title, SetValidationMessage(message.Title, null));
if (string.IsNullOrWhiteSpace(message.Content.Text))
message = message.With(_ => _.Content, SetValidationMessage(message.Content, new NonBlankTrimmedString("Must enter message content")));
else
message = message.With(_ => _.Content, SetValidationMessage(message.Content, null));
return message;
}
private TextEditState SetValidationMessage(
TextEditState textEditState,
Optional<NonBlankTrimmedString> message)
{
if (textEditState == null)
throw new ArgumentNullException("textEditState");
return textEditState.With(_ => _.ValidationError, message);
}
That is.. more verbose, I think would be a polite way to describe it. I would normally have wrapped some of the lines in order to fit the horizontal scrolling budget I allow on code samples on my blog but I wanted to give this arrangement the best change at looking succint that it could. And it's still not looking very good.
What's much worse, though, is that I don't think that this code is very easy to read. I think that there's quite a lot of noise that masks the actual intent. It's not complicated, by a long shot, but I think that the actual logic that it's trying to apply is drowning a little bit in all the code that's required. The verbosity itself, is not the biggest problem for me - I will take code that is slightly longer if it's clearer (I'm not just talking about descriptive variable and method names and I'm don't mean avoiding compact "clever" code, I mean like the changes from mutable classes to IAmImmutable implementations; they are more verbose but they are much more expressive).
One alternative would be:
private MessageEditState ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
var caption = string.IsNullOrWhiteSpace(message.Title.Text)
? new NonBlankTrimmedString("Untitled")
: new NonBlankTrimmedString(message.Title.Text);
var titleEditState = string.IsNullOrWhiteSpace(message.Title.Text)
? SetValidationMessage(message.Title, new NonBlankTrimmedString("Must enter a title"))
: null;
var contentEditState = string.IsNullOrWhiteSpace(message.Content.Text)
? SetValidationMessage(message.Content, new NonBlankTrimmedString("Must enter message content"))
: null;
return message
.With(_ => _.Caption, caption)
.With(_ => _.Title, titleEditState)
.With(_ => _.Content, contentEditState);
}
This is much improved. The code looks cleaner at a glance and, crucially, it's much clearer in its intent.
However.. the way that we've reduced the syntactic noise is by separating the "what should the new values be" from the "set these new values". This isn't too bad with only three properties, but if the object being validated was more complex then the new-value-determining code would drift further from the new-value-setting code, which would be a pity since they are intrinsically linked concepts (and it would be nice - meaning that the code should be easier to understand at a glance - if the two types of code were linked again for each property, with each new-value-determiner being present alongside the new-value-setter).
Instead of splitting the code up for clarity, we can try to make it clearer by using abstractions.
Let's start by introducing a method to abstract the setting-or-removing of validation messages from TextEditState instances -
private TextEditState Validate(
TextEditState textEditState,
Predicate<TextEditState> validIf,
NonBlankTrimmedString messageIfInvalid)
{
if (textEditState == null)
throw new ArgumentNullException("textEditState");
if (validIf == null)
throw new ArgumentNullException("validIf");
if (messageIfInvalid == null)
throw new ArgumentNullException("messageIfInvalid");
return textEditState.With(_ => _.ValidationError, validIf(textEditState)
? null
: messageIfInvalid);
}
This will take a TextEditState, a rule that determines whether or not its "Text" value should be considered valid and a message to set if the value is not valid (if it is valid then the message will be cleared).
This would allow us to set (or remove) the validation message on the "Title" property with code such as:
message = message.With(
_ => _.Title,
Validate(
message.Title,
textEditState => string.IsNullOrWhiteSpace(textEditState.Text),
new NonBlankTrimmedString("Must enter a title")
)
);
Since the validation logic for both "Title" and "Content" is the same and "textEditState => string.IsNullOrWhiteSpace(textEditState.Text)" is quite long and going to be responsible for a lot of the "syntactic noise" that I want to avoid, this could also be abstracted by defining another method -
private bool MustHaveValue(TextEditState textEditState)
{
if (textEditState == null)
throw new ArgumentNullException("textEditState");
return !string.IsNullOrWhiteSpace(textEditState.Text);
}
If we also move the constant messages (the two validation warnings and the "Untitled" caption string) into static class members -
private readonly static NonBlankTrimmedString _defaultCaption
= new NonBlankTrimmedString("Untitled");
private readonly static NonBlankTrimmedString _noTitleWarning
= new NonBlankTrimmedString("Must enter a title");
private readonly static NonBlankTrimmedString _noContentWarning
= new NonBlankTrimmedString("Must enter message content");
.. then we can make the "Title" validation-message-setting/unsetting much clearer:
message = message
.With(_ => _.Title, Validate(message.Title, MustHaveValue, _noTitleWarning));
If we add a final helper method to make the setting of the "Caption" property simpler -
private NonBlankTrimmedString ToNonBlankTrimmedString(
TextEditState textEditState,
NonBlankTrimmedString fallback)
{
if (textEditState == null)
throw new ArgumentNullException("textEditState");
if (fallback == null)
throw new ArgumentNullException("fallback");
return (textEditState.Text.Trim() == "")
? fallback
: new NonBlankTrimmedString(textEditState.Text);
}
.. then the "ValidateMessage" can be reduced to the following:
private MessageEditState ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
return message
.With(_ => _.Caption, ToNonBlankTrimmedString(message.Title, fallback: _defaultCaption))
.With(_ => _.Title, Validate(message.Title, MustHaveValue, _noTitleWarning))
.With(_ => _.Content, Validate(message.Content, MustHaveValue, _noContentWarning));
}
Now I really think that we have the best of every world. The actual code in "ValidateMessage" is short and to the point. While there are more lines of code in total (when you also consider the "ToNonBlankTrimmedString", "Validate" and "MustHaveValue" methods), each method is more focused and very easy to fully comprehend at a glance. This is the crux of the matter for me - these changes are all about (say it with, because I'm sure that you know what's coming by this point): making code easier to reason about and hence easier to read, maintain and extend.
In the past, I've had a tendency to interchange the words "method" and "function". For the last year or so (and definitely in this series of posts, I hope!) I've been more careful not to use "function" when I mean "method". Having read around, it seems like the accepted difference between the two is (to quote an excellent example from a StackOverflow answer) -
A function is a piece of code that is called by name. It can be passed data to operate on (ie. the parameters) and can optionally return data (the return value).
All data that is passed to a function is explicitly passed.
A method is a piece of code that is called by name that is associated with an object. In most respects it is identical to a function except for two key differences.
It is implicitly passed the object on which it was called.
It is able to operate on data that is contained within the class (remembering that an object is an instance of a class - the class is the definition, the object is an instance of that data).
That means that C# only has methods since every method is associated with an object. Even static methods are, technically, since they have access to anything else that is static within the type that declares the static method.
A function will only consider data passed explicitly in arguments, which is not a concept that is possible to represent with C#.
The key difference, then, being that a function is absolutely guaranteed to always retun the same value given the same argument(s). Parallels are often drawn to mathematical functions. If you think about the need to "calculate the square root of x", this is a good example of a function as it will always return the same result for any value of "x". "x" is the only thing that matters.
With C#, you can get no such guarantees. Just to really drive the point home, here are three example - first, an instance method:
public class Adder
{
private readonly int _amountToIncrement;
public Adder(int amountToIncrement)
{
_amountToIncrement = amountToIncrement;
}
public int AddTo(int value)
{
return value + _amountToIncrement;
}
}
Obviously, the return value from "AddTo" depends on more than the argument passed in - it also depends upon the "_amountToIncrement" that the Adder instance has.
And a couple of static examples:
public static class Adder
{
private static int _amountToIncrement = 0;
public static int AddTo(int value)
{
_amountToIncrement++;
return value + _amountToIncrement;
}
}
public static class DayNameRetriever
{
public static string GetDayNameForDateThisMonth(int date)
{
var today = DateTime.Now;
var firstDayOfMonth = today.AddDays(-today.Day).AddDays(date);
return firstDayOfMonth.ToString("dddd");
}
}
Granted, theses examples are clearly contrived to illustrate a point and are not genuinely useful code. But they are also not totally unlike code that exists in the real world. The point is that, because C# only has methods, the mental burden in fully comprehending any method is increased because you have to be aware of anything else that the method might have access to.
Which is a pity, because the "ToNonBlankTrimmedString", "Validate" and "MustHaveValue" methods are perfect examples of genuine functions - they only operate on their arguments. The "ValidateMessage" only strays outside of its arguments to access the "default caption", "missing-title validation message" and "missing-content validation message" values, but since there are effectively constants (since they are static readonly instances of immutable types) then "ValidateMessage" could also be considered to be a true function (in particular, we know that it will always return the same data given the same arguments).
Note: Interestingly, there is a [Pure] attribute in the .net framework, which is intended to indicate that a method is a "pure function" (where the phrase "pure function" is effectively consistent with the description of a "function" that I gave above). This seems like a nice idea, but it's not actually enforced by the compiler and so it's more of a suggestion, which greatly reduces my enthusiasm. The reason that I want to use immutable types to represent data that should not change (like React components' props types) is that it encodes the "this data is immutable" information into the type system and results in any code that tries to break this requirement (by trying to set a value on an immutable type, for example) as being identified as an error by the compiler. The [Pure] attribute will, alas, not result in any compiler warnings or errors.
The closest that we can get to indicating that a method should be considered a "function" is by making it static. As I showed above, this does not mean that the method is truly "pure" (at least, there is no provision for the compiler to confirm that this is so) but making a function static does, at least, mean that it can not access any instance fields, properties or methods and so there is still less to consider when reading one of these methods. If you know that a method is static, then there is less mental burden in reading it since you know that there is less code that you need to consider that may possibly affect the current method.
What I'm trying to get at is that the "ValidateMessage", "ToNonBlankTrimmedString", "Validate" and "MustHaveValue" methods should all be made static and, to go further, it's worth writing all methods as static unless you have a compelling reason not to. For a lot of methods, it's obvious that they have to be instance methods - the "Render" methods on the React component classes have to be instance methods, obviously, because they depend upon the "props" data for that component instance. But, in the final MessageWriterStore implementation (see below), if we pull the "OnChange" method into a lambda then there is no need for any of the methods to not be static.
It seems, in general, that people write methods as instance methods by default and then make them static if they encounter a good reason to do so. I suggest that methods be written as static by default and only made into instance methods if there is a good reason to do so.
(To be honest, this is still something that I'm trying to consistently apply to my own work - it's very easy to unconsciously write instance methods by default by omitting the "static" keyword; old habits die hard!)
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Stores
{
public class MessageWriterStore
{
private Optional<RequestId> _saveActionRequestId, _lastDataUpdatedRequestId;
public MessageWriterStore(IReadAndWriteMessages messageApi, AppDispatcher dispatcher)
{
if (messageApi == null)
throw new ArgumentNullException("messageApi");
if (dispatcher == null)
throw new ArgumentNullException("dispatcher");
Message = GetInitialMessageEditState();
MessageHistory = NonNullList<Saved<MessageId, MessageDetails>>.Empty;
dispatcher.Receive(a => a
.If<StoreInitialised>(
condition: action => (action.Store == this),
work: action => { }
)
.Else<UserEditRequested<MessageEditState>>(action =>
Message = ValidateMessage(action.NewState)
)
.Else<SaveRequested<MessageDetails>>(action =>
{
_saveActionRequestId = messageApi.SaveMessage(action.Data);
Message = Message.With(_ => _.IsSaveInProgress, true);
})
.Else<SaveSucceeded>(
condition: action => (action.RequestId == _saveActionRequestId),
work: action =>
{
_saveActionRequestId = null;
Message = GetInitialMessageEditState();
_lastDataUpdatedRequestId = messageApi.GetMessages();
}
)
.Else<DataUpdated<NonNullList<Saved<MessageId, MessageDetails>>>>(
condition:
action => action.RequestId.IsEqualToOrComesAfter(_lastDataUpdatedRequestId),
work: action =>
{
_lastDataUpdatedRequestId = action.RequestId;
MessageHistory = action.Data;
}
)
.IfAnyMatched(() => { if (Change != null) Change(); })
);
}
public event Action Change;
public MessageEditState Message { get; private set; }
public NonNullList<Saved<MessageId, MessageDetails>> MessageHistory { get; private set; }
private readonly static NonBlankTrimmedString _defaultCaption
= new NonBlankTrimmedString("Untitled");
private readonly static NonBlankTrimmedString _noTitleWarning
= new NonBlankTrimmedString("Must enter a title");
private readonly static NonBlankTrimmedString _noContentWarning
= new NonBlankTrimmedString("Must enter message content");
private static MessageEditState GetInitialMessageEditState()
{
return new MessageEditState(
caption: _defaultCaption,
title: new TextEditState("", _noTitleWarning),
content: new TextEditState("", _noContentWarning),
isSaveInProgress: false
);
}
private static MessageEditState ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
return message
.With(_ => _.Caption, ToNonBlankTrimmedString(message.Title, _defaultCaption))
.With(_ => _.Title, Validate(message.Title, MustHaveValue, _noTitleWarning))
.With(_ => _.Content, Validate(message.Content, MustHaveValue, _noContentWarning));
}
private static NonBlankTrimmedString ToNonBlankTrimmedString(
TextEditState textEditState,
NonBlankTrimmedString fallback)
{
if (textEditState == null)
throw new ArgumentNullException("textEditState");
if (fallback == null)
throw new ArgumentNullException("fallback");
return (textEditState.Text.Trim() == "")
? fallback
: new NonBlankTrimmedString(textEditState.Text);
}
private static TextEditState Validate(
TextEditState textEditState,
Predicate<TextEditState> validIf,
NonBlankTrimmedString messageIfInvalid)
{
if (textEditState == null)
throw new ArgumentNullException("textEditState");
if (validIf == null)
throw new ArgumentNullException("validIf");
if (messageIfInvalid == null)
throw new ArgumentNullException("messageIfInvalid");
return textEditState.With(_ => _.ValidationError, validIf(textEditState)
? null
: messageIfInvalid);
}
private static bool MustHaveValue(TextEditState textEditState)
{
if (textEditState == null)
throw new ArgumentNullException("textEditState");
return !string.IsNullOrWhiteSpace(textEditState.Text);
}
}
}
Note that, since the RequestId values are now Optional<RequestId> instances, we need to change the "IsEqualToOrComesAfter" extension method -
using System;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.API
{
public static class RequestIdExtensions
{
public static bool IsEqualToOrComesAfter(
this RequestId source,
Optional<RequestId> other)
{
if (source == null)
throw new ArgumentNullException("source");
// If the "other" reference is no-RequestId then the "source" may be considered to
// come after it
if (!other.IsDefined)
return true;
return (source == other.Value) || source.ComesAfter(other.Value);
}
}
}
There's been a lot of theory covered so far. To really put it into practice, though, we need to fix the rest of the compile errors in the example application.
Changing the MessageEditor, MessageHistory, AppContainer and MessageWriteStore to use the new immutable types (the now-immutable MessageDetails and the NonNullList type from ProductiveRage.Immutable) require further changes to the MessageApi and the App file that initialises the application.
And, while we're making everything immutable, let's change the action classes. Currently, we have actions such as:
using Bridge.React;
using BridgeReactTutorial.API;
namespace BridgeReactTutorial.Actions
{
public class DataUpdated<T> : IDispatcherAction
{
public RequestId RequestId;
public T Data;
}
public static class DataUpdated
{
public static DataUpdated<T> For<T>(RequestId requestId, T data)
{
return new DataUpdated<T> { RequestId = requestId, Data = data };
}
}
}
This should be:
using Bridge.React;
using BridgeReactTutorial.API;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Actions
{
public class DataUpdated<T> : IDispatcherAction, IAmImmutable
{
public DataUpdated(RequestId requestId, T data)
{
this.CtorSet(_ => _.RequestId, requestId);
this.CtorSet(_ => _.Data, data);
}
public RequestId RequestId { get; }
public T Data { get; }
}
public static class DataUpdated
{
public static DataUpdated<T> For<T>(RequestId requestId, T data)
{
return new DataUpdated<T>(requestId, data);
}
}
}
The others require similar changes -
using Bridge.React;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Actions
{
public class SaveRequested<T> : IDispatcherAction, IAmImmutable
{
public SaveRequested(T data)
{
this.CtorSet(_ => _.Data, data);
}
public T Data { get; }
}
public static class SaveRequested
{
public static SaveRequested<T> For<T>(T data)
{
return new SaveRequested<T>(data);
}
}
}
using Bridge.React;
using BridgeReactTutorial.API;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Actions
{
public class SaveSucceeded : IDispatcherAction, IAmImmutable
{
public SaveSucceeded(RequestId requestId)
{
this.CtorSet(_ => _.RequestId, requestId);
}
public RequestId RequestId { get; }
}
}
using Bridge.React;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Actions
{
public class StoreInitialised : IDispatcherAction, IAmImmutable
{
public StoreInitialised(object store)
{
this.CtorSet(_ => _.Store, store);
}
public object Store { get; }
}
}
using Bridge.React;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Actions
{
public class UserEditRequested<T> : IDispatcherAction, IAmImmutable
{
public UserEditRequested(T newState)
{
this.CtorSet(_ => _.NewState, newState);
}
public T NewState { get; }
}
public static class UserEditRequested
{
public static UserEditRequested<T> For<T>(T newState)
{
return new UserEditRequested<T>(newState);
}
}
}
The App class requires only minor tweaks, from:
using System.Linq;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.API;
using BridgeReactTutorial.Components;
using BridgeReactTutorial.Stores;
namespace BridgeReactTutorial
{
public class App
{
[Ready]
public static void Go()
{
var container = Document.GetElementById("main");
container.ClassName = string.Join(
" ",
container.ClassName.Split().Where(c => c != "loading")
);
var dispatcher = new AppDispatcher();
var messageApi = new MessageApi(dispatcher);
var store = new MessageWriterStore(messageApi, dispatcher);
React.Render(
new AppContainer(new AppContainer.Props
{
Dispatcher = dispatcher,
Store = store
}),
container
);
dispatcher.Dispatch(new StoreInitialised { Store = store });
}
}
}
to:
using System.Linq;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.API;
using BridgeReactTutorial.Components;
using BridgeReactTutorial.Stores;
namespace BridgeReactTutorial
{
public class App
{
[Ready]
public static void Go()
{
var container = Document.GetElementById("main");
container.ClassName = string.Join(
" ",
container.ClassName.Split().Where(c => c != "loading")
);
var dispatcher = new AppDispatcher();
var messageApi = new MessageApi(dispatcher);
var store = new MessageWriterStore(messageApi, dispatcher);
React.Render(
new AppContainer(dispatcher, store),
container
);
dispatcher.Dispatch(new StoreInitialised(store));
}
}
}
Finally, the MessageApi needs various alterations to deal with the fact that all data types (such as the MessageDetails, the message history and the action classes) are immutable -
using System;
using Bridge;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.API
{
public class MessageApi : IReadAndWriteMessages
{
private readonly AppDispatcher _dispatcher;
private NonNullList<Saved<MessageId, MessageDetails>> _messages;
public MessageApi(AppDispatcher dispatcher)
{
if (dispatcher == null)
throw new ArgumentException("dispatcher");
_dispatcher = dispatcher;
_messages = NonNullList<Saved<MessageId, MessageDetails>>.Empty;
// To further mimic a server-based API (where other people may be recording messages
// of their own), after a 10s delay a periodic task will be executed to retrieve a
// new message
Window.SetTimeout(
() => Window.SetInterval(GetChuckNorrisFact, 5000),
10000
);
}
public RequestId SaveMessage(MessageDetails message)
{
return SaveMessage(message, optionalSaveCompletedCallback: null);
}
private RequestId SaveMessage(
MessageDetails message,
Action optionalSaveCompletedCallback)
{
if (message == null)
throw new ArgumentNullException("message");
var requestId = new RequestId();
Window.SetTimeout(
() =>
{
_messages = _messages.Add(Saved.For(
(MessageId)(int)_messages.Count,
message
));
_dispatcher.Dispatch(new SaveSucceeded(requestId));
if (optionalSaveCompletedCallback != null)
optionalSaveCompletedCallback();
},
1000 // Simulate a roundtrip to the server
);
return requestId;
}
public RequestId GetMessages()
{
var requestId = new RequestId();
Window.SetTimeout(
() => _dispatcher.Dispatch(DataUpdated.For(requestId, _messages)),
1000 // Simulate a roundtrip to the server
);
return requestId;
}
private void GetChuckNorrisFact()
{
var request = new XMLHttpRequest();
request.ResponseType = XMLHttpRequestResponseType.Json;
request.OnReadyStateChange = () =>
{
if (request.ReadyState != AjaxReadyState.Done)
return;
if ((request.Status == 200) || (request.Status == 304))
{
try
{
var apiResponse = (ChuckNorrisFactApiResponse)request.Response;
if ((apiResponse.Type == "success")
&& (apiResponse.Value != null)
&& !string.IsNullOrWhiteSpace(apiResponse.Value.Joke))
{
// The Chuck Norris Facts API (http://www.icndb.com/api/) returns strings
// html-encoded, so they need decoding before be wrapped up in a
// MessageDetails instance
// - Note: After the save has been processed, GetMessages is called so
// that a MessageHistoryUpdate action is dispatched
SaveMessage(
new MessageDetails(
title: new NonBlankTrimmedString("Fact"),
content: new NonBlankTrimmedString(HtmlDecode(apiResponse.Value.Joke))
),
() => GetMessages()
);
return;
}
}
catch
{
// Ignore any error and drop through to the fallback message-generator below
}
}
SaveMessage(new MessageDetails(
title: new NonBlankTrimmedString("Fact"),
content: new NonBlankTrimmedString("API call failed when polling for content :(")
));
};
request.Open("GET", "http://api.icndb.com/jokes/random");
request.Send();
}
private string HtmlDecode(string value)
{
if (value == null)
throw new ArgumentNullException("value");
var wrapper = Document.CreateElement("div");
wrapper.InnerHTML = value;
return wrapper.TextContent;
}
[IgnoreCast]
private class ChuckNorrisFactApiResponse
{
public extern string Type { [Template("type")] get; }
public extern FactDetails Value { [Template("value")] get; }
[IgnoreCast]
public class FactDetails
{
public extern int Id { [Template("id")] get; }
public extern string Joke { [Template("joke")]get; }
}
}
}
}
One pleasant change was the removal of code that was previously in the MessageApi with the following comment:
// ToArray is used to return a clone of the message set - otherwise, the caller would
// end up with a list that is updated when the internal reference within this class
// is updated (which sounds convenient but it's not the behaviour that would be
// exhibited if this was really persisting messages to a server somewhere)
Since the message list is described by an immutable structure, there is no way that a particular reference's data could change. There is no way that a component could have a reference to this data type and then find that the data in that reference had changed by the time that an event bubbled up to that component from one of its child components. Similarly, when the MessageApi passes its message history data out, there is no action that may be performed by any code that receives the message history reference that could "pollute" the data that the MessageApi stores internally.
Previously, when the MessageApi wanted to share its message history data, we had two options - we could hope that the mutable list of mutable MessageDetails would never be manipulated when it was passed out from the MessageApi or we could try to make it impossible for other code to pollute the MessageApi's copy of the data, which is what the "ToArray" call went some way towards (note that this wouldn't have saved us from code that received the mutable message history and then changed one of the fields on any of the individual mutable MessageDetails instances - this would have polluted to MessageApi's internal data).
This is a nice example of how immutable structures can actually aid performance as well as aiding code clarity. Before, we were using a defensive "ToArray" call to try to avoid any silly mistakes polluting the MessageApi's internal data. This was only a partial solution anyway, since, to really protect ourselves, we would have needed to clone the entire list - cloning each individual MessageDetails instance, as well as the list itself. Now that the data is immutable, such cloning (which can be very expensive in some case) is not necessary. I maintain, though, that the biggest benefit is to code clarity rather than performance - it is now impossible to make the "silly mistake" of mutating shared data, because the previously-implicit behaviour guideline of "do not try to mutate this data, please" is now encapsulated in the type system.
It's not uncommon to hear people claim that using immutable types incur a performance cost. I believe that this is only really true at a highly localised level. For example, if you have an array and you want to change the element at index 5, that is an incredibly cheap operation and it is not possible to have an "immutable array" that has a method that will give you a new immutable array instance with a different value at index 5 as cheaply. At this level, mutable structures can perform operations more quickly. However, immutable structures can allow techniques that provide performance benefits at a higher level, such as described above, where expensive cloning operations may be avoided entirely (general-case cloning operations can be expensive in CPU and in memory, since a clone will duplicate the entire data structure, whereas mature immutable-type libraries leverage clever "persistent structures" to reuse data between instances).
(It has become less common to hear this argument against immutable data structures since they are being much more widely used these days - React is an excellent example in that it leverages immutability to allow for fantastic performance, rather than immutability being a cost that the React library has to pay).
While performance is not my number one goal (which is not to say that I don't think it's an important target, I'm just saying that I value code clarity more highly), this topic of conversation leads me nicely on to the next topic.
If a "pure function" is one that returns a value based solely upon its arguments, then a "pure component" is a parallel concept - it generates content based solely upon its "props" data.
To recall what our example application looks like -
There are basically two parts to it; the Message Editor and the Message History. The Message Editor part changes after one of any of the following occurs -
The Message History part changes only when the MessageApi sends a message to say that there is new message data to display.
What happens in our application is that every change results in a full React re-render. React is very efficient and its Virtual DOM minimises (and can batch) changes to the slow browser DOM, so this is rarely something to worry about. However, it might lead you to think -
If I know that a particular event will only result in changes to the Message Editor, isn't it wasteful to React to re-render the entire Message History content in its Virtual DOM - particularly if it does this only to discover that no changes to the browser DOM need to be applied?
This is an entirely reasonable question. If there are a hundred messages in the Message History, does that component really have to re-render them all in the Virtual DOM every time that the user presses a button to change the value in the "Title" text input in the Message Editor? What if there 1000 messages in the history? Or what if this was a much more complicated application with many, many editable inputs and lists of results all over the page - do I really want the entirety of this complicated UI to be re-rendered by the Virtual DOM every time that the user edits a single field?
If we were using mutable structures to represent the data that the React component hierarchy has to render, we would have a few options. One would be to change the component structure such that there were more stateful components in order to try to only update branches of the hierarchy that need to change according to particular changes. In our example, the MessageEditor could be made stateful in a bid to limit changes to the text inputs from resulting in re-renders of the MessageHistory (which would also have to become a stateful component, so that it could update itself when the message history data changes). This would require the MessageWriteStore to be changed as well. On the surface of it, this doesn't necessarily sound like a terrible idea, but stateful components will always require more thought and planning than stateless components and so this would be a move back towards more complicated component models. This would be an unfortunate step back from where we are now, where the complications are minimised and most components are stateless. We would no longer have a render-down and pass-events-up model, we would have a sort of branched version of it.
Another option would be to try to have components make use of React's "shouldComponentUpdate" method. This is a method that may optionally be implemented on components, that is called before a component's "Render" method is called, so long as that component has been rendered at least once before. It will be given two "props" values - one is the props data from the last render and the second is the new props data that has been specified for the re-render. If this method returns true then the component is re-rendered (to the Virtual DOM) as normal. If it returns false then the component's "Render" method is not called. This would mean that none of its child components would be re-rendered either, since those re-renders are only triggered by code in their parent's component's "Render" method. If it was possible for the MessageHistory to look at its last props and its next props and see that they describe the same data, then the entire re-render work could be avoided. The problem comes in working that out, though - for the cases where the old and new messages data was the same and when we had an IEnumerable set of mutable MessageDetails instances, we would have had to have enumerated through every value in the set and compared the "Title" and "Content" values on each message. If they all matched then the old and new data would have been proven to have been the same and the re-render would not be required. But was all that comparison work really much cheaper than just letting the Virtual DOM do its magic?
One of the good thing about immutable structures is that data can safely be shared and reused. Now that the MessageHistory component takes an immutable NonNullList of immutable MessageDetails instances, if the data hasn't changed then the same NonNullList<Saved<MessageId, MessageDetails>> reference will be passed to the MessageHistory.Props instance - but if the data has changed then, by necessity, a new NonNullList<Saved<MessageId, MessageDetails>> reference will be provided. This would make a "shouldComponentUpdate" implementation very simple - just look at each property on the old and new props references and use reference equality comparisons to see if anything's changed.
The bad news is that the StatelessComponent base class in the Bridge / React bindings doesn't support "shouldComponentUpdate". So you can't try to take advantage of it to reduce the work that the Virtual DOM does - only the Component (which is the full stateful component) base class supports it. The good news is that you don't need to implement it manually. If you're creating stateless components whose props classes have properties that are all primitive values (such as bool, int, string, etc..) and / or immutable types and / or functions* and if the components genuinely render entirely according to the props data (no accessing DateTime.Now, for example) then you can derive from the PureComponent base class instead. This automatically implements "shouldComponentUpdate" behind the scenes.
* (There are some cases where "props" properties that are callbacks - like "OnChange" on the TextInput - can't be compared by the PureComponent's magic, but most of the time they can be and the details of when they can and can't are outside the scope of this article - so let's just assume that function / Action<T> / callback property types CAN always be handled).
To illustrate, add the following line to the start of the MessageHistory "Render" method -
Console.WriteLine("MessageHistory.Render");
Now, run the application in the browser and bring up the browser console in the dev tools. Every time that the app re-renders in the Virtual DOM, "MessageHistory.Render" will be written to the console. Every time that a key is pressed to change one of the text input elements, the entire UI will be re-rendered and "MessageHistory.Render" will be displayed in the console.
Now, change the base class of the MessageHistory from StatelessComponent<MessageHistory.Props> to PureComponent<MessageHistory.Props>. It will look like this:
using System;
using System.Linq;
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class MessageHistory : StatelessComponent<MessageHistory.Props>
{
public MessageHistory(
NonNullList<Saved<MessageId, MessageDetails>> messages,
Optional<NonBlankTrimmedString> className = new Optional<NonBlankTrimmedString>())
: base(new Props(className, messages)) { }
public override ReactElement Render()
{
Console.WriteLine("MessageHistory.Render");
var className = props.ClassName;
if (!props.Messages.Any())
className = className.Add(" ", new NonBlankTrimmedString("zero-messages"));
// Any time a set of child components is dynamically-created (meaning that the
// numbers of items may vary from one render to another), each must have a unique
// "Key" property set (this may be a int or a string). Here, this is simple as
// each message tuple is a unique ID and the contents of that message.
var messageElements = props.Messages
.Select(savedMessage => DOM.Div(new Attributes { Key = (int)savedMessage.Key },
DOM.Span(new Attributes { ClassName = "title" }, savedMessage.Value.Title),
DOM.Span(new Attributes { ClassName = "content" }, savedMessage.Value.Content)
));
// When child components are specified (as they are through the second argument of
// DOM.Div), the argument is of type Union<ReactElement, string>[] (meaning that each
// element may be another component or it may be a simple text value)
// - The React bindings have an extension method that transforms an IEnumerable set
// of components (such as "messageElements") into an Union<ReactElement, string>[]
return DOM.FieldSet(
new FieldSetAttributes { ClassName = className.ToStringIfDefined() },
DOM.Legend(null, "Message History"),
DOM.Div(null, messageElements)
);
}
public class Props : IAmImmutable
{
public Props(
Optional<NonBlankTrimmedString> className,
NonNullList<Saved<MessageId, MessageDetails>> messages)
{
this.CtorSet(_ => _.ClassName, className);
this.CtorSet(_ => _.Messages, messages);
}
public Optional<NonBlankTrimmedString> ClassName { get; }
public NonNullList<Saved<MessageId, MessageDetails>> Messages { get; }
}
}
}
Re-build the application and refresh it in the browser. Now change the text input values while keeping an eye on the console. The "MessageHistory.Render" message will initially only appear in the console when the MessageHistory component is first rendered, the changes to the text inputs no longer require the MessageHistory component be redrawn by the Virtual DOM. When the message data actually changes (whether due to you saving a new message or due to a new Chuck Norris fact arriving), the MessageHistory component will update - indicated by another "MessageHistory.Render" message being displayed in the console.
This is an excellent example of how using immutable structures can result in cleaner and more efficient code. In a complicated UI (or if your application is very performance-sensitive - if you're trying to achieve 60fps on mobile, for example) then being able to save the Virtual DOM the work of determining whether entire branches of the component hierarchy should be re-rendered is invaluable. And getting this optimisation "for free" makes it even better - if you're following my recommendations and the vast majority of your React components are stateless and al of your data types are immutable then you might as well use the PureComponent base class and reap the performance benefits.
I want to go into some of the finer point of the PureComponent's optimisation rules. There's nothing particularly complicated or surprising, but there are some nuances that it's worth being aware of.
When the PureComponent implements "shouldComponentUpdate" behind the scenes, the React library provides it with two separate instances to compare of the Props class for the current component. The first thing that the PureComponent does is ensure that the two props instances are of the same type (while it would be strange, it would not be illegal to create types derived from the component's Props class - but if different derived types were provided for the old and new props then it doesn't seem safe to try to compare them, who knows why they are different or what significance there could be in the differences). If the old and new props references are of the precise same type, then it enumerates the properties on the type and compares the values on the old and new props instances. Each pair of property values must match - this means that they are either both null or they are both the same value of a primitive type or they are the same reference or they are both non-null and the first value has an "Equals" method that returns true when the second value is passed into it.
On the whole, this means that things largely work completely intuitively. Particularly due to the way that the "With" extension methods works for IAmImmutable-implementing class. If "With" is called with a property value update where the new value is the same as the old value, then "With" returns the original instance unaltered. This is most easily explained with an example:
var title = new TextEditState(text: "hello", validationError: null);
var title2 = title.With(_ => _.Text, "hello");
var title3 = title.With(_ => _.Text, "hell");
The "title2" instance will be the same as the "title" reference - the text value was asked to changed from "hello" to "hello", which is no change at all. Since the TextEditState type is immutable, there is no pointing copying "title" to create a new "title2" reference with all of the same data. "title3" will be a new instance, since the text value needs to change from "hello" to "hell".
Having "With" return the same reference when no change is required is beneficial for garbage collection - the fewer references that are created means the less work that it has to do. But it's also very important for the PureComponent since that prefers to use referential equality to determine whether a property value has changed. If a Props class has a TextEditState property on it, when the values on the old and new props are compared then we want the TextEditState references to be the same if the data that they represent hasn't changed.
I think that another example is called for. In the example application from this series, the Message Editor form is given information in its props that describes the current state of the form - the "Caption", the "Title" text-input-value-and-any-validation-message, the "Content" text-input-value-and-any-validation-message (and information about whether the form should be disabled because a save is in progress). This data is all contained within a MessageEditState instance. The MessageEditor component renders each text input by generating child ValidatedTextInput components. These child ValidatedTextInput components raise an "OnChange" event when the user wants to alter the content in the text input element, the event includes a string argument for the new text value. When this happens, the MessageEditor takes this new text value and uses it to create a new MessageEditState instance, using the "With" extension method - this new instance is then passed up on the MessageEditor's "OnChange" event. This event will result in the UI being re-rendered to display the new data.
However, React raises change events from text inputs for user interactions even if they don't actually result in a change. If, for instance, there is a text input with the value "Hello" in it and you highlight that text and copy it to the clipboard and then paste it into that same text input then the value obviously hasn't changed, but React still raises a change event from the text input due to the paste action. What we want to happen in this case is for the "new" MessageEditState instance that the MessageEditor creates when it raises its "OnChange" event to be the exact same reference as the MessageEditState given to the MessageEditor when it last rendered. This way, when the MessageEditor is asked to re-render then it will find that the "new" props data is exactly the same as the old props data and the PureComponent logic will tell React that it needn't bother re-rendering the component at all.
Maybe it's worth examining the MessageEditor code again to try to make this seem less abstract:
using System;
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
using ProductiveRage.Immutable;
namespace BridgeReactTutorial.Components
{
public class MessageEditor : StatelessComponent<MessageEditor.Props>
{
public MessageEditor(
MessageEditState message,
Action<MessageEditState> onChange,
Action onSave,
Optional<NonBlankTrimmedString> className = new Optional<NonBlankTrimmedString>())
: base(new Props(className, message, onChange, onSave)) { }
public override ReactElement Render()
{
var formIsInvalid =
props.Message.Title.ValidationError.IsDefined ||
props.Message.Content.ValidationError.IsDefined;
return DOM.FieldSet(
new FieldSetAttributes { ClassName = props.ClassName.ToStringIfDefined() },
DOM.Legend(null, props.Message.Caption),
DOM.Span(new Attributes { ClassName = "label" }, "Title"),
new ValidatedTextInput(
className: new NonBlankTrimmedString("title"),
disabled: props.Message.IsSaveInProgress,
content: props.Message.Title.Text,
onChange: newTitle => props.OnChange(
props.Message.With(_ => _.Title, new TextEditState(newTitle))
),
validationMessage: props.Message.Title.ValidationError
),
DOM.Span(new Attributes { ClassName = "label" }, "Content"),
new ValidatedTextInput(
className: new NonBlankTrimmedString("content"),
disabled: props.Message.IsSaveInProgress,
content: props.Message.Content.Text,
onChange: newContent => props.OnChange(
props.Message.With(_ => _.Content, new TextEditState(newContent))
),
validationMessage: props.Message.Content.ValidationError
),
DOM.Button(
new ButtonAttributes
{
Disabled = formIsInvalid || props.Message.IsSaveInProgress,
OnClick = e => props.OnSave()
},
"Save"
)
);
}
public class Props : IAmImmutable
{
public Props(
Optional<NonBlankTrimmedString> className,
MessageEditState message,
Action<MessageEditState> onChange,
Action onSave)
{
this.CtorSet(_ => _.ClassName, className);
this.CtorSet(_ => _.Message, message);
this.CtorSet(_ => _.OnChange, onChange);
this.CtorSet(_ => _.OnSave, onSave);
}
public Optional<NonBlankTrimmedString> ClassName { get; }
public MessageEditState Message { get; }
public Action<MessageEditState> OnChange { get; }
public Action OnSave { get; }
}
}
}
If the user attempted this copy-paste-same-value thing in the "Title" input, then the "onChange" event from the ValidatedTextInput that renders the Title value would be raised -
onChange: newTitle => props.OnChange(
props.Message.With(_ => _.Title, new TextEditState(newTitle))
)
This will result in the MessageEditor's "OnChange" event being raised, with a new MessageEditState instance. The key thing is that we want the new MessageEditState instance to be the same as the current MessageEditState instance if the new "Title" string is exactly the same as the current "Title" string.
One way to do this would be to not worry about how "With" does or doesn't work and to add a condition into the lambda - eg.
onChange: newTitle =>
{
if (newTitle != props.Message.Title.text)
props.OnChange(props.Message.With(_ => _.Title, new TextEditState(newTitle)));
}
However, this means that more logic has to go in the components (which I want to avoid). Worse, it's boring and repetitive logic, which is the kind that I find is most likely to be done incorrectly by accident because you almost feel like you can write it on auto-pilot. It would be best if this could be handled automatically.
Well, it can be if we always use "With" to update values. In the code above, I've actually been a bit naughty. A TextEditState includes two values - "Text" and "ValidationMessage". The "ValidationMessage" will get set according to the "Text" value when validation is applied (which happens in the MessageWriterStore in this application). The code above creates a new TextEditState with the "newTitle" string, erasing any "ValidationMessage" value. This is not really correct, since only the validation logic should change the "ValidationMessage" value. In this example, a validation message should only be displayed if the text value is empty - but the components should have no knowledge of this since we want them to be as dumb as possible. So, any time that a component creates a new TextEditState to include in an "OnChange" event, the "ValidationMessage" property should be untouched - again, it is the responsibility of the store (and not the component) to worry about ensuring that the "ValidationMessage" value is correct for the "Text" value before a re-render is triggered.
So, this code:
onChange: newTitle => props.OnChange(
props.Message.With(_ => _.Title, new TextEditState(newTitle))
)
should really be this:
onChange: newTitle => props.OnChange(
props.Message.With(_ => _.Title, props.Message.Title.With(_ => _.Text, newContent))
)
This only changes the "Text" property on the "Title" TextEditState - which means that, in our copy-paste-same-value case, no new TextEditState instance will be created. This still means that the MessageEditor's "OnChange" event will be raised (which will result in an action being sent through the Dispatcher and received by the MessageWriterStore, which will raise a "Change" event and cause the AppContainer to re-render the UI), but when the MessageEditor is asked to re-render then it will realise that the new data is the same as its current data and it will tell React not to bother re-rendering it. The code still had to do one full pass of the raise-event-up-to-top-level-component-and-send-change-message-through-Dispatcher-to-the-Store, even though the data hadn't changed, but that sort of work is very cheap (certainly much cheaper than any DOM or even Virtual DOM interactions).
All of the above came "for free" by using IAmImmutable-implementing classes and PureComponent and by applying updates using the "With" extension method. There are some cases where you need to do a little work to help the system out, though. If you recall the "ValidateMessage" function that we wrote earlier -
private static MessageEditState ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
return message
.With(_ => _.Caption, ToNonBlankTrimmedString(message.Title, fallback: _defaultCaption))
.With(_ => _.Title, Validate(message.Title, MustHaveValue, _noTitleWarning))
.With(_ => _.Content, Validate(message.Content, MustHaveValue, _noContentWarning));
}
This always set the "Caption" based upon the "Title" value. If there is a "Title" value in the text input of the message editor form of "My New Message" then the "Caption" value will also be "My New Message". The "ToNonBlankTrimmedString" method is implemented as:
private static NonBlankTrimmedString ToNonBlankTrimmedString(
TextEditState textEditState,
NonBlankTrimmedString fallback)
{
if (textEditState == null)
throw new ArgumentNullException("textEditState");
if (fallback == null)
throw new ArgumentNullException("fallback");
return (textEditState.Text.Trim() == "")
? fallback
: new NonBlankTrimmedString(textEditState.Text);
}
Every time that "ValidateMessage" is called and there is a non-blank "Title" value then a new NonBlankTrimmedString instance will be created for the "Caption". The problem is that if the "Title" input isn't changing (if the user is currently changing the "Content" text input box, for example) then we will be creating new NonBlankTrimmedString instances for the same "Title" value - and the PureComponent will see each new NonBlankTrimmedString reference as a new and distinct value.
We could try to prevent this by changing "ValidateMessage" such that it tries to avoid creating new NonBlankTrimmedString instances for the Caption -
private static MessageEditState ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
if (message.Title.Text.Trim() == "")
message = message.With(_ => _.Caption, _defaultCaption);
else if (message.Title.Text.Trim() != message.Caption)
message = message.With(_ => _.Caption, new NonBlankTrimmedString(message.Title.Text));
return message
.With(_ => _.Title, Validate(message.Title, MustHaveValue, _noTitleWarning))
.With(_ => _.Content, Validate(message.Content, MustHaveValue, _noContentWarning));
}
.. but this brings us back round to "ValidateMessage" being very noisy. This will have a tendency to make it prone to error and it definitely moves away from the goal of code clarity that I'm aiming for.
An alternative is to implement an "Equals" override for the NonBlankTrimmedString class. The "With" extension method, like the PureComponent's "shouldComponentUpdate" logic", will consider an "Equals" method if referential equality fails. This means that if we call
.With(_ => _.Caption, ToNonBlankTrimmedString(message.Title, fallback: _defaultCaption))
.. and if the return value from "ToNonBlankTrimmedString" is a NonBlankTrimmedString instance that is equivalent to the current "Caption" value (according to the NonBlankTrimmedString "Equals" implementation below), then "With" will return the same reference.
using System;
using Bridge;
using Bridge.React;
namespace BridgeReactTutorial.API
{
public sealed class NonBlankTrimmedString
{
public NonBlankTrimmedString(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Null, blank or whitespace-only value specified");
Value = value.Trim();
}
/// <summary>
/// This will never be null, blank or have any leading or trailing whitespace
/// </summary>
public string Value { get; }
/// <summary>
/// It's convenient to be able to pass a NonBlankTrimmedString instance as any argument
/// that requires a string
/// </summary>
public static implicit operator string(NonBlankTrimmedString value)
{
if (value == null)
throw new ArgumentNullException("value");
return value.Value;
}
/// <summary>
/// It's convenient to be able to pass a NonBlankTrimmedString instance as any argument
/// that requires a ReactElement-or-string, such as for the children array of the React
/// DOM component factories
/// </summary>
public static implicit operator Union<ReactElement, string>(NonBlankTrimmedString value)
{
if (value == null)
throw new ArgumentNullException("value");
return value.Value;
}
public override bool Equals(object o)
{
var otherNonBlankTrimmedString = o as NonBlankTrimmedString;
return
(otherNonBlankTrimmedString != null) &&
(otherNonBlankTrimmedString.Value == Value);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
}
}
Having an "Equals" implementation on types such as NonBlankTrimmedString makes a lot of sense because we essentially want them to be treated as "value types" - if two references describe the same data then they should be treated as the same value. Note that a custom "Equals" implementation was not necessary for MessageId, since that is a struct - in C#, structs automatically get an "Equals" implementation created for them that considers two struct instances to be equivalent if all of their properties have the same values and the JavaScript generated by Bridge does the same in order to maintain compatibility.
One thing that I snuck into the new NonBlankTrimmedString above is that the class is now sealed. If it is possible to inherit from a given class, it becomes much more difficult to implement a reliable "Equals" method. For the sake of argument, imagine a class "X" that is not sealed and that has a single string "Value" property and that implements its own "Equals" method, exactly the same as the NonBlankTrimmedString "Equals" method above. Then imagine that a class "Y" is derived from "X" and adds a second property, a "Count" int. If "Y" does not override the "Equals" implementation on "X" then it seems likely that the "Equals" implementation that "Y" inherits from "X" will be inaccurate - if an instance of "Y" with a "Value" of "Hello" and "Count" of 1 was compared to another instance of "Y" with a "Value" of "Hello" but a "Count" of 2 then they would be considered to be equivalent, which would almost certainly not be the expected behaviour. There is an implicit assumption that if "X" is derived from and the derived type adds properties then that derived type should have its own "Equals" method. A failure to respect this implicit assumption will not result in any compiler warnings, it will likely result only in unexpected runtime behaviour at some point. I don't like implicit assumptions, as I'm sure that you've been able to tell from the themes in this post. An easy way to avoid this problem is to prevent "X" from being inherited from, by making it sealed. This is precisely what I've done with the NonBlankTrimmedString.
This leads me on to another guideline - I believe that 99% of all classes should be abstract or sealed. Any class that may be inherited from requires planning, to try to make it as easy as possible for any derived types to work in non-suprising manners. This is complicated, if not impossible (as the "Equals" conundrum above hopefully illustrates for an extremely simple case). However, if you really think that it should be possible for a class to be inherited from, then I think that you should document any implicit assumptions on that base class and you should carefully think about how it should and shouldn't be used. I have found that the most common cases for which a base class are suitable are those where some shared functionality is required that can't easily be provided via composition (which I will talk about in a moment), but where that base class is not fully-functional itself and so must be inherited from in order to be useful. The Component, StatelessComponent and PureComponent base classes in the Bridge / React bindings are excellent examples; they are required in order to define components that the React library can work with, but the classes are not functional on their own - they must be inherited from in order to be useful, therefore they are defined as abstract classes.
Historically, I think that there have been beliefs that many object models lend themselves well to inheritance hierarchies. In Web Forms (if I remember correctly - it's been a long time), a Button inherited from a WebControl and that inherited from a Control or a Component.. or something. The idea was that every component as you went up the hierarchy was a specialisation of the one below it. Which arguably made sense. But I find that the waters often get very murky when these "is-a" hierarchies are constructed and it's very easy to confuse a "has-a" characteristic for being an "is-a". For example, the MessageWriterStore needs to register for events from the Dispatcher, so perhaps it is a specialisation of a "DispatcherMessageReceiver". But it's also responsible for saving messages, so perhaps it's also specialisation of a "MessageRecorder". Not only is it more complicated to design classes that are intended to be inherited from, but C# only allows one class to inherit from a single base class - so if MessageWriterStore needs to be a specialisation of both a "DispatcherMessageReceiver" and a "MessageRecorder" then we have a problem; possibly only solved by creating a specialisation of the "DispatcherMessageReceiver" that also deals with recording messages, so that the MessageWriterStore can inherit from that.
Perhaps that example is a little fatuous, but similar sorts of issues do genuinely occur when inheritance trees are viewed as "the best way" to build classes. The alternative is to use composition, which is where classes are designed in a "has-a" manner. The current MessageWriterStore is designed in this way as its constructor declares that it requires an AppDispatcher and a IReadAndWriteMessages implementation. Not only does composition avoid the multiple inheritance problem (also known as the "deadly diamond of death") but it makes code easier to fully comprehend. With inheritance, each class is a sum of its behaviour and all of the behaviour of its base class (which is likewise a sum of its behaviour and all of the behaviour of its base class, repeated for as many levels as the inheritance tree is deep). With composition, each piece is fully self-contained and communication between one part and another is throughly tightly constrained interfaces.
(A couple of months ago, I read "5 reasons your team will love you for composition", which I think offers a good take on some of the practical benefits positives of preferring composition to inheritance - it's well worth a quick read).
In summary, I strongly believe that almost all relations between classes may be described better by using composition than inheritance (where "better" means that the code is easier to reason about and, also, easier to test) and so almost no classes should need to be inherited from. In the few cases where inheritance is necessary, the base classes must be carefully designed for inheritance and the use cases that necessitate inheritance will almost always be ones where the base classes are non-functional in isolation, where they require a derived type in order to work.
Therefore, 99% of classes should be written to be abstract or to be sealed. This goes for all classes that I've shown code for in this series - the MessageEditor component class should be sealed, its Props class should be sealed, the MessageWriterStore should be sealed, the MessageDetails should be sealed, the Saved class should be sealed and the NonBlankTrimmedString should be sealed. They should all be sealed.
Now, if we wanted to play devil's advocate then we could say that any "ClassName" property on a component class shouldn't be Optional<NonBlankTrimmedString> since I've said that everything should be really specific, since richer types have a lot of value in expressing intent. We could say that there should be a more specific ClassName class, which only allows characters that are valid in an html class name (React deals with encoding string "ClassName" properties, so this isn't really a big concern - but I'm trying to prove another point, so bear with me). We could also say that this ClassName class really is a specialisation of NonBlankTrimmedString and so surely we should allow NonBlankTrimmedString to be inherited from so that ClassName could be derived from it. On the surface, this doesn't seem too unreasonable. However, the only actual benefit of having ClassName inherit from NonBlankTrimmedString (other than it just feeling like something that may possibly be a good idea) would be if there was a point at which you had a method that took an argument of type NonBlankTrimmedString that you wanted to give a ClassName instance - because it seems like any valid ClassName will also be a valid NonBlankTrimmedString. I think that the benefit is just too small to outweight the cost. Inheritance brings with it too much potential complication (and associated mental burden) that any small benefit like this is not enough to bring inheritance into play. On top of which, if you really did want to be able to use a ClassName instance anywhere that a NonBlankTrimmedString is required, you could do this by implementing an implicit operator method on ClassName that allows it be implicitly cast to a NonBlankTrimmedString - then you would have the marginal benefit of being able to use a ClassName anywhere that you need a NonBlankTrimmedString but still be able to keep NonBlankTrimmedString sealed and less prone to error.
Writing classes that are sealed by default is another practice that I am still trying to instill into myself. Much like writing methods to be static by default, I think that it makes a huge amount of sense but it's also a concept that I've only cemented in the last year or so and I'm still trying to consistently apply it!
This has turned out to be a pretty huge post. But I didn't want to try to split it up because all of the principles are related, really.
If you'd stopped reading after Part Two then I think that you would have the tools to construct client-side applications that will scale from something simple up to something massively complex. The combination of C# (which is time-proven as a language that not only enables large applications to be built and maintained over long periods of time, it is a language where it is not exceptional for this to be the case*) and React is very powerful.
* (Languages such as PHP and JavaScript CAN be used to build and maintain large code bases, but I think that it's telling that the companies that take this work seriously rely on, and develop, additional tooling to make it manageable, like Hack and Flow).
However, the principles behind React that I found most appealing when I first encountered it - that it primarily exists to make complicated interactions in an application simpler and scalable - are what I want to apply to my C#. In the concepts that I'm advocating here for writing C# React applications and in React itself, performance is not the number one priority; it is not something for which code clarity must be sacrificed in order to attain. Performance is, however, still important - but it is achieved through high-level design decisions and not through micro-optimisations (I still think that the best example of this is how performing particular operations on an immutable structure will almost always be slower than on the corresponding mutable structure, but the algorithms that may be written based upon the safe sharing of immutable types mean that actual application performance will be greater - whether this is in the avoidance of deep-cloning references for safety or in the way that the PureComponent can avoid re-rendering of entire branches of the UI by the Virtual DOM).
To summarise, I believe that if the guidelines presented here are followed then you will be able to go beyond the C# React applications that you would have written with Part-Two Knowledge and be able to write code that is easier to understand (whether by other Developers or by yourself, when you come back to a piece of code after six months), easier to maintain and extend and that is more efficient.
To recap, the guidelines are as follows:
I'm not sure that there's anything earth-shatteringly original about any one of these suggestions. Combining them can, in my ever-so-humble opinion, result in significantly higher quality code than not following them. While these posts have been about writing React applications using Bridge.NET, these rules may be applied just as well to any C# code. The ProductiveRage.Immutable NuGet package only works with Bridge projects, though - but if anyone asked them I would happily consider recreating it for vanilla C#! :)
There are some other things to consider when writing complete Bridge / React applications. I keep saying that it's important for code to be testable, but I haven't offered any way to write the actual unit tests. I've also not had to offer any URL-routing capabilities, since the example application is so simple. But routing is something that is almost certainly going to be required in any real browser-based application.
These are both technologies that I would like to cover in future posts, but probably as shorter follow-up pieces. In terms of how to write React applications in C#, I am happy with what I've covered in these three posts - I wanted to get the example code to where it is now but without jumping straight to the final architecture; I wanted to illustrate why one-way data binding is so beneficial and then why a Flux-like structure is so helpful and then why immutable types are such a good idea. Hopefully, by starting from the basics and introducing new concepts and approaches only when there was a way to illustrate the improvements that they yield, you all agree with me that this is the way to do things!
Of course, if you disagree in general, or on any particular points, or you have any tweaks to what I've suggested then I would be very interested to hear. That's what the comments section is for!
Posted at 21:32
16 March 2016
To summarise where we got to in Part One: we've got an entry form where, after you enter Title and Content values, you may save a message. During the message-saving process, the form and save button are disabled. Once the saving has completed, the form will be cleared and it will be re-enabled. Validation logic in the MessageEditor prevents the form from being saved while one or both of the required inputs are without value. After the save has succeeded, a read action will begin in the background - once the read operation has completed, a MessageHistory component will display all of the saved messages. All interactions with the MessageApi are handled by the top-level AppContainer component. Similarly, all user interaction events are passed up to this top-level component, from where re-renders of the UI state are triggered.
I see this arrangement as a top-to-bottom tree in terms of rendering - the top-level component is in control of what to display in the component hierarchy beneath it, and all of the information required to display those child components is contained within the top-level component's state data.
In terms of event-handling, events may occur deep down within the tree and are then passed back up to the top-level component. As an event passes up from the html element that it originates in, up to the top level, it will gather more and more information - for example, when a user requests a change to the "Title" text input (such as by pressing a key while the input has focus), then an event is raised by the TextInput component saying that a string value should be changed. This TextInput is a child component of the MessageEditor, which acknowledges this string-changed event and raises it own event; a MessageDetails-changed event. The "Content" value of this new message will be unchanged, but the "Title" value will have a new value - the new value that the TextInput should take. This event is received by the AppContainer and it uses it to update its internal representation of the application, changing its "state" reference by calling "SetState" and so triggering a re-render.
The path may be traced downward when considering rendering and may be traced upward when considering events.
This one-way passing of data is very powerful in terms of modelling interactions that are easy to follow. As a reminder, a common way to deal with interactions like this in days gone by (ie. before React popularised "one-way bindings") was for changes to elements to be reflected immediately in-place and for any interested parties to subscribe to events that those elements raise after they change. So, for the message entry form -
With the old method, the "Title" and the "Content" inputs would accept changes that the user makes immediately - and the fieldset legend and the save button components would need to listen out to some of these changes. The legend changes to match what is entered in the "Title" input (unless it's blank, in which case the legend shows "Untitled"). The save button needs to be enabled if both "Title" and "Content" have values and disabled if one or both of them are without.
I mentally envisage this as star-shaped event handling - a change to one element may fan out to many others. In some cases, these changes would then cascade on to other elements, and then on again and again (hopefully not "again and again and..", but it was difficult to keep a handle on these things with this sort of approach - because it was difficult to get a simple view as to what could affect what).
With one-way data binding, events go up to the top, are processed and then the re-render distributes this new information all the way down. When the MessageEditor is rendered, it knows what the current "Title" value is and so it knows what to put in that "Title" TextInput and it knows what the fieldset legend should be and it knows whether the save button should be enabled or not.
This arrangement can take you a long way, I think. But there are a couple of things that I take issue with -
We can refine this arrangement a little by introducing an intermediary for events to pass through and by pulling out the logic that exists within components (such as the validation within the MessageEditor and the when-a-save-happens-then-.. logic in the AppContainer).
Rather than talk about how some applications could theoretically benefit from a change to the architecture, I want to do it with a demonstration.
I'm going to change the MessageApi so that, after a short delay, new messages start appearing in its internal list. The "SaveMessage" and "GetMessages" methods are unchanged, it's just that there's a background process going on as well. To keep things interesting, I'm going to source these message from the The Internet Chuck Norris Database API -
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bridge;
using Bridge.Html5;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.API
{
public class MessageApi : IReadAndWriteMessages
{
private readonly List<Tuple<int, MessageDetails>> _messages;
public MessageApi()
{
_messages = new List<Tuple<int, MessageDetails>>();
// To further mimic a server-based API (where other people may be recording messages
// of their own), after a 10s delay a periodic task will be executed to retrieve a
// new message
Window.SetTimeout(
() => Window.SetInterval(GetChuckNorrisFact, 5000),
10000
);
}
public Task SaveMessage(MessageDetails message)
{
if (message == null)
throw new ArgumentNullException("message");
if (string.IsNullOrWhiteSpace(message.Title))
throw new ArgumentException("A title value must be provided");
if (string.IsNullOrWhiteSpace(message.Content))
throw new ArgumentException("A content value must be provided");
var task = new Task<object>(null);
Window.SetTimeout(
() =>
{
_messages.Add(Tuple.Create(_messages.Count, message));
task.Complete();
},
1000 // Simulate a roundtrip to the server
);
return task;
}
public Task<IEnumerable<Tuple<int, MessageDetails>>> GetMessages()
{
// ToArray is used to return a clone of the message set - otherwise, the caller would
// end up with a list that is updated when the internal reference within this class
// is updated (which sounds convenient but it's not the behaviour that would be
// exhibited if this was really persisting messages to a server somewhere)
var task = new Task<IEnumerable<Tuple<int, MessageDetails>>>(null);
Window.SetTimeout(
() => task.Complete(_messages.ToArray()),
1000 // Simulate a roundtrip to the server
);
return task;
}
private void GetChuckNorrisFact()
{
var request = new XMLHttpRequest();
request.ResponseType = XMLHttpRequestResponseType.Json;
request.OnReadyStateChange = () =>
{
if (request.ReadyState != AjaxReadyState.Done)
return;
if ((request.Status == 200) || (request.Status == 304))
{
try
{
var apiResponse = (ChuckNorrisFactApiResponse)request.Response;
if ((apiResponse.Type == "success")
&& (apiResponse.Value != null)
&& !string.IsNullOrWhiteSpace(apiResponse.Value.Joke))
{
// The Chuck Norris Facts API (http://www.icndb.com/api/) returns strings
// html-encoded, so they need decoding before be wrapped up in a
// MessageDetails instance
SaveMessage(new MessageDetails
{
Title = "Fact",
Content = HtmlDecode(apiResponse.Value.Joke)
});
return;
}
}
catch
{
// Ignore any error and drop through to the fallback message-generator below
}
}
SaveMessage(new MessageDetails
{
Title = "Fact",
Content = "API call failed when polling for server content :("
});
};
request.Open("GET", "http://api.icndb.com/jokes/random");
request.Send();
}
private string HtmlDecode(string value)
{
if (value == null)
throw new ArgumentNullException("value");
var wrapper = Document.CreateElement("div");
wrapper.InnerHTML = value;
return wrapper.TextContent;
}
[IgnoreCast]
private class ChuckNorrisFactApiResponse
{
public extern string Type { [Template("type")] get; }
public extern FactDetails Value { [Template("value")] get; }
[IgnoreCast]
public class FactDetails
{
public extern int Id { [Template("id")] get; }
public extern string Joke { [Template("joke")]get; }
}
}
}
}
The Chuck-Norris-fact-retrieval code could be made shortener by casting the "apiResponse" reference to dynamic, but I thought that it would a nice opportunity to show how you can call a JSON-returning API with Bridge and then access the data through a known object model (which is why I created a ChuckNorrisFactApiResponse class to use instead).
Without any other changes to the code we have so far, this works.. in a manner of speaking. Any time you save a message of your own, the read action that follows the save will pull back all of the messages from the MessageApi's in-memory set. So, when a read is explicitly initiated, all of the most recent data will come back.
But it would be nice if the new messages could appear in the MessageHistory even without you, as the user, saving your own messages. They could appear in the history even as you are in the process of writing your own content.
One way to do this would be to have the MessageApi raise an event whenever its message history data changes. Then, the AppContainer would listen for events from its MessageApi reference (which it has in its props, since props on a stateful component are used to provide references to the "external environment") as well as listening to events from the components that it renders.
On the one hand, this would actually make the save logic cleaner - the OnSave handler that the AppContainer passes to the MessageEditor in its props would only have to disable the form during the save and clear / re-enable it after the save, it wouldn't have to then request updated message data from the MessageApi, since it would know that the MessageApi would raise its own event when it had accepted the newly-saved message.
But, on the other hand, dealing with more event sources means that more logic is required in the AppContainer (which I want to move away from) and we no longer have the simple rendering-goes-down-the-tree and events-come-up-the-tree, now we have rendering-goes-down-the-tree and events-come-up-the-tree and events-come-from-some-other-places-too.
So I'm going to look at an alternative..
Instead of the AppContainer potentially having to deal with multiple event sources and working out how events may or may not change its state, I'm going to pull that handling of UI state into another class entirely; somewhere to store this state, that will act as a single (and very simple) event source for AppContainer. This store will have a single event that the AppContainer will subscribe to; an argument-less "OnChange" event. When the AppContainer receives an "OnChange" event from the store, it will access data that the store makes available in order to update its own state reference and thus trigger a re-render.
Since events will be handled by this store, whenever an event from the component tree is passed up to the AppContainer, the AppContainer needs to pass it along to the store (instead of trying to process the event itself). So the AppContainer could act like an event source for this new store class. And, since the store class will be dealing with processing events (such as "user has requested a new message be saved"), it will also have to deal with the MessageApi being an event source.
This is a good illustration of separation of concerns - the AppContainer used to be responsible for rendering the component tree and dealing with handling events (which is where the real complexity of an application lies). With this new plan, the AppContainer will only deal with re-rendering and the event processing is dealt with by "pure" (ie. non-UI-related) C# code. However, we could still make the "multiple event source" issue a little cleaner. The store in this example will only have two event sources (the AppContainer - from user interactions - and the MessageApi - from new Chuck Norris facts arriving), but a more complex application could result in many event sources being required.
And, sometimes, a particular store might not even want access to the full event source reference; if our store was only going to be used for dealing with data for a form that edits an existing message (and doesn't want to show a message history anywhere), then it wouldn't need a full MessageApi reference - it just needs to be able to read one message in particular to edit, be able to request a change to that message be saved and then know when that save request had been processed. For this store to say that it requires a full MessageApi would be an exaggeration - it would be claiming that it had a greater dependency than it really does.
It makes sense to have one "UI store" per page of your application, so if we were to extend this example such that the main page allowed you to record new messages and see the message history and we added a way to edit a particular message on a different screen, then we might have two stores and two top-level components and a router to handle navigation from one to the other. I don't want to jump too far ahead here, I just mean to point out that different stores may rely upon different event sources and rely on different abilities of those event sources.
So the next change that I'm going to propose is to decouple event sources from the store by having all events broadcast as messages. These are sent to some sort of message bus, which then passes them on to any interested parties. This would mean that the AppContainer would send a message to the bus whenever the user has interacted with the UI (whether this be an edit to a field or a request to save). When "SaveMessage" is called on the MessageApi then it will no longer return a Task, instead a message will be passed to the bus when the save has completed. When a new Chuck Norris fact is received by the MessageApi, it will send a message to the bus to say that new message data is available.
This would mean that the store class will only have a single "event source", which is this message bus. It will subscribe to this bus, and whenever a message is dispatched that the store is interested in then it will deal with it accordingly. If a store were to receive a message that it wasn't interested in, then it would just ignore it.
Introducing a message bus like this is a common technique to decouple areas of a system and it is another approach that makes unit testing easier later on - since the store class(es) are where the complicated event-handling logic lives, this is the code that needs the most testing. With this proposed arrangement, to test a store's logic you need only to new one up, pass it a message bus reference used solely within the current unit test, push particular messages through the bus and then verify that the store raises its OnChange event after messages when you expect it to and that the data that the store makes public is what you expect at these times. This allows all of the complicated logic to be tested with zero UI components involved and reduces the times when mocks are required since so much of the communication with the store is done via messages. (In order to test the "Save" functionality in our example app, a mock IReadAndWriteMessages would be required by our MessageWriterStore, however, so that it could call "SaveMessage" on something that doesn't actually persist data - and so that the unit test could confirm that "SaveMessage" was called with the expected data).
To summarise all of the above, we will move away slightly from the paths of communication being rendering-goes-down-the-tree and events-come-up-the-tree. Before, these paths were a continuous chain because events came up the tree and were processed and the re-render immediately went back down the tree again. Now we have rendering-goes-down-the-tree and events-come-up-the-tree but then nothing seems to happen immediately, instead the top-level component sends off a message and does nothing more.. until the store receives that message, processes it as an event (and applies any complicated logic) and then raises its OnChange event, which the top-level component receives and triggers a re-render.
I must admit, that sounds more complicated! But don't lose sight of the fact that we will side-step the complexities that multiple event sources can introduce and we will separate logic from UI, making the code more testable and making each part of it easier to reason about and thus easier to maintain and extend in the future.
(Note: This approach brings us much closer to treating everything as "asynchronous by default" - even UI changes now are implemented in a fire-and-forget manner; the top-level component sends out a message when the UI should be updated and then does nothing until it's informed by the store that it should update. While it initially feels strange to not expect to "immediately" update UI components in a synchronous manner, the advantage to async being the norm is that it's common for async code to be a bit scary in otherwise synchronous code - but here it's all the same, and not scary at all. It's also worth noting that we weren't truly updating in a synchronous manner before, since React's SetState method actually operates asynchronously - this allows React to batch updates if many occur in succession, potentially further reducing the number of times that the browser DOM actually needs to be interacted with; clever stuff!)
I must admit, at this point, that I can't take any credit for the above ideas. What I've outlined is referred to as the Flux Architecture (since different people have extracted their own variations on the concept, it might be safer to describe it as a Flux-like architecture, but let's not split hairs).
Below is the classic Flux diagram -
The message bus is referred to as the "Dispatcher", messages are known as "Actions". The "View" is the React component tree (the AppContainer listens for the "OnChange" event from the Store and re-renders when it receives it). Note that actions come not just from the View but also from outside the cycle; in our case we have messages coming from the MessageApi when new Chuck Norris facts arrive. In an application with routing, there would be actions from the router when the URL changes.
So... what's required to get from the current architecture that the example application has to a Flux-like one?
Let's start with the Dispatcher and the messages that flow through it. The first good news is that the Bridge React bindings include a Dispatcher implementation; the AppDispatcher. This has two methods:
void Receive(Action<IDispatcherAction> callback);
void Dispatch(IDispatcherAction action);
The IDispatcherAction interface is empty and is only used as a marker to identify classes as being intended for use as a Dispatcher action.
When writing in JavaScript, actions tend to be simple objects with a "type" or "actionType" property that identifies what sort of action it is. It will then have further properties, depending upon what action it needs to describe. I've taken the below example from an article (that wasn't written by me); A (little more) complex react and flux example -
AppDispatcher.dispatch({
actionType: BookConstants.MESSAGE_ADD,
message: {
color: 'green',
text: msg
}
});
However, we're writing in C# and we're going to take advantage of that! Our actions will be distinct types. When the store listens out messages, it won't compare an "actionType" string to work out what a particular action represents, instead we'll perform type comparisons and, when we find an action that we're interested in, we'll cast the current action to the matched type and then access its data in a type-safe manner.
Create a new folder in the project from Part One called "Actions". The simplest action to begin with is the action that would be raised by the AppContainer when the user has clicked the Save button -
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Actions
{
public class MessageSaveRequested : IDispatcherAction
{
public MessageDetails Message;
}
}
Previously, the AppContainer's Render method contained logic about dealing with save requests -
OnSave = async () =>
{
// Set SaveInProgress to true while the save operation is requested
SetState(new State {
Message = state.Message,
IsSaveInProgress = true,
MessageHistory = state.MessageHistory
});
await props.MessageApi.SaveMessage(state.Message);
// After the save has completed, clear the message entry form and reset
// SaveInProgress to false
SetState(new State {
Message = new MessageDetails { Title = "", Content = "" },
IsSaveInProgress = false,
MessageHistory = state.MessageHistory
});
// Then re-load the message history state and re-render when that data arrives
var allMessages = await props.MessageApi.GetMessages();
SetState(new State {
Message = state.Message,
IsSaveInProgress = state.IsSaveInProgress,
MessageHistory = allMessages
});
}
But, if all that it needs to do when a save-request bubbles up to it is send an appropriate action through the Dispatcher, then it becomes much simpler -
OnSave = () => props.Dispatcher.Dispatch(
new MessageSaveRequested { Message = state.Message }
)
I've only shown the change to the "OnSave" property, rather than show the change within the context of the complete AppContainer class because there are other things I want to rearrange at this point. Firstly, the MessageEditor "OnChange" handler also needs to be altered - as I described above, when a user interaction is expected to require a re-render, this will not result in SetState being called immediately. Instead, an action will be sent to the Dispatcher. We need to define this action, so create another class in the "Actions" folder -
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Actions
{
public class MessageEditStateChanged : IDispatcherAction
{
public MessageEditState NewState;
}
}
The other change is in the format of the data that is passed to the MessageEditor. Before, separate "Title", "Content" and "Disabled" values were passed to it and the component would do three things with that information -
These second and third things are precisely the sort of logic that should be extracted out into the store. Consequently, the MessageEditor will be changed so that it no longer takes these individual values and, instead, takes a MessageEditState that has a "Caption" string (for the legend text), "Title" and "Content" strings and validation messages for these user-entered strings. The "Disabled" property will replaced with "IsSaveInProgress" - if this is true then none of the form elements (the text inputs or the save button) should be enabled. If a save is not in progress, then the text inputs should be enabled but the save button should only be enabled if neither validation message has any content. That is arguably more logic that could be extracted, but I think that this approach will strike a good balance - keeping the component "dumb" without having to spell out every little thing to the nth degree. Add two new files to the "ViewModels" folder to define the following classes -
namespace BridgeReactTutorial.ViewModels
{
public class MessageEditState
{
public string Caption;
public TextEditState Title;
public TextEditState Content;
public bool IsSaveInProgress;
}
}
namespace BridgeReactTutorial.ViewModels
{
public class TextEditState
{
public string Text;
public string ValidationError;
}
}
Now the MessageEditor may be rewritten to the following (note that after this change, the project isn't going to compile any more - there are a bunch of other alterations that will be required until everything builds again, all of which will be covered below):
using System;
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class MessageEditor : StatelessComponent<MessageEditor.Props>
{
public MessageEditor(Props props) : base(props) { }
public override ReactElement Render()
{
var formIsInvalid =
!string.IsNullOrWhiteSpace(props.Message.Title.ValidationError) ||
!string.IsNullOrWhiteSpace(props.Message.Content.ValidationError);
return DOM.FieldSet(new FieldSetAttributes { ClassName = props.ClassName },
DOM.Legend(null, props.Message.Caption),
DOM.Span(new Attributes { ClassName = "label" }, "Title"),
new ValidatedTextInput(new ValidatedTextInput.Props
{
ClassName = "title",
Disabled = props.Message.IsSaveInProgress,
Content = props.Message.Title.Text,
OnChange = newTitle => props.OnChange(new MessageEditState
{
Title = new TextEditState { Text = newTitle },
Content = props.Message.Content
}),
ValidationMessage = props.Message.Title.ValidationError
}),
DOM.Span(new Attributes { ClassName = "label" }, "Content"),
new ValidatedTextInput(new ValidatedTextInput.Props
{
ClassName = "content",
Disabled = props.Message.IsSaveInProgress,
Content = props.Message.Content.Text,
OnChange = newContent => props.OnChange(new MessageEditState
{
Title = props.Message.Title,
Content = new TextEditState { Text = newContent },
}),
ValidationMessage = props.Message.Content.ValidationError
}),
DOM.Button(
new ButtonAttributes
{
Disabled = formIsInvalid || props.Message.IsSaveInProgress,
OnClick = e => props.OnSave()
},
"Save"
)
);
}
public class Props
{
public string ClassName;
public MessageEditState Message;
public Action<MessageEditState> OnChange;
public Action OnSave;
}
}
}
Now the AppContainer becomes much simpler -
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.ViewModels;
using BridgeReactTutorial.Stores;
namespace BridgeReactTutorial.Components
{
public class AppContainer : Component<AppContainer.Props, AppContainer.State>
{
public AppContainer(AppContainer.Props props) : base(props) { }
protected override void ComponentDidMount()
{
props.Store.Change += StoreChanged;
}
protected override void ComponentWillUnmount()
{
props.Store.Change -= StoreChanged;
}
private void StoreChanged()
{
SetState(new State
{
Message = props.Store.Message,
MessageHistory = props.Store.MessageHistory
});
}
public override ReactElement Render()
{
if (state == null)
return null;
return DOM.Div(null,
new MessageEditor(new MessageEditor.Props
{
ClassName = "message",
Message = state.Message,
OnChange = newState => props.Dispatcher.Dispatch(
new MessageEditStateChanged { NewState = newState }
),
OnSave = () => props.Dispatcher.Dispatch(
new MessageSaveRequested
{
Message = new MessageDetails
{
Title = state.Message.Title.Text,
Content = state.Message.Content.Text
}
}
)
}),
new MessageHistory(new MessageHistory.Props
{
ClassName = "history",
Messages = state.MessageHistory
})
);
}
public class Props
{
public AppDispatcher Dispatcher;
public MessageWriterStore Store;
}
public class State
{
public MessageEditState Message;
public IEnumerable<Tuple<int, MessageDetails>> MessageHistory;
}
}
}
It is now almost devoid of logic, it only exists to listen to changes from the store (which we'll define in a moment), to render components and to direct events that are passed up from these components to the Dispatcher. Note that it no longer has any dependency upon the MessageApi.
A couple of things to note - the Dispatcher and Store are both passed to the component through its props, but when the Store raises a Change event the AppContainer copies data references from the Store into its own state, meaning that all of the information that the AppContainer requires to render itself is contained within its state. This follows the "Guidelines for Stateful Components" that I wrote last time -
The AppContainer now uses some React component life cycle methods that it wasn't before - "ComponentDidMount" and "ComponentWillUnmount" - to ensure that the handler attached to the Store's Change event is correctly removed when no longer required. In our example application, the AppContainer is never "unmounted" but in a more complicated application then the top-level components may be changed based upon how the user navigates through the application. (A component is "mounted" when it's being added to the component tree and "unmounted" when it's being removed - in an application with a router, the current top-level component may be changed based upon the current route, in which case there would be top-level components being mounted and unmounted as the route changes and it is important that any event handlers that they attached be detached when they're not needed).
One final thing to note before moving on to the Store implementation is that there is no longer a "GetInitialState" implementation in the AppContainer and the "Render" method will return null if state doesn't yet have a value. "GetInitialState" was another example of logic that is better placed outside of the component classes - now the AppContainer is not responsible for having to know what its initial state should be, it just renders nothing until the Store has raised a Change request that tells the AppContainer what to display.
The child components are "dumb" as all they have to do is render according to the props data that they are provided with and now the top-level stateful component is similarly "dumb" as all it does is listen to the Store and pass the information down to dumb stateless components - and then listen for events from the child components, in order to pass the information on to the Dispatcher.
We're almost ready to talk about how to create the Store now, but first we need to adapt the MessageApi to work with the Dispatcher, rather than with Tasks.
using System;
using System.Collections.Generic;
using Bridge;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.API
{
public class MessageApi : IReadAndWriteMessages
{
private readonly AppDispatcher _dispatcher;
private readonly List<Tuple<int, MessageDetails>> _messages;
public MessageApi(AppDispatcher dispatcher)
{
if (dispatcher == null)
throw new ArgumentException("dispatcher");
_dispatcher = dispatcher;
_messages = new List<Tuple<int, MessageDetails>>();
// To further mimic a server-based API (where other people may be recording messages
// of their own), after a 10s delay a periodic task will be executed to retrieve a
// new message
Window.SetTimeout(
() => Window.SetInterval(GetChuckNorrisFact, 5000),
10000
);
}
public RequestId SaveMessage(MessageDetails message)
{
return SaveMessage(message, optionalSaveCompletedCallback: null);
}
private RequestId SaveMessage(
MessageDetails message,
Action optionalSaveCompletedCallback)
{
if (message == null)
throw new ArgumentNullException("message");
if (string.IsNullOrWhiteSpace(message.Title))
throw new ArgumentException("A title value must be provided");
if (string.IsNullOrWhiteSpace(message.Content))
throw new ArgumentException("A content value must be provided");
var requestId = new RequestId();
Window.SetTimeout(
() =>
{
_messages.Add(Tuple.Create(_messages.Count, message));
_dispatcher.Dispatch(
new MessageSaveSucceeded { RequestId = requestId }
);
if (optionalSaveCompletedCallback != null)
optionalSaveCompletedCallback();
},
1000 // Simulate a roundtrip to the server
);
return requestId;
}
public RequestId GetMessages()
{
// ToArray is used to return a clone of the message set - otherwise, the caller would
// end up with a list that is updated when the internal reference within this class
// is updated (which sounds convenient but it's not the behaviour that would be
// exhibited if this was really persisting messages to a server somewhere)
var requestId = new RequestId();
Window.SetTimeout(
() => _dispatcher.Dispatch(new MessageHistoryUpdated
{
RequestId = requestId,
Messages = _messages.ToArray()
}),
1000 // Simulate a roundtrip to the server
);
return requestId;
}
private void GetChuckNorrisFact()
{
var request = new XMLHttpRequest();
request.ResponseType = XMLHttpRequestResponseType.Json;
request.OnReadyStateChange = () =>
{
if (request.ReadyState != AjaxReadyState.Done)
return;
if ((request.Status == 200) || (request.Status == 304))
{
try
{
var apiResponse = (ChuckNorrisFactApiResponse)request.Response;
if ((apiResponse.Type == "success")
&& (apiResponse.Value != null)
&& !string.IsNullOrWhiteSpace(apiResponse.Value.Joke))
{
// The Chuck Norris Facts API (http://www.icndb.com/api/) returns strings
// html-encoded, so they need decoding before be wrapped up in a
// MessageDetails instance
// - Note: After the save has been processed, GetMessages is called so
// that a MessageHistoryUpdate action is dispatched
SaveMessage(
new MessageDetails
{
Title = "Fact",
Content = HtmlDecode(apiResponse.Value.Joke)
},
() => GetMessages()
);
return;
}
}
catch
{
// Ignore any error and drop through to the fallback message-generator below
}
}
SaveMessage(new MessageDetails
{
Title = "Fact",
Content = "API call failed when polling for server content :("
});
};
request.Open("GET", "http://api.icndb.com/jokes/random");
request.Send();
}
private string HtmlDecode(string value)
{
if (value == null)
throw new ArgumentNullException("value");
var wrapper = Document.CreateElement("div");
wrapper.InnerHTML = value;
return wrapper.TextContent;
}
[IgnoreCast]
private class ChuckNorrisFactApiResponse
{
public extern string Type { [Template("type")] get; }
public extern FactDetails Value { [Template("value")] get; }
[IgnoreCast]
public class FactDetails
{
public extern int Id { [Template("id")] get; }
public extern string Joke { [Template("joke")]get; }
}
}
}
}
This means that the IReadAndWriteMessages interface no longer returns Tasks -
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.API
{
public interface IReadAndWriteMessages
{
RequestId SaveMessage(MessageDetails message);
RequestId GetMessages();
}
}
Each of the two methods now return a "RequestId". This is a unique identifier that will be used to tie future actions back to specific calls to the "GetMessages" or "SaveMessage" methods. When a user requests that a message be saved in our sample app, the AppContainer sends a MessageSaveRequested action through the Dispatcher. The store will receive this action from the Dispatcher and use the message data in it to call "SaveMessage", which will give the Store a unique RequestId. After the MessageApi has completed the save, it will raise a MessageSaveSucceeded action that has a "RequestId" value, the same RequestId as "SaveMessage" returned. This is how the Store knows that the save which succeeded was, in fact, the save that it initiated. In the app here, there wouldn't be any doubt since there is only one place where a new message may be saved, but in a more complicated application it's feasible that there may be multiple components that could initiate a save and it would be important to be able to be able to trace any "save succeeded" notification back to where it came from.
The RequestId has a nice feature in that two instances may be compared to determine which is most recent - this could be applicable to an application like our example because, shortly, the message history will be updated after a user has created a new message and it will be automatically updated when a new Chuck Norris fact appears. It's not too difficult to imagine that there could be a race condition that occurs when two "new message history" actions are received by the Store (one from the user-saves-message-and-then-fresh-history-is-automatically-retrieved-after-the-save-is-completed process and one from a new Chuck Norris fact arriving). In the real world, with unpredictable server and network times, it's possible for "Server Call A" to start before "Server Call B" but for "Server Call A" to finish after "Server Call B" - in this case we want the "new message history" from "Server Call B", since it should be more recent, but the data from "Server Call A" arrives after it and we need to know which result is freshest. If each action that relates to "new data arrived from API" has a RequestId then we can compare the two values using the "ComesAfter" function, allow us to ignore the stale data.
The RequestId implementation is fairly simple (add a new "RequestId.cs" file to the "API" folder and paste in the following) -
using System;
namespace BridgeReactTutorial.API
{
public class RequestId
{
private static DateTime _timeOfLastId = DateTime.MinValue;
private static int _offsetOfLastId = 0;
private readonly DateTime _requestTime;
private readonly int _requestOffset;
public RequestId()
{
_requestTime = DateTime.Now;
if (_timeOfLastId < _requestTime)
{
_offsetOfLastId = 0;
_timeOfLastId = _requestTime;
}
else
_offsetOfLastId++;
_requestOffset = _offsetOfLastId;
}
public bool ComesAfter(RequestId other)
{
if (other == null)
throw new ArgumentNullException("other");
if (_requestTime == other._requestTime)
return _requestOffset > other._requestOffset;
return (_requestTime > other._requestTime);
}
}
}
In the new MessageApi code above, two actions were referenced that haven't been defined yet, so add two more classes to the "Actions" folder for the following:
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Actions
{
public class MessageHistoryUpdated : IDispatcherAction
{
public RequestId RequestId;
public IEnumerable<Tuple<int, MessageDetails>> Messages;
}
}
using Bridge.React;
using BridgeReactTutorial.API;
namespace BridgeReactTutorial.Actions
{
public class MessageSaveSucceeded : IDispatcherAction
{
public RequestId RequestId;
}
}
(Note: A MessageHistoryUpdated will be emitted after a "GetMessages" call is made but one will also be emitted every time that a new Chuck Norris fact arrives).
While we're adding actions, we're going to need a StoreInitialised action class (I'll talk about this more later on, for now just add the following class to the "Actions" folder):
using Bridge.React;
using BridgeReactTutorial.API;
namespace BridgeReactTutorial.Actions
{
public class StoreInitialised : IDispatcherAction
{
public object Store;
}
}
Now, finally, we so create a Store.
Create a new folder in the project root called "Stores" and add a new class file to it; "MessageWriterStore.cs" -
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Stores
{
public class MessageWriterStore
{
private RequestId _saveActionRequestId, _lastDataUpdatedRequestId;
public MessageWriterStore(IReadAndWriteMessages messageApi, AppDispatcher dispatcher)
{
if (messageApi == null)
throw new ArgumentNullException("messageApi");
if (dispatcher == null)
throw new ArgumentNullException("dispatcher");
Message = GetInitialMessageEditState();
MessageHistory = new Tuple<int, MessageDetails>[0];
dispatcher.Receive(action =>
{
if (action is StoreInitialised)
{
var storeInitialised = (StoreInitialised)action;
if (storeInitialised.Store == this)
OnChange();
}
else if (action is MessageEditStateChanged)
{
var messageEditStateChanged = (MessageEditStateChanged)action;
Message = messageEditStateChanged.NewState;
ValidateMessage(Message);
OnChange();
}
else if (action is MessageSaveRequested)
{
var messageSaveRequested = (MessageSaveRequested)action;
_saveActionRequestId = messageApi.SaveMessage(messageSaveRequested.Message);
Message.IsSaveInProgress = true;
OnChange();
}
else if (action is MessageSaveSucceeded)
{
var messageSaveSucceeded = (MessageSaveSucceeded)action;
if (messageSaveSucceeded.RequestId == _saveActionRequestId)
{
_saveActionRequestId = null;
Message = GetInitialMessageEditState();
OnChange();
_lastDataUpdatedRequestId = messageApi.GetMessages();
}
}
else if (action is MessageHistoryUpdated)
{
var messageHistoryUpdated = (MessageHistoryUpdated)action;
if ((_lastDataUpdatedRequestId == null)
|| (_lastDataUpdatedRequestId == messageHistoryUpdated.RequestId)
|| messageHistoryUpdated.RequestId.ComesAfter(_lastDataUpdatedRequestId))
{
_lastDataUpdatedRequestId = messageHistoryUpdated.RequestId;
MessageHistory = messageHistoryUpdated.Messages;
OnChange();
}
}
});
}
public event Action Change;
public MessageEditState Message;
public IEnumerable<Tuple<int, MessageDetails>> MessageHistory;
private MessageEditState GetInitialMessageEditState()
{
// Note: By using the ValidateMessage here, we don't need to duplicate the "Untitled"
// string that should be used for the Caption value when the UI is first rendered
// or when the user has entered some Title content but then deleted it again.
// Similarly, we avoid having to repeat the validation messages that should be
// displayed when the form is empty, since they will be set by ValidateMessage.
var blankMessage = new MessageEditState
{
Caption = "",
Title = new TextEditState { Text = "" },
Content = new TextEditState { Text = "" },
IsSaveInProgress = false
};
ValidateMessage(blankMessage);
return blankMessage;
}
private void ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
message.Caption = string.IsNullOrWhiteSpace(message.Title.Text)
? "Untitled"
: message.Title.Text.Trim();
message.Title.ValidationError = string.IsNullOrWhiteSpace(message.Title.Text)
? "Must enter a title"
: null;
message.Content.ValidationError = string.IsNullOrWhiteSpace(message.Content.Text)
? "Must enter message content"
: null;
}
private void OnChange()
{
if (Change != null)
Change();
}
}
}
There's really nothing very complicated here at all. What I like is that all of the logic that was previously ensconced within component classes is now in a C# class which has zero UI-based dependencies. What I also like is how clear the logic is, it's very easy to read through how the various actions are matched and to see precisely what state changes will occur. In fact, the MessageWriterStore is just a simple state machine where each transition is based upon the action that it receives from the Dispatcher. It's reassuring that the Flux architecture is based upon these time-tested computer science devices; the message bus and state machine - while it might take a little while to internalise how to write applications based around this "one-way binding" / "one-way message passing" arrangement, once it clicks it feels very natural.
Having the core application logic in classes like this really helps ensure that code will have those two great properties that I will keep repeating through this series - that it's easy to reason about and that it's easy to test. It's easy to reason about as it's easy to see how user (and server) actions flow through the code, there are few surprises. It's easy to test because there are few dependencies - to test anything in the MessageWriterStore, each unit test would need to provide an AppDispatcher instance and a mock IReadAndWriteMessages implementation, it would then push one or more messages through the Dispatcher and confirm that the public MessageWriterStore state matches expectations at the end. For example, to test the "Content" text input validation, you would play back a MessageEditStateChanged action with a blank "Content" string in the MessageEditState and ensure that the expected validation message text was present in the "Message" reference of the MessageWriterStore afterwards.
There are a couple of minor things that I'm not so keen about in the code above. Firstly, there's the laborious type-checking and casting that is required when matching the actions. Secondly, there's the duplication on calling "OnChange" whenever an action is matched. Thirdly, the logic around RequestId comparison when a MessageHistoryUpdated is matched is a bit clumsy.
For that third point, add a new file "RequestIdExtensions.cs" to the "API" folder with the following content -
using System;
namespace BridgeReactTutorial.API
{
public static class RequestIdExtensions
{
public static bool IsEqualToOrComesAfter(this RequestId source, RequestId other)
{
if (source == null)
throw new ArgumentNullException("source");
// If the "other" reference is no-RequestId then the "source" may be considered to
// come after it
if (other == null)
return true;
return (source == other) || source.ComesAfter(other);
}
}
}
And for the first two points, we can use some extension methods which are included in the Bridge / React bindings -
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Stores
{
public class MessageWriterStore
{
private RequestId _saveActionRequestId, _lastDataUpdatedRequestId;
public MessageWriterStore(IReadAndWriteMessages messageApi, AppDispatcher dispatcher)
{
if (messageApi == null)
throw new ArgumentNullException("messageApi");
if (dispatcher == null)
throw new ArgumentNullException("dispatcher");
Message = GetInitialMessageEditState();
MessageHistory = new Tuple<int, MessageDetails>[0];
dispatcher.Receive(a => a
.If<StoreInitialised>(
condition: action => (action.Store == this),
work: action => { }
)
.Else<MessageEditStateChanged>(action =>
{
Message = action.NewState;
ValidateMessage(Message);
})
.Else<MessageSaveRequested>(action =>
{
_saveActionRequestId = messageApi.SaveMessage(action.Message);
Message.IsSaveInProgress = true;
})
.Else<MessageSaveSucceeded>(
condition: action => (action.RequestId == _saveActionRequestId),
work: action =>
{
_saveActionRequestId = null;
Message = GetInitialMessageEditState();
_lastDataUpdatedRequestId = messageApi.GetMessages();
}
)
.Else<MessageHistoryUpdated>(
condition: action =>
action.RequestId.IsEqualToOrComesAfter(_lastDataUpdatedRequestId),
work: action =>
{
_lastDataUpdatedRequestId = action.RequestId;
MessageHistory = action.Messages;
}
)
.IfAnyMatched(OnChange)
);
}
public event Action Change;
public MessageEditState Message;
public IEnumerable<Tuple<int, MessageDetails>> MessageHistory;
private MessageEditState GetInitialMessageEditState()
{
// Note: By using the ValidateMessage here, we don't need to duplicate the "Untitled"
// string that should be used for the Caption value when the UI is first rendered
// or when the user has entered some Title content but then deleted it again.
// Similarly, we avoid having to repeat the validation messages that should be
// displayed when the form is empty, since they will be set by ValidateMessage.
var blankMessage = new MessageEditState
{
Caption = "",
Title = new TextEditState { Text = "" },
Content = new TextEditState { Text = "" },
IsSaveInProgress = false
};
ValidateMessage(blankMessage);
return blankMessage;
}
private void ValidateMessage(MessageEditState message)
{
if (message == null)
throw new ArgumentNullException("message");
message.Caption = string.IsNullOrWhiteSpace(message.Title.Text)
? "Untitled"
: message.Title.Text.Trim();
message.Title.ValidationError = string.IsNullOrWhiteSpace(message.Title.Text)
? "Must enter a title"
: null;
message.Content.ValidationError = string.IsNullOrWhiteSpace(message.Content.Text)
? "Must enter message content"
: null;
}
private void OnChange()
{
if (Change != null)
Change();
}
}
}
Hopefully it's clear enough how they work. The "If" and the "Else" functions both have a generic type parameter for the kind of action to match and may be called with a single "work" argument (meaning "do this if the action type is matched") or two arguments; "condition" and "work" (where "condition" looks at the action, typed to match the generic type parameter, and returns true or false depending upon whether the action should be considered or ignored). The "condition" argument is most clearly illustrated by the MessageHistoryUpdated, it ensures that any stale MessageHistoryUpdated action will be ignored. The "work" implementation for StoreInitialised is empty because all that is required when a StoreInitialised action is received (that targets the current store) is to call "OnChange" and the "IfAnyMatched" extension method calls "OnChange" if any of the actions are matched.
There's just one final thing to do now in order to make the application compile again, the entry point logic in App.cs needs updating -
using System.Linq;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.API;
using BridgeReactTutorial.Components;
using BridgeReactTutorial.Stores;
namespace BridgeReactTutorial
{
public class App
{
[Ready]
public static void Go()
{
var container = Document.GetElementById("main");
container.ClassName = string.Join(
" ",
container.ClassName.Split().Where(c => c != "loading")
);
var dispatcher = new AppDispatcher();
var messageApi = new MessageApi(dispatcher);
var store = new MessageWriterStore(messageApi, dispatcher);
React.Render(
new AppContainer(new AppContainer.Props
{
Dispatcher = dispatcher,
Store = store
}),
container
);
dispatcher.Dispatch(new StoreInitialised { Store = store });
}
}
}
Both the MessageWriterStore and the AppContainer need a reference to a shared Dispatcher, MessageWriterStore needs a Message API wrapper to talk to and the AppContainer needs a reference to the Store. In a lot of the Flux articles that I read at first, the Dispatcher was a static reference that everything had access to - but I much prefer this arrangement, where the places that explicitly need it (ie. the Stores and the top-level components) have it passed in as a constructor argument or through props. This makes a class' requirements much more explicit - if a class has implicit dependencies then it's more difficult to tell at a glance how it works. And not having a static Dispatcher means that unit testing is simpler, since there is no implicit shared state between elements within an application.
The only part of this code that may not be very intuitive is the need to send a StoreInitialised action to the Dispatcher immediately after setting up all of the references. This is required before the AppContainer won't render anything until it processes its first "Change" event from the MessageWriterStore (because, until that point, the AppContainer's state reference will be null). When the Store receives the StoreInitialised action, it will raise its "Change" event and the AppContainer will perform its first "real" render. If this was an application with a routing element, with a Store per page / form, then it would seem natural for the router to raise a StoreInitialised action for the Store that should be active for the current route (it is just a little odd-looking in a simple application like the example here unless you know why it is necessary).
With that, the change in architecture is complete. Hopefully it's easy to envisage how further functionality is enabled by adding further specialised components and communicating using different action types. Each action is handled in a very clear way in the Store(s) and so the overall complexity should (approximately) grow linearly with the essential complexity, rather than exponentially (which is what tends to happens a lot with more haphazard architectures, or where you have the "star-shaped event handling" that I described earlier).
The only adjustment that I'd like to make at this point is to the actions themselves - if there's a variation of the MessageEditStateChanged, MessageSaveRequested and MessageSaveSucceeded actions required for every kind of form where a user creates / edits data and tries to save it, then there's going to be a lot of action classes that are basically the same.
This seems like a perfect case for C# generics - a single generic class may be used to represent many types of user edit action without sacrificing type safety. Rename the "MessageEditStateChanged.cs" action class to "UserEditRequested.cs" and replace the content, which is currently:
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Actions
{
public class MessageEditStateChanged : IDispatcherAction
{
public MessageEditState NewState;
}
}
With this:
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Actions
{
public class UserEditRequested<T> : IDispatcherAction
{
public T NewState;
}
public static class UserEditRequested
{
public static UserEditRequested<T> For<T>(T newState)
{
return new UserEditRequested<T> { NewState = newState };
}
}
}
Now, anywhere that there is a reference to MessageEditStateChanged, you will need to change it to be a UserEditRequested<MessageEditState>.
The non-generic "For" function is just for convenience, it allows you to create a new UserEditRequested<T> instance without writing out the type of "T" - it will be inferred by the type of the "newState" reference passed for it. The "OnChange" lambda set on the MessageEditor props was previously
OnChange = newState => props.Dispatcher.Dispatch(
new MessageEditStateChanged { NewState = newState }
)
but should now become
OnChange = newState => props.Dispatcher.Dispatch(
UserEditRequested.For(newState)
)
(Since "newState" is a MessageEditState instance, the action that will be raised will be a UserEditRequested<MessageEditState>).
Now, the action-matching code in the MessageWriteStore needs to change from
.Else<MessageEditStateChanged>(action =>
{
Message = action.NewState;
ValidateMessage(Message);
})
to
.Else<UserEditRequested<MessageEditState>>(action =>
{
Message = action.NewState;
ValidateMessage(Message);
})
Similar changes should be made, so that "MessageSaveRequested.cs" is replaced with
using Bridge.React;
namespace BridgeReactTutorial.Actions
{
public class SaveRequested<T> : IDispatcherAction
{
public T Data;
}
public static class SaveRequested
{
public static SaveRequested<T> For<T>(T data)
{
return new SaveRequested<T> { Data = data };
}
}
}
And "MessageSaveSucceeded.cs" is replaced with
using Bridge.React;
using BridgeReactTutorial.API;
namespace BridgeReactTutorial.Actions
{
public class SaveSucceeded : IDispatcherAction
{
public RequestId RequestId;
}
}
And, finally, "MessageHistoryUpdated.cs" replaced with
using Bridge.React;
using BridgeReactTutorial.API;
namespace BridgeReactTutorial.Actions
{
public class DataUpdated<T> : IDispatcherAction
{
public RequestId RequestId;
public T Data;
}
public static class DataUpdated
{
public static DataUpdated<T> For<T>(RequestId requestId, T data)
{
return new DataUpdated<T> { RequestId = requestId, Data = data };
}
}
}
(Note that SaveSucceeded is not a generic class because the only information that it contains is the RequestId that corresponds to the save operation that it indicates has completed).
The action type-matching that occurs in the MessageWriteStore now needs to be changed to:
dispatcher.Receive(a => a
.If<StoreInitialised>(
condition: action => (action.Store == this),
work: action => { }
)
.Else<UserEditRequested<MessageEditState>>(action =>
{
Message = action.NewState;
ValidateMessage(Message);
})
.Else<SaveRequested<MessageDetails>>(action =>
{
_saveActionRequestId = messageApi.SaveMessage(action.Data);
Message.IsSaveInProgress = true;
})
.Else<SaveSucceeded>(
condition: action => (action.RequestId == _saveActionRequestId),
work: action =>
{
_saveActionRequestId = null;
Message = GetInitialMessageEditState();
_lastDataUpdatedRequestId = messageApi.GetMessages();
}
)
.Else<DataUpdated<IEnumerable<Tuple<int, MessageDetails>>>>(
condition: action =>
action.RequestId.IsEqualToOrComesAfter(_lastDataUpdatedRequestId),
work: action =>
{
_lastDataUpdatedRequestId = action.RequestId;
MessageHistory = action.Data;
}
)
.IfAnyMatched(OnChange)
);
The AppContainer now becomes:
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.ViewModels;
using BridgeReactTutorial.Stores;
namespace BridgeReactTutorial.Components
{
public class AppContainer : Component<AppContainer.Props, AppContainer.State>
{
public AppContainer(AppContainer.Props props) : base(props) { }
protected override void ComponentDidMount()
{
props.Store.Change += StoreChanged;
}
protected override void ComponentWillUnmount()
{
props.Store.Change -= StoreChanged;
}
private void StoreChanged()
{
SetState(new State
{
Message = props.Store.Message,
MessageHistory = props.Store.MessageHistory
});
}
public override ReactElement Render()
{
if (state == null)
return null;
return DOM.Div(null,
new MessageEditor(new MessageEditor.Props
{
ClassName = "message",
Message = state.Message,
OnChange = newState => props.Dispatcher.Dispatch(
UserEditRequested.For(newState)
),
OnSave = () => props.Dispatcher.Dispatch(
SaveRequested.For(new MessageDetails
{
Title = state.Message.Title.Text,
Content = state.Message.Content.Text
})
)
}),
new MessageHistory(new MessageHistory.Props
{
ClassName = "history",
Messages = state.MessageHistory
})
);
}
public class Props
{
public AppDispatcher Dispatcher;
public MessageWriterStore Store;
}
public class State
{
public MessageEditState Message;
public IEnumerable<Tuple<int, MessageDetails>> MessageHistory;
}
}
}
And the MessageApi:
using System;
using System.Collections.Generic;
using System.Linq;
using Bridge;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.Actions;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.API
{
public class MessageApi : IReadAndWriteMessages
{
private readonly AppDispatcher _dispatcher;
private readonly List<Tuple<int, MessageDetails>> _messages;
public MessageApi(AppDispatcher dispatcher)
{
if (dispatcher == null)
throw new ArgumentException("dispatcher");
_dispatcher = dispatcher;
_messages = new List<Tuple<int, MessageDetails>>();
// To further mimic a server-based API (where other people may be recording messages
// of their own), after a 10s delay a periodic task will be executed to retrieve a
// new message
Window.SetTimeout(
() => Window.SetInterval(GetChuckNorrisFact, 5000),
10000
);
}
public RequestId SaveMessage(MessageDetails message)
{
return SaveMessage(message, optionalSaveCompletedCallback: null);
}
private RequestId SaveMessage(MessageDetails message, Action optionalSaveCompletedCallback)
{
if (message == null)
throw new ArgumentNullException("message");
if (string.IsNullOrWhiteSpace(message.Title))
throw new ArgumentException("A title value must be provided");
if (string.IsNullOrWhiteSpace(message.Content))
throw new ArgumentException("A content value must be provided");
var requestId = new RequestId();
Window.SetTimeout(
() =>
{
_messages.Add(Tuple.Create(_messages.Count, message));
_dispatcher.Dispatch(new SaveSucceeded { RequestId = requestId });
if (optionalSaveCompletedCallback != null)
optionalSaveCompletedCallback();
},
1000 // Simulate a roundtrip to the server
);
return requestId;
}
public RequestId GetMessages()
{
// ToArray is used to return a clone of the message set - otherwise, the caller would
// end up with a list that is updated when the internal reference within this class
// is updated (which sounds convenient but it's not the behaviour that would be
// exhibited if this was really persisting messages to a server somewhere).. and
// then AsEnumerable() is required since the store checks for an action of type
// DataUpdated<IEnumerable<Tuple<int, MessageDetails>>>>
var requestId = new RequestId();
Window.SetTimeout(
() => _dispatcher.Dispatch(DataUpdated.For(requestId, _messages.ToArray().AsEnumerable())),
1000 // Simulate a roundtrip to the server
);
return requestId;
}
private void GetChuckNorrisFact()
{
var request = new XMLHttpRequest();
request.ResponseType = XMLHttpRequestResponseType.Json;
request.OnReadyStateChange = () =>
{
if (request.ReadyState != AjaxReadyState.Done)
return;
if ((request.Status == 200) || (request.Status == 304))
{
try
{
var apiResponse = (ChuckNorrisFactApiResponse)request.Response;
if ((apiResponse.Type == "success")
&& (apiResponse.Value != null)
&& !string.IsNullOrWhiteSpace(apiResponse.Value.Joke))
{
// The Chuck Norris Facts API (http://www.icndb.com/api/) returns strings
// html-encoded, so they need decoding before be wrapped up in a
// MessageDetails instance
// - Note: After the save has been processed, GetMessages is called so
// that a MessageHistoryUpdate action is dispatched
SaveMessage(
new MessageDetails
{
Title = "Fact",
Content = HtmlDecode(apiResponse.Value.Joke)
},
() => GetMessages()
);
return;
}
}
catch
{
// Ignore any error and drop through to the fallback message-generator below
}
}
SaveMessage(new MessageDetails
{
Title = "Fact",
Content = "API call failed when polling for server content :("
});
};
request.Open("GET", "http://api.icndb.com/jokes/random");
request.Send();
}
private string HtmlDecode(string value)
{
if (value == null)
throw new ArgumentNullException("value");
var wrapper = Document.CreateElement("div");
wrapper.InnerHTML = value;
return wrapper.TextContent;
}
[IgnoreCast]
private class ChuckNorrisFactApiResponse
{
public extern string Type { [Template("type")] get; }
public extern FactDetails Value { [Template("value")] get; }
[IgnoreCast]
public class FactDetails
{
public extern int Id { [Template("id")] get; }
public extern string Joke { [Template("joke")]get; }
}
}
}
}
(I contemplated leaving it as an exercise for the reader to change all of the action instantiation code to use the new generic classes, but with so much code in this post already I thought I might as well go whole-hog and include the final version of the everything!)
No type-safety has been lost in this refactor but, hopefully, it's clear how action classes can scale with an application's complexity. Needing a lot of new actions every time that a new section is added to an application would be no fun and would just add to the code churn required, so being able to reuse actions like this is a boon.
We've covered a lot of ground here, so I'm going to draw things to a close. Flux is a big deal and I've read plenty of articles that make it sound mind-bending and difficult to grasp. Hopefully this has explored why it makes sense as much as how to think in terms of it (and write code in the style of it!).
Next time, now that React and Flux are dealt with, I want to look at how we can go further in the quest to make code easier to reason about and to test - there is a lot more that we can do with the C# type system to really express intent, requirements and limitations and I strongly believe that doing so will lead to higher quality code. As a side benefit, I'll also be showing how to make a simple tweak to components that will offer potentially huge performance benefits to highly-complex / deeply-nested UIs. I really think that using C# and Bridge can be more than just a possibility for writing React applications, it can be one of the best ways to do so!
Posted at 22:50
15 March 2016
(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! :)
I'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.
(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).
I'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).
All 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:
As more messages are written, more entries will appear in the "Message History". Seems simple.
Before 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.
Almost all of the time, components that you use will be "controlled components" -
A Controlled component does not maintain its own internal state; the component renders purely based on props.
This 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.
This 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".
This 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).
The 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 -
(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).
public class TextInput : Component<TextInput.Props, TextInput.State>
{
public TextInput(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
Type = InputType.Text,
Value = (state == null) ? props.InitialValue : state.Value,
OnChange = ev =>
{
var newValue = ev.CurrentTarget.Value;
SetState(new State { Value = newValue });
props.OnChange(newValue);
}
});
}
public class Props
{
public string InitialValue;
public Action<string> OnChange;
}
public class State
{
public string Value;
}
}
The 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).
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).
Partly 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.
Another 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 -
If 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.
In 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) -
public class TextInput : StatelessComponent<TextInput.Props>
{
public TextInput(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
Type = InputType.Text,
Value = props.Value,
OnChange = ev => props.OnChange(ev.CurrentTarget.Value)
});
}
public class Props
{
public string Value;
public Action<string> OnChange;
}
}
The component code is much more succinct this way, as well as helping us avoid the nightmare scenario described above.
It does leave one big question, though.. if these components don't update themselves, then what does?
Answer: 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.
In the sample application here -
The component hierarchy will look something like this:
AppContainer
MessageEditor
Span ("Title")
ValidatedTextInput
Input
Span ("Content")
ValidatedTextInput
Input
Button
MessageHistory
Div
Span (Message Title)
Span (Message Content)
Div
Span (Message Title)
Span (Message Content)
The 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.
When 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<InputElement> callback that the input raises in a simpler Action<string>). 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).
When 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.
The 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.
I'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.
The 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).
(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).
I 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.
Open 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.
The 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:
{
"output": "Bridge/output",
"combineScripts": true
}
This 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.
Now create an empty html file in the project root called "demo.html" and paste in the following:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Bridge.React Tutorial</title>
<link rel="Stylesheet" type="text/css" href="styles.css" media="screen" />
</head>
<body>
<noscript>JavaScript is required</noscript>
<div id="main" class="loading">Loading..</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js"></script>
<script src="Bridge/output/BridgeReactTutorial.js"></script>
</body>
</html>
(If you called your project something other than "BridgeReactTutorial" then you might have to change the filename in that last script tag).
This 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.
When you created the class library project, a Class1.cs file will have been added to the project. Change its contents -
using System.Linq;
using Bridge.Html5;
using Bridge.React;
namespace BridgeReactTutorial
{
public class Class1
{
[Ready]
public static void Main()
{
var container = Document.GetElementById("main");
container.ClassName = string.Join(
" ",
container.ClassName.Split().Where(c => c != "loading")
);
React.Render(
DOM.Div(new Attributes { ClassName = "welcome" }, "Hi!"),
container
);
}
}
}
Build 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!
There 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-perceptible 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).
To 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) -
/* http://meyerweb.com/eric/tools/css/reset/ v2.0b1 | 201101 NOTE: WORK IN PROGRESS
USE WITH CAUTION AND TEST WITH ABANDON */
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote,
pre,a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s,
samp, small, strike, strong, sub, sup, tt, var,b, u, i, center, dl, dt, dd, ol, ul,
li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu,
nav, section, summary, time, mark, audio, video
{
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav,
section { display: block; }
body { line-height: 1; }
ol, ul { list-style: none; }
blockquote, q { quotes: none; }
blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; }
/* remember to highlight inserts somehow! */ ins { text-decoration: none; }
del { text-decoration: line-through; }
table { border-collapse: collapse; border-spacing: 0; }
div#main.loading { color: #f1f1f1; }
At 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.
That's the theory and the project scaffolding out of the way. Now to create a form that actually does something.
We'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 -
using System;
using Bridge.Html5;
using Bridge.React;
namespace BridgeReactTutorial.Components
{
public class TextInput : StatelessComponent<TextInput.Props>
{
public TextInput(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
Type = InputType.Text,
ClassName = props.ClassName,
Value = props.Content,
OnChange = e => props.OnChange(e.CurrentTarget.Value)
});
}
public class Props
{
public string ClassName;
public string Content;
public Action<string> OnChange;
}
}
}
(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).
Now 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 -
namespace BridgeReactTutorial.ViewModels
{
public class MessageDetails
{
public string Title;
public string Content;
}
}
Now add another file to the "Components" folder; "MessageEditor.cs" and paste in this:
using System;
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class MessageEditor : StatelessComponent<MessageEditor.Props>
{
public MessageEditor(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.FieldSet(new FieldSetAttributes { ClassName = props.ClassName },
DOM.Legend(null, string.IsNullOrWhiteSpace(props.Title) ? "Untitled" : props.Title),
DOM.Span(new Attributes { ClassName = "label" }, "Title"),
new TextInput(new TextInput.Props
{
ClassName = "title",
Content = props.Title,
OnChange = newTitle => props.OnChange(new MessageDetails
{
Title = newTitle,
Content = props.Content
})
}),
DOM.Span(new Attributes { ClassName = "label" }, "Content"),
new TextInput(new TextInput.Props
{
ClassName = "content",
Content = props.Content,
OnChange = newContent => props.OnChange(new MessageDetails
{
Title = props.Title,
Content = newContent
})
})
);
}
public class Props
{
public string ClassName;
public string Title;
public string Content;
public Action<MessageDetails> OnChange;
}
}
}
Now things are getting interesting!
This 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 TextInputs raises an on-change event, the MessageEditor raises its on-change events with a new MessageDetails instance.
Note that there's no validation yet. We'll get this rough version working first and then add that later.
There 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" -
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class AppContainer : Component<object, AppContainer.State>
{
public AppContainer() : base(null) { }
protected override State GetInitialState()
{
return new State
{
Message = new MessageDetails { Title = "", Content = "" }
};
}
public override ReactElement Render()
{
return new MessageEditor(new MessageEditor.Props
{
ClassName = "message",
Title = state.Message.Title,
Content = state.Message.Content,
OnChange = newMessage => SetState(new State { Message = newMessage })
});
}
public class State
{
public MessageDetails Message;
}
}
}
This 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<TProps, TState> base class is just "object".
When 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.
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.
The 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" -
using System.Linq;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.Components;
namespace BridgeReactTutorial
{
public class App
{
[Ready]
public static void Go()
{
var container = Document.GetElementById("main");
container.ClassName = string.Join(
" ",
container.ClassName.Split().Where(c => c != "loading")
);
React.Render(new AppContainer(), container);
}
}
}
All that's required now is to make it look a little nicer when you view "demo.html", so add the following to "styles.css" -
body
{
font-family: 'Segoe UI';
padding: 8px;
}
fieldset
{
padding: 8px;
border: 1px solid #f1f1f1;
border-radius: 4px;
}
fieldset legend
{
color: blue;
padding: 0 8px;
}
fieldset.message span.label { padding: 0 8px; }
That'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.
I'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!
While 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 artificial delays to mimic server-calling time.
Create a new folder in the project root called "API" and add a new .cs file "IReadAndWriteMessages.cs", the contents of which should be:
using System.Threading.Tasks;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.API
{
public interface IReadAndWriteMessages
{
Task SaveMessage(MessageDetails message);
}
}
We'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.
Add another file into the "API" folder, "MessageApi.cs" -
using System;
using System.Threading.Tasks;
using Bridge.Html5;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.API
{
public class MessageApi : IReadAndWriteMessages
{
public Task SaveMessage(MessageDetails message)
{
if (message == null)
throw new ArgumentNullException("message");
if (string.IsNullOrWhiteSpace(message.Title))
throw new ArgumentException("A title value must be provided");
if (string.IsNullOrWhiteSpace(message.Content))
throw new ArgumentException("A content value must be provided");
var task = new Task<object>(null);
Window.SetTimeout(
() => task.Complete(),
1000 // Simulate a roundtrip to the server
);
return task;
}
}
}
Bridge 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.
In 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.
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 instantaneously 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.
The MessageEditor should now looks like this:
using System;
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class MessageEditor : StatelessComponent<MessageEditor.Props>
{
public MessageEditor(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.FieldSet(new FieldSetAttributes { ClassName = props.ClassName },
DOM.Legend(null, string.IsNullOrWhiteSpace(props.Title) ? "Untitled" : props.Title),
DOM.Span(new Attributes { ClassName = "label" }, "Title"),
new TextInput(new TextInput.Props
{
ClassName = "title",
Disabled = props.Disabled,
Content = props.Title,
OnChange = newTitle => props.OnChange(new MessageDetails
{
Title = newTitle,
Content = props.Content
})
}),
DOM.Span(new Attributes { ClassName = "label" }, "Content"),
new TextInput(new TextInput.Props
{
ClassName = "content",
Disabled = props.Disabled,
Content = props.Content,
OnChange = newContent => props.OnChange(new MessageDetails
{
Title = props.Title,
Content = newContent
})
}),
DOM.Button(
new ButtonAttributes { Disabled = props.Disabled, OnClick = e => props.OnSave() },
"Save"
)
);
}
public class Props
{
public string ClassName;
public string Title;
public string Content;
public Action<MessageDetails> OnChange;
public Action OnSave;
public bool Disabled;
}
}
}
The "Disabled" flag needs to be able to be applied to the TextInput components, so TextInput needs to look like this:
using System;
using Bridge.Html5;
using Bridge.React;
namespace BridgeReactTutorial.Components
{
public class TextInput : StatelessComponent<TextInput.Props>
{
public TextInput(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
Type = InputType.Text,
ClassName = props.ClassName,
Disabled = props.Disabled,
Value = props.Content,
OnChange = e => props.OnChange(e.CurrentTarget.Value)
});
}
public class Props
{
public string ClassName;
public bool Disabled;
public string Content;
public Action<string> OnChange;
}
}
}
This 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.
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.
Now, 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 -
At 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.
The AppContainer will now become the following -
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class AppContainer : Component<AppContainer.Props, AppContainer.State>
{
public AppContainer(AppContainer.Props props) : base(props) { }
protected override State GetInitialState()
{
return new State
{
Message = new MessageDetails { Title = "", Content = "" },
IsSaveInProgress = false
};
}
public override ReactElement Render()
{
return new MessageEditor(new MessageEditor.Props
{
ClassName = "message",
Title = state.Message.Title,
Content = state.Message.Content,
OnChange = newMessage => SetState(new State
{
Message = newMessage,
IsSaveInProgress = state.IsSaveInProgress
}),
OnSave = async () =>
{
SetState(new State { Message = state.Message, IsSaveInProgress = true });
await props.MessageApi.SaveMessage(state.Message);
SetState(new State
{
Message = new MessageDetails { Title = "", Content = "" },
IsSaveInProgress = false
});
},
Disabled = state.IsSaveInProgress
});
}
public class Props
{
public IReadAndWriteMessages MessageApi;
}
public class State
{
public MessageDetails Message;
public bool IsSaveInProgress;
}
}
}
You will need to update App.cs to pass a props reference with a MessageApi instance to the AppContainer constructor -
using System.Linq;
using Bridge.Html5;
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.Components;
namespace BridgeReactTutorial
{
public class App
{
[Ready]
public static void Go()
{
var container = Document.GetElementById("main");
container.ClassName = string.Join(
" ",
container.ClassName.Split().Where(c => c != "loading")
);
React.Render(
new AppContainer(new AppContainer.Props { MessageApi = new MessageApi() }),
container
);
}
}
}
With 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 stateless 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.
At 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..
I 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 -
using System;
using Bridge.React;
namespace BridgeReactTutorial.Components
{
public class ValidatedTextInput : StatelessComponent<ValidatedTextInput.Props>
{
public ValidatedTextInput(Props props) : base(props) { }
public override ReactElement Render()
{
var className = props.ClassName;
if (!string.IsNullOrWhiteSpace(props.ValidationMessage))
className = (className + " invalid").Trim();
return DOM.Span(new Attributes { ClassName = className },
new TextInput(new TextInput.Props
{
ClassName = props.ClassName,
Disabled = props.Disabled,
Content = props.Content,
OnChange = props.OnChange
}),
string.IsNullOrWhiteSpace(props.ValidationMessage)
? null
: DOM.Span(
new Attributes { ClassName = "validation-message" },
props.ValidationMessage
)
);
}
public class Props
{
public string ClassName;
public bool Disabled;
public string Content;
public Action<string> OnChange;
public string ValidationMessage;
}
}
}
This allows the MessageEditor to be changed to use these ValidatedTextInputs instead of regular TextInputs, setting the "ValidationMessage" values according to whether the "Content" string has a value -
using System;
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class MessageEditor : StatelessComponent<MessageEditor.Props>
{
public MessageEditor(Props props) : base(props) { }
public override ReactElement Render()
{
var formIsInvalid =
string.IsNullOrWhiteSpace(props.Title) ||
string.IsNullOrWhiteSpace(props.Content);
return DOM.FieldSet(new FieldSetAttributes { ClassName = props.ClassName },
DOM.Legend(null, string.IsNullOrWhiteSpace(props.Title) ? "Untitled" : props.Title),
DOM.Span(new Attributes { ClassName = "label" }, "Title"),
new ValidatedTextInput(new ValidatedTextInput.Props
{
ClassName = "title",
Disabled = props.Disabled,
Content = props.Title,
OnChange = newTitle => props.OnChange(new MessageDetails
{
Title = newTitle,
Content = props.Content
}),
ValidationMessage = string.IsNullOrWhiteSpace(props.Title)
? "Must enter a title"
: null
}),
DOM.Span(new Attributes { ClassName = "label" }, "Content"),
new ValidatedTextInput(new ValidatedTextInput.Props
{
ClassName = "content",
Disabled = props.Disabled,
Content = props.Content,
OnChange = newContent => props.OnChange(new MessageDetails
{
Title = props.Title,
Content = newContent
}),
ValidationMessage = string.IsNullOrWhiteSpace(props.Content)
? "Must enter message content"
: null
}),
DOM.Button(
new ButtonAttributes
{
Disabled = props.Disabled || formIsInvalid,
OnClick = e => props.OnSave()
},
"Save"
)
);
}
public class Props
{
public string ClassName;
public string Title;
public string Content;
public Action<MessageDetails> OnChange;
public Action OnSave;
public bool Disabled;
}
}
}
Now, 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!
(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).
To keep things looking pretty, adding the following to "styles.css" -
fieldset.message span.title, fieldset.message span.content { position: relative; }
fieldset.message span.validation-message
{
position: absolute;
top: -6px;
right: 2px;
padding: 2px 4px;
font-size: 70%;
background: #FFF9D8;
border: 1px solid #EFE9CB;
border-radius: 2px;
color: #A8A390;
}
fieldset.message button { margin-left: 8px; }
What'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" -
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.API
{
public interface IReadAndWriteMessages
{
Task SaveMessage(MessageDetails message);
Task<IEnumerable<Tuple<int, MessageDetails>>> GetMessages();
}
}
This needs implementing in MessageApi -
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bridge.Html5;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.API
{
public class MessageApi : IReadAndWriteMessages
{
private readonly List<Tuple<int, MessageDetails>> _messages;
public MessageApi()
{
_messages = new List<Tuple<int, MessageDetails>>();
}
public Task SaveMessage(MessageDetails message)
{
if (message == null)
throw new ArgumentNullException("message");
if (string.IsNullOrWhiteSpace(message.Title))
throw new ArgumentException("A title value must be provided");
if (string.IsNullOrWhiteSpace(message.Content))
throw new ArgumentException("A content value must be provided");
var task = new Task<object>(null);
Window.SetTimeout(
() =>
{
_messages.Add(Tuple.Create(_messages.Count, message));
task.Complete();
},
1000 // Simulate a roundtrip to the server
);
return task;
}
public Task<IEnumerable<Tuple<int, MessageDetails>>> GetMessages()
{
// ToArray is used to return a clone of the message set - otherwise, the caller would
// end up with a list that is updated when the internal reference within this class
// is updated (which sounds convenient but it's not the behaviour that would be
// exhibited if this was really persisting messages to a server somewhere)
var task = new Task<IEnumerable<Tuple<int, MessageDetails>>>(null);
Window.SetTimeout(
() => task.Complete(_messages.ToArray()),
1000 // Simulate a roundtrip to the server
);
return task;
}
}
}
Now, we'll need a way to render this information -
using System;
using System.Collections.Generic;
using System.Linq;
using Bridge.React;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class MessageHistory : StatelessComponent<MessageHistory.Props>
{
public MessageHistory(Props props) : base(props) { }
public override ReactElement Render()
{
var className = props.ClassName;
if (!props.Messages.Any())
className = (className + " zero-messages").Trim();
// Any time a set of child components is dynamically-created (meaning that the
// numbers of items may vary from one render to another), each must have a unique
// "Key" property set (this may be a int or a string). Here, this is simple as
// each message tuple is a unique id and the contents of that message (and the
// unique id is ideal for use as a unique "Key" property).
var messageElements = props.Messages
.Select(idAndMessage => DOM.Div(new Attributes { Key = idAndMessage.Item1 },
DOM.Span(new Attributes { ClassName = "title" }, idAndMessage.Item2.Title),
DOM.Span(new Attributes { ClassName = "content" }, idAndMessage.Item2.Content)
));
return DOM.FieldSet(new FieldSetAttributes { ClassName = className },
DOM.Legend(null, "Message History"),
DOM.Div(null, messageElements)
);
}
public class Props
{
public string ClassName;
public IEnumerable<Tuple<int, MessageDetails>> Messages;
}
}
}
This 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".
There 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:
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).
The quote above is from Facebook's docs about Dynamic Children - and so "clobbered" must be an official term!
The 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).
For 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".
Now 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 -
using System;
using System.Collections.Generic;
using Bridge.React;
using BridgeReactTutorial.API;
using BridgeReactTutorial.ViewModels;
namespace BridgeReactTutorial.Components
{
public class AppContainer : Component<AppContainer.Props, AppContainer.State>
{
public AppContainer(AppContainer.Props props) : base(props) { }
protected override State GetInitialState()
{
return new State
{
Message = new MessageDetails { Title = "", Content = "" },
IsSaveInProgress = false,
MessageHistory = new Tuple<int, MessageDetails>[0]
};
}
public override ReactElement Render()
{
return DOM.Div(null,
new MessageEditor(new MessageEditor.Props
{
ClassName = "message",
Title = state.Message.Title,
Content = state.Message.Content,
OnChange = newMessage => SetState(new State
{
Message = newMessage,
IsSaveInProgress = state.IsSaveInProgress,
MessageHistory = state.MessageHistory
}),
OnSave = async () =>
{
// Set SaveInProgress to true while the save operation is requested
SetState(new State {
Message = state.Message,
IsSaveInProgress = true,
MessageHistory = state.MessageHistory
});
await props.MessageApi.SaveMessage(state.Message);
// After the save has completed, clear the message entry form and reset
// SaveInProgress to false
SetState(new State {
Message = new MessageDetails { Title = "", Content = "" },
IsSaveInProgress = false,
MessageHistory = state.MessageHistory
});
// Then re-load the message history state and re-render when that data arrives
var allMessages = await props.MessageApi.GetMessages();
SetState(new State {
Message = state.Message,
IsSaveInProgress = state.IsSaveInProgress,
MessageHistory = allMessages
});
},
Disabled = state.IsSaveInProgress
}),
new MessageHistory(new MessageHistory.Props
{
ClassName = "history",
Messages = state.MessageHistory
})
);
}
public class Props
{
public IReadAndWriteMessages MessageApi;
}
public class State
{
public MessageDetails Message;
public bool IsSaveInProgress;
public IEnumerable<Tuple<int, MessageDetails>> MessageHistory;
}
}
}
The 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).
One 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.
It'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:
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.
To keep things look nice, add the following to "styles.css" -
fieldset.history
{
opacity: 1;
transition: opacity .5s ease-in-out;
}
fieldset.history.zero-messages { opacity: 0; }
fieldset.history span.title
{
padding: 0 8px;
font-weight: bold;
}
This will have the MessageHistory invisible to begin with, fading in when the first message is available to display.
I 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.
Which 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.
All 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.
Posted at 23:55
11 February 2016
React's great strength is that it makes creating UIs simple(r) because you can treat the view as a pure function - often, you essentially give a props reference into a top level component and it works out what to draw. Then, when something changes, you do the same again; trigger a full re-draw and rely upon React's Virtual DOM to work out what changed in an efficient manner and apply those changes to the browser DOM. The browser DOM is slow, which is why interactions with it should be minimised. The Virtual DOM is fast.
The common pre-React way to deal with UIs was to have some code to render the UI in an initial state and then further code that would change the UI based upon user interactions. React reduces these two types of state-handling (initial-display and update-for-specific-interaction) into one (full re-render).
And a lot of the time, the fast Virtual DOM performs quickly enough that you don't have to worry about what it's doing. But sometimes, you may have a UI that is so complicated that it's a lot of work for the Virtual DOM to calculate the diffs to apply to the browser DOM. Or you might have particularly demanding performance requirements, such as achieving 60 fps animations on mobile.
Handily, React has a way for you to give it hints - namely the ShouldComponentUpdate method that components may implement. This method can look at the component's current props and state values and the next props and state values and let React know if any changes are required. The method returns a boolean - false meaning "no, I don't need to redraw, this data looks the same" and true meaning "yes, I need to redraw for this new data". The method is optional, if a component doesn't implement it then it's equivalent to it always returning true. Remember, if a component returns true for "do I need to be redrawn?", the Virtual DOM is still what is responsible for dealing with the update - and it usually deals with it in a very fast and efficient manner. Returning true is not something to necessarily be worried about. However, if you can identify cases where ShouldComponentUpdate can return false then you can save the Virtual DOM from working out whether that component or any of its child components need to be redrawn. If this can be done high up in a deeply-nested component tree then it could save the Virtual DOM a lot of work.
The problem is, though, that coming up with a mechanism to reliably and efficiently compare object references (ie. props and / or state) to determine whether they describe the same data is difficult to do in the general case.
Let me paint a picture by describing a very simple example React application..
Imagine an app that can read a list of messages from an API and allow the user of the app to edit these messages. Each message has "Content" and "Author" properties that are strings. Either of these values may be edited in the app. These messages are part of a message group that has a title - this also may be edited in the app.
(I didn't say that it was a useful or realistic app, it's just one to illustrate a point :)
The way that I like to create React apps is to categorise components as one of two things; a "Container Component" or a "Presentation Component". Presentation Components should be state-less, they should just be handed a props reference and then go off and draw themselves. Any interactions that the user makes with this component or any of its child components are effectively passed up (via change handlers on the props reference) until it reaches a Container Component. The Container Component will translate these interaction into actions to send to the Dispatcher. Actions will be handled by a store (that will be listening out for Dispatcher actions that it's interested in). When a store handles an action, it emits a change event. The Container Component will be listening out for change events on stores that it is interested in - when this happens, the Container Component will trigger a re-render of itself by updating its state based upon data now available in the store(s) it cares about. This is a fairly standard Flux architecture and, I believe, the terms "Container Component" / "Presentation Component are in reasonably common use (I didn't make them up, I just like the principle - one of the articles that I've read that uses these descriptions is Component Brick and Mortar: The React documentation I wish I had a year ago).
So, for my example app, I might have a component hierarchy that looks this:
AppContainer
Title
TextInput
Input
MessageList
MessageRow
TextInput
Input
TextInput
Input
MessageRow
TextInput
Input
TextInput
Input
There will be as many "MessageRow" components as there are messages to edit. Input is a standard React-rendered element and all of the others (AppContainer, Title, MessageList, MessageRow and TextInput) are custom components.
(Note: This is not a sufficiently deeply-nested hierarchy that React would have any problems with rendering performance, it's intended to be just complicated enough to demonstrate the point that I'm working up to).
The AppContainer is the only "Container Component" and so is the only component that has a React state reference as well as props. A state reference is, essentially, what prevents a component from being what you might consider a "pure function" - where the props that are passed in are all that affects what is rendered out. React "state" is required to trigger a re-draw of the UI, but it should be present in as few places as possible - ie. there should only be one, or a small number of, top level component(s) that have state. Components that render only according to their props data are much easier to reason about (and hence easier to write, extend and maintain).
My Bridge.NET React bindings NuGet package makes it simple to differentiate between stateful (ie. Container) components and stateless (ie. Presentation) components as it has both a Component<TProps, TState> base class and a StatelessComponent<TProps> base class - you derive from the appropriate one when you create custom components (for more details, see React (and Flux) with Bridge.net - Redux).
To start with the simplest example, below is the TextInput component. This just renders a text Input with a specified value and communicates up any requests to change that string value via an "OnChange" callback -
public class TextInput : StatelessComponent<TextInput.Props>
{
public TextInput(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
Type = InputType.Text,
Value = props.Content,
OnChange = OnTextChange
});
}
private void OnTextChange(FormEvent<InputElement> e)
{
props.OnChange(e.CurrentTarget.Value);
}
public class Props
{
public string Content { get; set; }
public Action<string> OnChange { get; set; }
}
}
It is fairly easy to envisage how you might try to implement "ShouldComponentUpdate" here - given a "this is the new props value" reference (which gets passed into ShouldComponentUpdate as an argument called "nextProps") and the current props reference, you need only look at the "Content" and "OnChange" references on the current and next props and, if both Content/Content and OnChange/OnChange references are the same, then we can return false (meaning "no, we do not need to re-draw this TextInput").
(Two things to note here: Firstly, it is not usually possible to directly compare the current props reference with the "nextProps" reference because it is common for the parent component to create a new props instance for each proposed re-render of a child component, rather than re-use a previous props instance - so the individual property values within the props references may all be consistent between the current props and nextProps, but the actual props references will usually be distinct. Secondly, the Bridge.NET React bindings only support React component life cycle method implementations on custom components derived from Component<TProps, TState> classes and not those derived from StatelessComponent<TProps>, so you couldn't actually write your own "ShouldComponentUpdate" for a StatelessComponent - but that's not important here, we're just working through a thought experiment).
Now let's move on to the MessageList and MessageRow components, since things get more complicated there -
public class MessageList : StatelessComponent<MessageList.Props>
{
public MessageList(Props props) : base(props) { }
public override ReactElement Render()
{
var messageRows = props.IdsAndMessages
.Select(idAndMessage => new MessageRow(new MessageRow.Props
{
Key = idAndMessage.Item1,
Message = idAndMessage.Item2,
OnChange = newMessage => props.OnChange(idAndMessage.Item1, newMessage)
}));
return DOM.Div(
new Attributes { ClassName = "message-list" },
messageRows
);
}
public class Props
{
public Tuple<int, MessageEditState>[] IdsAndMessages;
public Action<int, MessageEditState> OnChange;
}
}
public class MessageRow : StatelessComponent<MessageRow.Props>
{
public MessageRow(Props props) : base(props) { }
public override ReactElement Render()
{
// Note that the "Key" value from the props reference does not explicitly need
// to be mentioned here, the React bindings will deal with it (it is important
// to give dynamic children components unique key values, but it is handled by
// the bindings and the React library so long as a "Key" property is present
// on the props)
// - See https://facebook.github.io/react/docs/multiple-components.html for
// more details
return DOM.Div(new Attributes { ClassName = "message-row" },
new TextInput(new TextInput.Props
{
Content = props.Message.Content,
OnChange = OnContentChange
}),
new TextInput(new TextInput.Props
{
Content = props.Message.Author,
OnChange = OnAuthorChange
})
);
}
private void OnContentChange(string newContent)
{
props.OnChange(new MessageEditState
{
Content = newContent,
Author = props.Message.Author
});
}
private void OnAuthorChange(string newAuthor)
{
props.OnChange(new MessageEditState
{
Content = props.Message.Content,
Author = newAuthor
});
}
public class Props
{
public int Key;
public MessageEditState Message;
public Action<MessageEditState> OnChange;
}
}
public class MessageEditState
{
public string Content;
public string Author;
}
If the MessageList component wanted to implement "ShouldComponentUpdate" then its job is more difficult as it has an array of message data to check. It could do one of several things - the first, and most obviously accurate, would be to perform a "deep compare" of the arrays from the current props and the "nextProps"; ensuring firstly that there are the same number of items in both and then comparing each "Content" and "Author" value in each item of the arrays. If everything matches up then the two arrays contain the same data and (so long as the "OnChange" callback hasn't changed) the component doesn't need to re-render. Avoiding re-rendering this component (and, subsequently, any of its child components) would be a big win because it accounts for a large portion of the total UI. Not re-rendering it would give the Virtual DOM much less work to do. But would a deep comparison of this type actually be any cheaper than letting the Virtual DOM do what it's designed to do?
The second option is to presume that whoever created the props references would have re-used any MessageEditState instances that haven't changed. So the array comparison could be reduced to ensuring that the current and next props references both have the same number of elements and then performing reference equality checks on each item.
The third option is to presume that whoever created the props reference would have re-used the array itself if the data hadn't changed, meaning that a simple reference equality check could be performed on the current and next props' arrays.
The second and third options are both much cheaper than a full "deep compare" but they both rely upon the caller following some conventions. This is why I say that this is a difficult problem to solve for the general case.
There is actually another option to consider, the object models for the props data could be rewritten to use immutable types. These have the advantage that if you find that two references are equal then they are guaranteed to contain the same data. They also have the advantage that it's much more common to re-use instances to describe the same data - partly because there is some overhead to initialising immutable types and partly because there is no fear that "if I give this reference to this function, I want to be sure that it can't change the data in my reference while doing its work" because it is impossible to change an immutable reference's data. (I've seen defensively-written code that clones mutable references that it passes into other functions, to be sure that no other code can change the data in the original reference - this is never required with immutable types).
Conveniently, I've recently written a library to use with Bridge.NET which I think makes creating and working with immutable types easier than C# makes it on its own. I wrote about it in "Friction-less immutable objects in Bridge (C# / JavaScript) applications" but the gist is that you re-write MessageEditState as:
// You need to pull in the "ProductiveRage.Immutable" NuGet package to use IAmImmutable
public class MessageEditState : IAmImmutable
{
public MessageEditState(string content, string author)
{
this.CtorSet(_ => _.Content, content);
this.CtorSet(_ => _.Author, author);
}
public string Content { get; private set; }
public string Author { get; private set; }
}
It's still a little more verbose than the mutable version, admittedly, but I'm hoping to convince you that it's worth it (if you need convincing!) for the benefits that we'll get.
When you have an instance of this new MessageEditState class, if you need to change one of the properties, you don't have to call the constructor each time to get a new instance, you can use the "With" extension methods that may be called on any IAmImmutable instance - eg.
var updatedMessage = message.With(_ => _.Content, "New information");
This would mean that the change handlers from MessageRow could be altered from:
private void OnContentChange(string newContent)
{
props.OnChange(new MessageEditState
{
Content = newContent,
Author = props.Message.Author
});
}
private void OnAuthorChange(string newAuthor)
{
props.OnChange(new MessageEditState
{
Content = props.Message.Content,
Author = newAuthor
});
}
and replaced with:
private void OnContentChange(string newContent)
{
props.OnChange(props.Message.With(_ => _.Content, newContent));
}
private void OnAuthorChange(string newAuthor)
{
props.OnChange(props.Message.With(_ => _.Author, newAuthor));
}
Immediately, the verbosity added to MessageEditState is being offset with tidier code! (And it's nice not having to set both "Content" and "Author" when only changing one of them).
The "With" method also has a small trick up its sleeve in that it won't return a new instance if the new property value is the same as the old property value. This is an eventuality that could happen in the code above as an "Input" element rendered by React will raise an "OnChange" event for any action that might have altered the text input's content. For example, if you had a text box with the value "Hello" in it and you selected all of that text and then pasted in text from the clipboard over the top of it, if the clipboard text was also "Hello" then the "OnChange" event will be raised, even though the actual value has not changed (it was "Hello" before and it's still "Hello" now). The "With" method will deal with this, though, and just pass the same instance straight back out. This is an illustration of the "reuse of instances for unchanged data" theme that I alluded to above.
The next step would be to change the array type in the MessageList.Props type from
public Tuple<int, MessageEditState>[] IdsAndMessages;
to
public NonNullList<Tuple<int, MessageEditState>> IdsAndMessages;
The NonNullList class is also in the ProductiveRage.Immutable NuGet package. It's basically an immutable IEnumerable that may be used in Bridge.NET projects. A simple example of it in use is:
// Create a new set of values (the static "Of" method uses type inference to determine
// the type of "T" in the returned "NonNullList<T>" - since 1, 2 and 3 are all ints, the
// "numbers" reference will be of type "NonNullList<int>")
var numbers = NonNullList.Of(1, 2, 3);
// SetValue takes an index and a new value, so calling SetValue(2, 4) on a set
// containing 1, 2, 3 will return a new set containing the values 1, 2, 4
numbers = numbers.SetValue(2, 4);
// Calling SetValue(2, 4) on a set containing values 1, 2, 4 does not require any
// changes, so the input reference is passed straight back out
numbers = numbers.SetValue(2, 4);
As with IAmImmutable instances we get two big benefits - we can rely on reference equality comparisons more often, since the data with any given reference can never change, and references will be reused in many cases if operations are requested that would not actually change the data. (It's worth noting that the guarantees fall apart if any property on an IAmImmutable reference is a of a mutable type, similarly if a NonNullList has elements that are a mutable type, or that have nested properties that are of a mutable type.. but so long as immutability is used "all the way down" then all will be well).
If this philosophy was followed, then suddenly the "ShouldComponentUpdate" implementation for the MessageList component would be very easy to write - just perform reference equality comparisons on the "IdsAndMessages" and "OnChange" values on the current props and on the nextProps. While solving the problem for the general case is very difficult, solving it when you introduce some constraints (such as the use of immutable and persistent data types) can be very easy!
If we did implement this MessageList "ShouldComponentUpdate" method, then we could be confident that when a user makes changes to the "Title" text input that the Virtual DOM would not have to work out whether the MessageList or any of its child components had changed - because we'd have told the Virtual DOM that they hadn't (because the "IdsAndMessages" and "OnChange" property references wouldn't have changed).
We could take this a step further, though, and consider the idea of implementing "ShouldComponentUpdate" on other components - such as MessageRow. If the user edits a text value within one row, then the MessageList will have to perform some re-rendering work, since one of its child components needs to be re-rendered. But there's no need for any of the other rows to re-render, it could be just the single row in which the change was requested by the user.
So the MessageRow could look at its props values and, if they haven't changed between the current props and the nextProps, then inform React (via "ShouldComponentUpdate") that no re-render is required.
And why not go even further and just do this on all Presentation Components? The TextInput could avoid the re-render of its child Input if the props' "Content" and "OnChange" reference are not being updated.
To make this easy, I've added a new base class to the React bindings (available in 1.4 of Bridge.React); the PureComponent<TProps>.
This, like the StatelessComponent<TProps>, is very simple and does not support state and only allows the "Render" method to be implemented - no other React lifecycle functions (such "ComponentWillMount", "ShouldComponentUpdate", etc..) may be defined on components deriving from this class.
The key difference is that it has its own "ShouldComponentUpdate" implementation that presumes that the props data is immutable and basically does what I've been describing above automatically - when React checks "ShouldComponentUpdate", it will look at the "props" and "nextProps" instances and compare their property values. (It also deals with the cases where one or both of them are null, in case you want components whose props reference is optional).
This is not an original idea, by a long shot. I first became aware of people doing this in 2013 when I read The Future of JavaScript MVC Frameworks, which was talking about using ClojureScript and its React interface "Om". More recently, I was reading Performance Engineering with React (Part 1), which talks about roughly the same subject but with vanilla JavaScript. And, of course, Facebook has long had its PureRenderMixin - though mixins can't be used with ES6 components (which seems to be the approach to writing components that Facebook is pushing at the moment).
So, this is largely just making it easy it when writing React applications with Bridge. However, using Bridge to do this does give us some extra advantages (on top of the joy of being able to write React apps in C#!). In the code earlier (from the MessageRow Render method) -
new TextInput(new TextInput.Props
{
Content = props.Message.Content,
OnChange = OnContentChange
})
Bridge will bind the "OnContentChange" method to the current MessageRow instance so that when it is called by the TextInput's "OnChange" event, "this" is the MessageRow and not the TextInput (which is important because OnContentChange needs to access the "props" reference scoped to the MessageRow).
This introduces a potential wrinkle in our plan, though, as this binding process creates a new JavaScript method each time and means that each time the TextInput is rendered, the "OnChange" reference is new. So if we try to perform simple reference equality checks on props values, then we won't find the current "OnChange" and the new "OnChange" to be the same.
This problem is mentioned in the "Performance Engineering" article I linked above:
Unfortunately, each call to Function.bind produces a new function.. No amount of prop checking will help, and your component will always re-render.
..
The simplest solution we've found is to pass the unbound function.
When using Bridge, we don't have the option of using an unbound function since the function-binding is automatically introduced by the C#-to-JavaScript translation process. And it's very convenient, so it's not something that I'd ideally like to have to workaround.
Having a dig through Bridge's source code, though, revealed some useful information. When Bridge.fn.bind is called, it returns a new function (as just discussed).. but with some metadata attached to it. When it returns a new function, it sets two properties on it "$scope" and "$method". The $scope reference is what "this" will be set to when the bound function is called and the $method reference is the original function that is being bound. This means that, when the props value comparisons are performed, if a value is a function and it the reference equality comparison fails, a fallback approach may be attempted - if both functions have $scope and $method references defined then compare them and, if they are both consistent between the function value on the current props and the function value on the nextProps, then consider the value to be unchanged.
The PureComponent's "ShouldComponentUpdate" implementation deals with this automatically, so you don't have to worry about it.
It's possibly worth noting that the "Performance Engineering" post did briefly consider something similar -
Another possibility we've explored is using a custom bind function that stores metadata on the function itself, which in combination with a more advanced check function, could detect bound functions that haven't actually changed.
Considering that Bridge automatically includes this additional metadata, it seemed to me to be sensible to use it.
There's one other equality comparison that is supported; as well as simple referential equality and the function equality gymnastics described above, if both of the values are non-null and the first has an "Equals" function then this function will be considered. This means that any custom "Equals" implementations that you define on classes will be automatically taken into consideration by the PureComponent's logic.
When I started writing this post, there was going to be a section here with a warning about using lambdas as functions in props instances, rather than using named functions (which the examples thus far have done).
As with bound functions, anywhere that an anonymous function is present in JavaScript, it will result in a new function value being created. If, for example, we change the MessageRow class from:
public class MessageRow : PureComponent<MessageRow.Props>
{
public MessageRow(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Div(new Attributes { ClassName = "message-row" },
new TextInput(new TextInput.Props
{
Content = props.Message.Content,
OnChange = OnContentChange
}),
new TextInput(new TextInput.Props
{
Content = props.Message.Author,
OnChange = OnAuthorChange
})
);
}
private void OnContentChange(string newContent)
{
props.OnChange(props.Message.With(_ => _.Content, newContent));
}
private void OnAuthorChange(string newAuthor)
{
props.OnChange(props.Message.With(_ => _.Author, newAuthor));
}
public class Props
{
public int Key;
public MessageEditState Message;
public Action<MessageEditState> OnChange;
}
}
to:
public class MessageRow : PureComponent<MessageRow.Props>
{
public MessageRow(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Div(new Attributes { ClassName = "message-row" },
new TextInput(new TextInput.Props
{
Content = props.Message.Content,
OnChange = newContent =>
props.OnChange(props.Message.With(_ => _.Content, newContent))
}),
new TextInput(new TextInput.Props
{
Content = props.Message.Author,
OnChange = newAuthor =>
props.OnChange(props.Message.With(_ => _.Author, newAuthor))
})
);
}
public class Props
{
public int Key;
public MessageEditState Message;
public Action<MessageEditState> OnChange;
}
}
then there would be problems with the "OnChange" props values specified because each new lambda - eg..
OnChange = newContent =>
props.OnChange(props.Message.With(_ => _.Content, newContent))
would result in a new JavaScript function being passed to Bridge.fn.bind every time that it was called:
onChange: Bridge.fn.bind(this, function (newContent) {
this.getprops().onChange(
ProductiveRage.Immutable.ImmutabilityHelpers.$with(
this.getprops().message,
function (_) { return _.getContent(); },
newContent
)
);
})
And this would prevent the PureComponent's "ShouldComponentUpdate" logic from being effective, since the $method values from the current props "OnChange" and the nextProps "OnChange" bound functions would always be different.
I was quite disappointed when I realised this and was considering trying to come up with some sort of workaround - maybe calling "toString" on both $method values and comparing their implementations.. but I couldn't find definitive information about the performance implications of this and I wasn't looking forward to constructing my own suite of tests to investigate any potential performance impact of this across different browsers and different browser versions.
My disappointment was two-fold: firstly, using the lambdas allows for more succinct code and less syntactic noise - since the types of the lambda's argument(s) and return value (if any) are inferred, rather than having to be explicitly typed out.
newContent => props.OnChange(props.Message.With(_ => _.Content, newContent))
is clearly shorter than
private void OnContentChange(string newContent)
{
props.OnChange(props.Message.With(_ => _.Content, newContent));
}
The other reason that I was deflated upon realising this was that it meant that the "ShouldComponentUpdate" implementation would, essentially, silently fail for components that used lambdas - "ShouldComponentUpdate" would return true in cases where I would like it to return false. There would be no compiler error and the UI code would still function, but it wouldn't be as efficient as it could be (the Virtual DOM would have to do more work than necessary).
Instead, I had a bit of a crazy thought.. lambdas like this, that only need to access their own arguments and the "this" reference, could be "lifted" into named functions quite easily. Essentially, I'm doing this manually by writing methods such as "OnContentChange". But could the Bridge translator do something like this automatically - take those C# lambdas and convert them into named functions in JavaScript? That way, I would get the benefit of the succinct lambda format in C# and the PureComponent optimisations would work.
Well, once again the Bridge.NET Team came through for me! I raised a Feature Request about this, explained what I'd like in an ideal world (and why) and five days later there was a branch on GitHub where I could preview changes that did precisely what I wanted!
This is not just an example of fantastic support from the Bridge Team, it is also, I believe, an incredible feature for Bridge and a triumph for writing front-end code in C#! Having this "translation step" from C# to JavaScript provides the opportunity for handy features to be included for free - earlier we saw how the insertion of Bridge.fn.bind calls by the translator meant that we had access to $method and $scope metadata (which side-steps one of the problems that were had by the author of Performance Engineering with React) but, here, the translation step can remove the performance overhead that anonymous functions were going to cause for our "ShouldComponentUpdate" implementation, without there being any burden on the developer writing the C# code.
It's also worth considering the fact that every allocation made in JavaScript is a reference that needs to be tidied up by the browser's garbage collector at some point. A big reason why judicious use of "ShouldComponentUpdate" can make UIs faster is that there is less work for the Virtual DOM to do, but it also eases the load on the garbage collector because none of the memory allocations need to be made for child components of components that do not need to be re-rendered. Since anonymous JavaScript functions are created over and over again (every time that the section of code that declares the anonymous function is executed), lifting them into named functions means that there will be fewer allocations in your SPA and hence even less work for the garbage collector to do.
Note: As of the 11th of February 2016, this Bridge.NET improvement has not yet been made live - but their release cycles tend to be fairly short and so I don't imagine that it will be very long until it is included in an official release. If you were desperate to write any code with PureComponent before then, you could either avoid lambdas in your C# code or you could use lambdas now, knowing that the PureComponent won't be giving you the full benefit immediately - but that you WILL get the full benefit when the Bridge Team release the update.
Well, until it transpired that the Bridge translator would be altered to convert these sorts of lambdas into named functions, I was going to say "this is good, but..". However, with that change in sight, I'm just going to say outright "yes, and I'm going to change all classes that derive from StatelessComponent in my projects to derive from PureComponent". This will work fine, so long as your props references are all immutable (meaning that they are immutable all the way down - you shouldn't have, say, a props property that is an immutable NonNullList of references, but where those references have mutable properties).
And, if you're not using immutable props types - sort yourself out! While a component is being rendered (according to the Facebook React Tutorial):
props are immutable: they are passed from the parent and are "owned" by the parent
So, rather than having props only be immutable during component renders (by a convention that the React library enforces), why not go whole-hog and use fully immutable classes to describe your props types - that way props are fully immutable and you can use the Bridge.React's PureComponent to get performance boosts for free!
(Now seems like a good time to remind you of my post "Friction-less immutable objects in Bridge (C# / JavaScript) applications", which illustrates how to use the ProductiveRage.Immutable NuGet package to make defining immutable classes just that bit easier).
Posted at 20:11
26 November 2015
Earlier in the year I wrote about using Bridge.net to write browser-based applications using React. Well, now, I'd like to present an update to that. I've changed how the base Component class ties into the React library (this is a class that may be derived from in order to create custom React components) - it now supports "SetState" - and I've added an alternative base class; the StatelessComponent, which will allow the writing of component classes that will operate as stateless components, as introduced by React 0.14. I've also improved how the components appear when viewed in the React Dev Tools browser extension and I've tied it into the latest, just-released version of Bridge (1.10) that has fixed a lot of bugs.
If you're the sort of person who likes to jump straight to the end of a book to see how it ends, then you can find the code on in my GitHub "Bridge.React" repo or you can add it to a Bridge project through NuGet (Bridge.React). But if you want to find out more of the details then keep reading! I'm not going to presume any prior knowledge from my previous post - so if you've read that, then I'm afraid I'm going to re-tread some of the same ground - however, I imagine that I don't have that many dedicated followers, so figure it makes more sense to make this entry nicely self-contained :)
In the past, I've also written about writing bindings for TypeScript (which is a language I liked.. but not as much as C#) and bindings for DuoCode (which is a project that seemed to have promise until they spent so longer thinking about their pricing model that I gave up on them) as well as a couple of posts about Bridge - and, often, I've got quite technical about how the bindings work under the hood. Today, though, I'm just going to deal with how to use the bindings. I'm happy that they're finally fully-populated and I've tried to make an effort to make them easy to consume, so let's just stick to getting Bridge apps talking to React and not worry about the magic behind the scenes!
I'm going to assume that you're familiar with React - though I won't be going into too much depth on it, so if you're not an expert then it shouldn't be any problem. I'm not going to assume that you have tried out Bridge yet, because it's so easy to presume that you haven't that it won't take us long to start from scratch!
So, let's really start from the basics. You need to create a new solution in Visual Studio - choose a C# Class Library. Now go to References / Manage NuGet Packages, search for "Bridge.React" online and install the package. This will automatically pull in the Bridge package as a dependency, and this sets up a "demo.html" file under the "Bridge/www" folder to make getting started as frictionless as possible. That file has the following content:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Bridge BridgeReactBlogPost</title>
<script src="../output/bridge.js"></script>
<script src="../output/BridgeReactBlogPost.js"></script>
</head>
<body>
<!--
Right-Click on this file
and select "View in Browser"
-->
</body>
</html>
Note that the title and the JavaScript filename are taken from the project name. So the file above mentions "BridgeReactBlogPost" because that's the name of the project that I'm creating myself alongside writing this post (just to ensure that I don't miss any steps or present any dodgy demonstration code!).
We need to add a few more items now - the React library JavaScript, the Bridge.React JavaScript and an element for React to render inside. So change demo.html to something like the following:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Bridge BridgeReactBlogPost</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.js"></script>
<script src="../output/bridge.js"></script>
<script src="../output/bridge.react.js"></script>
<script src="../output/BridgeReactBlogPost.js"></script>
</head>
<body>
<div id="main" />
</body>
</html>
(Aside: If you want to, then you can add the line
"combineScripts": true
to your bridge.json file, which will cause ALL of the project JavaScript files to be built into a single file - including "bridge.js" and "bridge.react.js" - so, if you used this option, you would only need to include a single JavaScript file. In this example, it would be just "../output/BridgeReactBlogPost.js").
Now change the "Class1.cs" file (that was created automatically when you requested the new "Class Library" project) thusly:
using Bridge.Html5;
using Bridge.React;
namespace BridgeReactBlogPost
{
public class Class1
{
[Ready]
public static void Main()
{
React.Render(
DOM.Div(
new Attributes { ClassName = "wrapper" },
"Hiya!"
),
Document.GetElementById("main")
);
}
}
}
.. and then right-click on demo.html, click "View in Browser" and you should be greeted by some React-rendered content. Good start!
Update (2nd December 2015): I originally showed a non-static method above with a [Ready] attribute on it - this worked in earlier versions of Bridge but does not work any longer. In the examples in this post, using an instance method with the [Ready] attribute will result in the method NOT being called at DOM ready (it will appear to fail silently by doing no work but showing no warnings). Don't make my mistake, make [Ready] methods static!
Now, let's be slightly more ambitious -
[Ready]
public static void Main()
{
React.Render(
DOM.Div(new Attributes { ClassName = "wrapper" },
DOM.Input(new InputAttributes
{
OnChange = e => Window.Alert(e.CurrentTarget.Value),
MaxLength = 3
})
),
Document.GetElementById("main")
);
}
Re-build then use "View in Browser" again. Now each change to the input box is thrown back in your face in an alert. The type of "e.CurrentTarget" is "InputElement" and so there is a string "Value" property available. And the "InputAttributes" class allows the setting of all of the properties that are specific to an InputElement, such as "MaxLength". This is one of the great things about using a type system to document your API - you use types (such as requiring an InputAttributes instance when DOM.Input is called) to inform the user of the API; what can and can't be done. And, while I've got a lot of respect for the people maintaining the DefinitelyTyped TypeScript type definitions, you don't get as much detail in their React bindings as are available here!
In fairness, I should really give credit where it's due here - the "InputElement" type comes from the Bridge.Html5 namespace, so I haven't had to write all of those definitions myself. And the "InputAttributes" class was based upon the InputElement's source code; I only had to remove read-only properties (for example, the html "input" element has a "valueAsNumber" property - only applicable to input elements with type "number" - that is read-only and so it would not make sense for this to be settable as a React attribute). I also had to remove some unsupported functionality (for example, checkbox input elements support an "indeterminate" flag in browsers but this is not supported by React).
All of the element factory methods in React ("div", "span", "input", etc..) have corresponding methods in the bindings, with types that express any additional properties that should be available - eg. we have
ReactElement TD(
TableCellAttributes properties,
params Any<ReactElement, string>[] children
);
where the "TableCellAttributes" introduces additional properties such as "int ColSpan" and "int RowSpan" (note that the bindings all use pascal-cased function and type names since this is what is more commonly seen in C# code - where the functions are translated into JavaScript they will automatically use the camel-cased JavaScript names, so "Div" becomes "div", for example).
But this is the boring stuff - as soon as you start using React, you want to create your own components!
React 0.14 introduced a concept, the "Stateless Component". In native JavaScript, this is just a function that takes a props reference and returns a React element. But to make it feel more natural in C#, the bindings have a base class which can effectively become a Stateless Component - eg.
public class MyLabel : StatelessComponent<MyLabel.Props>
{
public MyLabel(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Label(
new LabelAttributes { ClassName = props.ClassName },
props.Value
);
}
public class Props
{
public string Value;
public string ClassName;
}
}
The "StatelessComponent" base class takes a generic type parameter that describe the "props" reference type. Then, when "Render" is called, the "props" reference will be populated and ready to use within Render. If any other functions are declared within the class, they may be called from Render as you might expect (see further down). So we are able to write very simple custom components that React will treat as these special Stateless Components - about which, Facebook say:
In the future, we'll also be able to make performance optimizations specific to these components
Creating one of these components is as easy as:
React.Render(
new MyLabel(new MyLabel.Props { ClassName = "wrapper", Value = "Hi!" }),
Document.GetElementById("main")
);
It is important to note, however, that - due to the way that React creates components - the constructor of these classes must always be a no-op (it won't actually be called when React prepares the component) and the only data that the class can have passed in must be described in the props data. If you tried to do something like the following then it won't work -
public class MyLabel : StatelessComponent<MyLabel.Props>
{
private readonly int _index;
public MyLabel(Props props, int index) : base(props)
{
// THIS WON'T WORK - the constructor is not processed
_index = index;
}
public override ReactElement Render()
{
return DOM.Label(
new LabelAttributes { ClassName = props.ClassName },
props.Value + " (index: " + _index + ")"
);
}
public class Props
{
public string Value;
public string ClassName;
}
}
You can use instance members if you want to, you just can't rely on them being set in the constructor because the constructor is never called. Side note: I'm thinking about trying to write a C# Analyser to accompany these bindings so that any rules like this can be pointed out by the compiler, rather than you just having to remember them.
public class MyLabel : StatelessComponent<MyLabel.Props>
{
private int _index;
public MyLabel(Props props) : base(props) { }
public override ReactElement Render()
{
// Accessing instance fields and methods is fine, so long as it
// isn't done in the constructor
SetIndex();
return DOM.Label(
new LabelAttributes { ClassName = props.ClassName },
props.Value + " (index: " + _index + ")"
);
}
private void SetIndex()
{
_index = MagicStaticIndexGenerator.GetNext();
}
public class Props
{
public string Value;
public string ClassName;
}
}
You can also create custom components that have child elements. Just like "DOM.Div" takes an attributes reference (its "Props", essentially) and then an array of child elements, the StatelessComponent class takes a params array after that first "props" argument.
This array has elements of type "Any<ReactElement, string>", which means that it can be the result of a React factory method (such as "Div") or it can be a string, so that text elements can be easily rendered. Or it can be any class that derives from StatelessComponent as StatelessComponent has an implicit cast operator to ReactElement.
(Note: There used to be a ReactElementOrText class mentioned here but it didn't offer any benefit over Bridge's generic Any<,> class, so I've changed the NuGet package - as of 1.3.0 / 27th September 2015 - and have updated this post accordingly).
So, we could create a simple "wrapper" component that renders a Div with a class and some children -
public class MyWrapper : StatelessComponent<MyWrapper.Props>
{
public MyWrapper(Props props, params Any<ReactElement, string>[] children)
: base(props, children) { }
public override ReactElement Render()
{
return DOM.Div(
new Attributes { ClassName = props.ClassName },
Children
);
}
public class Props
{
public string ClassName;
}
}
And render it like this:
React.Render(
new MyWrapper(new MyWrapper.Props { ClassName = "wrapper" },
DOM.Span(null, "Child1"),
DOM.Span(null, "Child2"),
DOM.Span(null, "Child3")
),
Document.GetElementById("main")
);
or even just like:
React.Render(
new MyWrapper(new MyWrapper.Props { ClassName = "wrapper" },
"Child1",
"Child2",
"Child3"
),
Document.GetElementById("main")
);
The "Children" property accessed within MyWrapper is exposed through StatelessComponent and will echo back the child elements passed into the constructor when the component instance was declared. If there were no children specified then it will be an empty array.
This brings us on to the next topic - Keys for dynamic children. To aid React's reconciliation process in cases where dynamic children elements are specified, you should specify Key values for each item. Each Key should be consistent and unique within the parent component (for more details, read the "Keys / Reconciliation" section from the Facebook docs).
If you were declaring React components in vanilla JavaScript, then this would be as easy as including a "key" value in the props object. Using these Bridge bindings, it's almost as simple - if your component needs to support an optional "Key" property then its Props class should include a "Key" property. And that's all that's required! You don't need to set anything to that Key inside your component, you merely need to allow it to be set on the props. React will accept numeric or string keys, so I would recommend that you declare the "Key" property as either an int or a string or as an Any<int, string>, which is built-in Bridge class that allows either of the value types to be used. To illustrate:
public class MyListItem : StatelessComponent<MyListItem.Props>
{
public MyListItem(Props props) : base(props) { }
public override ReactElement Render()
{
return DOM.Li(null, props.Value);
}
public class Props
{
public Any<int, string> Key;
public string Value;
}
}
Note: In the earlier examples, the "Child{x}" elements were fixed at compile time and so didn't need Key properties to be set, but if you were displaying a list of search results that were based on data from an api call, for example, then these elements would NOT be fixed at compile time and so you should specify unique Key values for them.
So far, I've only talked about stateless components, which are like a slimmed-down version of full React components. But sometimes you need a stateful component, or one that supports the full React lifecycle.
For these times, there is another base class - simply called Component. This has two generic type parameters, one for the "props" data and for "state". However, the constructor signature is the same as the StatelessComponent; it takes a props reference and then any children element that the component instance has. The state reference is controlled by the two React component lifecycle functions "GetInitialState" and "SetState". "GetInitialState" is called when the component is first created and "SetState" can be used to not only update the internal "state" reference but also request that the component re-render.
The most basic example would be something like this:
// Note: I've not even declared a class fortthe State, I've just used
// "string" since the state in this class is just a string value. But
// that's because I'm lazy, the state was more complicated then it
// could be a separate class, just like Props.
public class StatefulControlledTextInput
: Component<StatefulControlledTextInput.Props, string>
{
public StatefulControlledTextInput(Props props) : base(props) { }
protected override string GetInitialState() { return ""; }
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
ClassName = props.ClassName,
Type = InputType.Text,
Value = state,
OnChange = ev => SetState(ev.CurrentTarget.Value)
});
}
public class Props
{
public string ClassName;
}
}
Each time the input's value is changed, the component calls its own SetState function so that it can re-render with the new value (there's a good Facebook summary article if you've forgotten the difference between "controlled" and "uncontrolled" components; the gist is the controlled components only raise events when the user requests that their values change, they won't be redrawn unless React cause them to redraw).
This isn't all that the Component class allows, though, it has support for the other React component lifecycle methods - for example, sometimes the "OnChange" event of a text input is raised when the content hasn't really changed (if you put focus in a text input and [Ctrl]-[C] / copy whatever value is in it and then [Ctrl]-[V] / paste that value straight back in, the OnChange event will be raised even though the new value is exactly the same as the old value). You might consider this redraw to be unacceptable. In which case, you could take advantage of the "ShouldComponentUpdate" function like this:
public class StatefulControlledTextInput
: Component<StatefulControlledTextInput.Props, string>
{
public StatefulControlledTextInput(Props props) : base(props) { }
protected override string GetInitialState() { return ""; }
protected override bool ShouldComponentUpdate(
StatefulControlledTextInput.Props nextProps,
string nextState)
{
return (props != nextProps) || (state != nextState);
}
public override ReactElement Render()
{
return DOM.Input(new InputAttributes
{
ClassName = props.ClassName,
Type = InputType.Text,
Value = state,
OnChange = ev => SetState(ev.CurrentTarget.Value)
});
}
public class Props
{
public string ClassName;
}
}
Now, in the cases where the input's value doesn't really change, the component's "update" will be bypassed.
Clearly, this is a trivial example, but it demonstrates how you could do something more complicated along these lines. All of the other functions "ComponentDidMount", "ComponentDidUpdate", "ComponentWillMount", "ComponentWillReceiveProps", "ComponentWillUnmount" and "ComponentWillUpdate" are also supported.
And, of course, the Component base class has the same "Children" integration that StatelessComponent has and the same support for specifying a "Key" props value.
There is one little oddity to be aware of, though: In React, "setState" has (in my opinion) a slightly odd behaviour in that it will accept a "partial state value" that it will then merge with the current state reference. So if you had a MyComponentState class with properties "Value1" and "Value2" then you could, in vanilla JavaScript React, call setState({ Value1: whatever }) and it would take that "Value1" and overwrite the current "Value1" in the current state reference, leaving any existing "Value2" untouched. In these bindings, you must specify an entire State reference and this merging does not occur - the old State reference is replaced entirely by the new. This is largely because the "SetState" function in the bindings takes a full "State" class reference (C# doesn't really have a concept of a part-of-this-class representation) but it's also because I think that it's clearer this way; I think that you should be explicit about what you're setting State to and having it be a-bit-of-what-was-there-before and a-bit-of-something-new is not as clear (if you ask me) as a complete here-is-the-new-state reference.
In React, it is strongly recommended that props and state be considered to be immutable references. In the examples here I've used immutability-by-convention; the "props" classes have not actually been immutable types. I'm intending to write a follow-up article or two because there is more that I want to explore, such as how to use these bindings to write React apps in a "Flux"-like manner and how to take more advantage of genuinely immutable types. But, hopefully, this has been a nice enough introduction into the bindings and got you thinking about trying to use C# to write some React apps! Because, if you're aiming to write a web application in a "Single Page Application" style, if your application is of any serious complexity then you're going to end up with quite a lot of code - and, while I have a real soft spot for JavaScript, if it comes to maintaining a large app that's written in JavaScript or that's written in C# then I know which way I would lean! Thank goodness Bridge.net has come along and let us combine JavaScript frameworks with C# :)
Posted at 23:29
19 August 2015
A few weeks ago, I wrote about using React with Bridge.net. I described that I'd only written the bare minimum of bindings required to get my samples working - so, while I had a function for React.DOM.div -
[Name("div")]
public extern static ReactElement Div(
HTMLAttributes properties,
params ReactElementOrText[] children
);
The HTMLAttributes class I had written really was the bare minimum:
[Ignore]
[ObjectLiteral]
public class HTMLAttributes
{
public string className;
}
It's time to revisit this and build up my bindings library!
An obvious resource to work from initially is the "DefinitelyTyped" bindings that allow you to use React from TypeScript. But I'd identified a pattern that I didn't like with them in my earlier post - the type system isn't being used to as full effect as it could be. For example, in the declaration of "input" elements. Let me explain (and please bear with me, I need to go through a few steps to get to the point)..
The TypeScript bindings describe a function for creating input elements:
React.DOM.input(props: HTMLAttributes, ...children: ReactNode[]): DOMElement
For any non-TypeScripters, this is a function that takes an argument named "props" that is of type HTMLAttributes, and then 0, 1, .. n arguments of type ReactNode that are wrapped up into an array (the same principle as "params" arguments in C#). It returns a DOMElement instance.
HTMLAttributes has 115 of its own properties (such as "className", "disabled" and "itemScope" - to take three at random) and extends DOMAttributes, which has 34 more properties (such as "onChange" and "onDragStart").
The "onChange" property is a FormEventHandler, which is derived from EventHandler<FormEvent>, where EventHandler<E> is a delegate which has a single "event" argument of type "E" which returns no value. It's a callback, in other words.
This looks promising and is, on the whole, a good use of TypeScript's generics system.
However, I don't think it uses this system enough. The FormEvent (that the "onChange" property passes in the callback) is a specialisation of a SyntheticEvent type:
interface FormEvent extends SyntheticEvent { }
interface SyntheticEvent {
bubbles: boolean;
cancelable: boolean;
currentTarget: EventTarget;
defaultPrevented: boolean;
eventPhase: number;
isTrusted: boolean;
nativeEvent: Event;
preventDefault(): void;
stopPropagation(): void;
target: EventTarget;
timeStamp: Date;
type: string;
}
(The EventTarget, which is what the "target" property is an instance of, is a DOM concept and is not a type defined by the React bindings, it just means that it is one of the DOM elements that are able to raise events).
The problem I have is that if we write code such as
React.DOM.input({
value: "hi"
onChange: e => { alert(e.target.value); }
})
Then we'll get a TypeScript compile error because "e.target" is only known to be of type EventTarget, it is not known to be an input element and so it is not known to have a "value" property. But we're specifying this "onChange" property while declaring an input element.. the type system should know that the "e.target" reference will be an input!
In fact, in TypeScript, we actually have to skirt around the type system to make it work:
// "<any>" means cast the "e.target" reference to the magic type "any", which
// is like "dynamic" in C# - you can specify any property or method and the
// compiler will assume you know what you're doing and allow it (resulting
// in a runtime exception if you get it wrong)
React.DOM.input({
value: "hi"
onChange: e => { alert((<any>e.target).value); }
})
In my React bindings for Bridge I improved this by defining an InputAttributes type:
[Ignore]
[ObjectLiteral]
public class InputAttributes : HTMLAttributes
{
public Action<FormEvent<InputEventTarget>> onChange;
public string value;
}
And having a generic FormEvent<T> which inherits from FormEvent -
[Ignore]
public class FormEvent<T> : FormEvent where T : EventTarget
{
public new T target;
}
This means that the "target" property can be typed more specifically. And so, when you're writing this sort of code in C# with Bridge.net, you can write things like:
// No nasty casts required! The type system knows that "e.target" is an
// "InputEventTarget" and therefore knows that it has a "value" property
// that is a string.
DOM.Input(new InputAttributes
{
value = "hi",
onChange = e => Global.Alert(e.target.value)
})
This is great stuff! And I'm not changing how React works in any way, I'm just changing how we interpret the data that React is communicating; the event reference in the input's "onChange" callback has always had a "target" which had a "value" property, it's just that the TypeScript bindings don't tell us this through the type system.
So that's all good.. but it did require me to write more code for the bindings. The InputEventTarget class, for example, is one I had to define:
[Ignore]
public class InputEventTarget : EventTarget
{
public string value;
}
And I've already mentioned having to define the FormEvent<T> and InputAttributes classes..
What I'm saying is that these improvements do not come for free, they required some analysis and some further effort putting into the bindings (which is not to take anything away from DefinitelyTyped, by the way - I'm a big fan of the work in that repository and I'm very glad that it's available, both for TypeScript / React work I've done in the past and to use as a starting point for Bridge bindings).
Seeing how these more focussed / specific classes can improve things, I come to my second problem with the TypeScript bindings..
The place that I wanted to start in extending my (very minimal) bindings was in fleshing out the HTMLAttributes class. Considering that it had only a single property ("className") so far, and that it would be used by so many element types, that seemed like a reasonable plan. But looking at the TypeScript binding, I felt like I was drowning in properties.. I realised that I wasn't familiar with everything that appeared in html5, but I was astonished by how many options there were - and convinced that they couldn't all be applicable to all elements types. So I picked one at random, of those that stood out as being completely unfamiliar to me: "download".
w3schools has this to say about the HTML <a> download Attribute:
The download attribute is new for the <a> tag in HTML5.
and
The download attribute specifies that the target will be downloaded when a user clicks on the hyperlink.
This attribute is only used if the href attribute is set.
The value of the attribute will be the name of the downloaded file. There are no restrictions on allowed values, and the browser will automatically detect the correct file extension and add it to the file (.img, .pdf, .txt, .html, etc.).
So it appears that this attribute is only applicable to anchor tags. Therefore, it would make more sense to not have a "React.DOM.a" function such as:
[Name("a")]
public extern static ReactElement A(
HTMLAttributes properties,
params ReactElementOrText[] children
);
and, like the "input" function, to be more specific and create a new "attributes" type. So the function would be better as:
[Name("a")]
public extern static ReactElement A(
AnchorAttributes properties,
params ReactElementOrText[] children
);
and the new type would be something like:
[Ignore]
[ObjectLiteral]
public class AnchorAttributes : HTMLAttributes
{
public string download;
}
This would allow the "download" property to be pulled out of HTMLAttributes (so that it couldn't be a applied to a "div", for example, where it has no meaning).
So one down! Many, many more to go..
Some properties are applicable to multiple element types, but these elements may not have anything else in common. As such, I think it would be more sensible to duplicate some properties in multiple attributes classes, rather than trying to come up with a complicated inheritance tree that tries to avoid any repeating of properties, at the cost of the complexities that inheritance can bring. For example, "href" is a valid attribute for both "a" and "link" tags, but these elements do not otherwise have much in common - so it might be better to have completely distinct classes
[Ignore]
[ObjectLiteral]
public class AnchorAttributes : HTMLAttributes
{
public string href;
public string download;
// .. and other attributes specified to anchor tags
}
[Ignore]
[ObjectLiteral]
public class LinkAttributes : HTMLAttributes
{
public string href;
// .. and other attributes specified to link tags
}
than to try to create a base class
[Ignore]
[ObjectLiteral]
public abstract class HasHrefAttribute : HTMLAttributes
{
public string href;
}
which AnchorAttributes and LinkAttributes could be derived from. While it might appear initially to make sense, I imagine that it will all come unstuck quite quickly and you'll end up finding yourself wanting to inherit from multiple base classes and all sorts of things that C# doesn't like. I think this is a KISS over DRY scenario (I'd rather repeat "public string href;" in a few distinct places than try to tie the classes together in some convoluted manner).
So, with more thought and planning, I think a reduced HTMLAttributes class could be written and a range of attribute classes produced that make the type system work for us. I should probably admit that I haven't actually done any of that further thought or planning yet! I feel like I've spent this month coming up with grandiose schemes and then writing about doing them rather than actually getting them done! :D
Anyway, enough about my shortcomings, there's another issue I found while looking into this "download" attribute. Thankfully, it's a minor problem that can easily be solved with the way that bindings may be written for Bridge..
There was an issue on React's GitHub repo: "Improve handling of download attribute" which says the following:
Currently, the "download" attribute is handled as a normal attribute. It would be nice if it could be treated as a boolean value attribute when its value is a boolean. ... For example,
a({href: 'thing', download: true}, 'clickme'); // => <a href="thing" download>clickme</a>
a({href: 'thing', download: 'File.pdf'}, 'clickme'); // => <a href="thing" download="File.pdf">
This indicates that
[Ignore]
[ObjectLiteral]
public class AnchorAttributes : HTMLAttributes
{
public string href;
public string download;
// .. and other attributes specified to anchor tags
}
is not good enough and that "download" needs to be allowed to be a string or a boolean.
This can be worked around by introducing a new class
[Ignore]
public sealed class StringOrBoolean
{
private StringOrBoolean() { }
public static implicit operator StringOrBoolean(bool value)
=> new StringOrBoolean();
public static implicit operator StringOrBoolean(string value)
=> new StringOrBoolean();
}
This looks a bit strange at first glance. But it is only be used to describe a way to pass information in a binding, that's why it's got the "Ignore" attribute on it - that means that this class will not be translated into any JavaScript by Bridge, it exists solely to tell the type system how one thing talks to another (my React with Bridge.net post talked a little bit about this attribute, and others similar to it, that are used in creating Bridge bindings - so if you want to know more, that's a good place to start).
This explains why the "value" argument used in either of the implicit operators is thrown away - it's because it's never used by the binding code! It is only so that we can use this type in the attribute class:
[Ignore]
[ObjectLiteral]
public class AnchorAttributes : HTMLAttributes
{
public string href;
public StringOrBoolean download;
// .. and other attributes specified to anchor tags
}
And this allows to then write code like
DOM.a(new AnchorAttributes
{
href: "/instructions.pdf",
download: "My Site's Instructions.pdf"
})
or
DOM.a(new AnchorAttributes
{
href: "/instructions.pdf",
download: true
})
We only require this class to exist so that we can tell the type system that React is cool with us giving a string value for "download" or a boolean value.
The "ObjectLiteral" attribute on these classes means that the code
DOM.a(new AnchorAttributes
{
href: "/instructions.pdf",
download: true
})
is not even translated into an instantiation of a class called "AnchorAttributes", it is instead translated into a simple object literal -
// It is NOT translated into this
React.DOM.a(
Bridge.merge(
new Bridge.React.AnchorAttributes(),
{ name: "/instructions.pdf", download: true }
)
)
// It IS just translated into this
React.DOM.a({ name: "/instructions.pdf", download: true })
Again, this illustrates why the "value" argument was thrown away in the StringOrBoolean implicit operator calls - because those calls do not exist in the translated JavaScript.
Another thing that I like about the "ObjectLiteral" attribute that I've used on these {Whatever}Attributes classes is that the translated code only includes the properties that have been explicitly set.
This means that, unlike in the TypeScript definitions, we don't have to declare all value types as nullable. If, for example, we have an attributes class for table cells - like:
[Ignore]
[ObjectLiteral]
public class TableCellAttributes : HTMLAttributes
{
public int colSpan;
public int rowSpan;
}
and we have C# code like this:
DOM.td(new TableCellAttributes { colSpan = 2 }, "Hello!")
Then the resulting JavaScript is simply:
React.DOM.td({ colSpan = 2 }, "Hello!")
Note that the unspecified "rowSpan" property does not appear in the JavaScript.
If we want it to appear, then we can specify a value in the C# code -
DOM.td(new TableCellAttributes { colSpan = 2, rowSpan = 1 }, "Hello!")
That will be translated as you would expect:
React.DOM.td({ colSpan = 2, rowSpan = 1 }, "Hello!")
This has two benefits, actually, because not only do we not have to mark all of the properties as nullable (while that wouldn't be the end of the world, it's nicer - I think - to have the attribute classes have properties that match the html values as closely as possible and using simple value types does so) but it also keeps the generated JavaScript succint. Imagine the alternative, where every property was included in the JavaScript.. every time a div element was declared it would have 150 properties listed along with it. The JavaScript code would get huge, very quickly!*
* (Ok, ok, it shouldn't be 150 properties for every div since half the point of this post is that it will be much better to create attribute classes that are as specific as possible - but there would still be a lot of properties that appear in element initialisations in the JavaScript which were not present in the C# code, it's much better only having the explicitly-specified values wind up in the translated output).
I was part way through writing about how pleased I was that unspecified properties in an [ObjectLiteral]-decorated class do not appear in the generated JavaScript when I decided to upgrade to Bridge 1.8 (which was just released two days ago).. and things stopped doing what I wanted.
With version 1.8, it seems like if you have an [ObjectLiteral] class then all of the properties will be included in the JavaScript - with default values if you did not specify them explicitly. So the example above:
DOM.td(new TableCellAttributes { colSpan = 2 }, "Hello!")
would result in something like:
React.DOM.td({
colSpan = 2,
rowSpan = 0,
id = null,
className = null,
// .. every other HTMLAttribute value here with a default value
},
"Hello!"
)
Which is a real pity.
The good news is that it appears to be as easy as also including an [Ignore] attribute on the type - doing so re-enables the behaviour that only includes explicitly-specified properties in the JavaScript. However, I have been unable to find authoritative information on how [ObjectLiteral] should behave and how it should behave with or without [Ignore]. I had a quick flick through the 1.8 release notes and couldn't see any mention of this being an explicit change from 1.7 to 1.8 (but, I will admit, I wasn't super thorough in that investigation).
I only came across the idea of combining [Ignore] with [ObjectLiteral] when I was looking through their source code on GitHub (open source software, ftw!) and found a few places where there are checks for one of those attributes or both of them in some places.
(I've updated the code samples in this post to illustrate what I mean - now anywhere that has [ObjectLiteral] also has [Ignore]).
I'm a little bit concerned that this may change again in the future or that I'm not using these options correctly, but I've raised a bug in their forums and they've been very good at responding to these in the past - ObjectLiteral classes generate values for all properties in 1.8 (changed from 1.7).
So.. how am I intending to progress this? Or am I going to just leave it as an interesting initial investigation, something that I've looked briefly into and then blogged about??
Well, no. Because I am actually planning to do some useful work on this! :) I'm a big fan of both React and Bridge and hope to be doing work with both of them, so moving this along is going to be a necessity as much as a nice idea to play around with. It's just a case of how to proceed - as the I-have-never-heard-of-this-new-download-attribute story goes to show, I'm not intimately familiar with every single tag and every single attribute, particular in regards to some of the less well-known html5 combinations.
Having done some research while writing this post, I think the best resource that I've found has been MDN (the Mozilla Developer Network). It seems like you can look up any tag - eg.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a
And then find details of every attribute that it has, along with compatibility information. For example, the td
table cell documentation..
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td
.. mentions "colSpan" and "rowSpan", with no particular mentions of compatibility (these have existed from day one, surely, and I don't think they're going to disappear any time soon) but also mentions attributes such as "align" and "valign" and highlights them as deprecated in html 4.01 and obsolete in html 5.
I'm strongly considering scraping these MDN pages and trying to parse out the attribute names and compatibility information (probably only supporting html5, since what's the point in supporting anything older when Bridge and React are new and and so I will be using them for writing new code and taking advantage of current standards). It doesn't provide type information (like "colSpan" is numeric or "download" may be a string or a boolean), but the DefinitelyTyped definitions will go some way in helping out with that. And MDN says that its wiki documents are available under the creative commons license, so I believe that this would acceptable use of the data, so long as they are given the appropriate credit in the bindings code that I will eventually generate (which only seems fair!).
So I think that that is what will come next - trying to glean all of the information I need about the attributes specific to particular tags and then using this to produce bindings that take as much advantage of the C# type system as possible!
Unless I'm missing something and someone else can think of a better way? Anyone??
Update (8th October 2015): I've had some suggestions from a member of the Bridge.net Team on how to reuse some of their work on html5 element definitions to make this a lot easier - so hopefully I'll have an update before too long based upon this. Before I can do so, the Bridge Team are looking into some improvements, such as allowing the "CurrentTarget" property of elements to be more strongly-typed (see http://forums.bridge.net/forum/general/feature-requests/630-open-461-generic-html5-element-and-event-classes), but hopefully we'll all have an update before too long!
Posted at 23:07
23 July 2015
A couple of months ago I talked about using DuoCode to write React applications in C#. Since then I've been waiting for DuoCode to release information about their licensing and it's been a case of "in a few days" for weeks now, so I've been looking around to if there are any alternatives. A few weeks ago I tried out Bridge.net which is an open source C#-to-JavaScript translator. It's been public for about the same length of time as DuoCode and releases have been about one a month, similar to DuoCode.
Its integration with Visual Studio doesn't have a dedicated project type option and it doesn't support the debug-in-VS-when-running-in-IE facility that DuoCode has. The compiler doesn't seem to be quite as mature as DuoCode's, I've found a few bugs with it.. but I've reported them on their forum and they've addressed them all! I reported one bug in their forum and got a reply 14 minutes later saying that a fix had been pushed to GitHub - not bad service, that! :D
It currently uses something called NRefactory to parse C# but it's moving over to Roslyn soon*. It has recently acquired Saltarelle, another open source C#-to-JavaScript translator, so now has even more resources working on it. Enough introduction, though, you can find out more on their website if you want to!
* (It seems that with the release of Visual Studio 2015 and with Roslyn hitting the mainstream, Mono is another project moving over from using NRefactory to Roslyn for its C# parsing, read more from Miguel de Icaza at Roslyn and Mono - I'm excited by this since I think Roslyn is really going to open some doors for C#'s extensibility in the future; Bridge, Mono and DuoCode are all examples of this.. I've also used it for some simple code analysis in the past, see Locating TODO comments with Roslyn).
One of the nice things about DuoCode is that it made it extremely easy to get started - you can create a new DuoCode project and run it directly. I believe that the Visual Studio project system has historically been a bit of a mess so there must have been a lot of work gone into this to make it so seamless.
Bridge is a bit different, though. The simplest way to get started is to create a new Class Library project and add the Bridge.net NuGet package. This will not make it directly executable but it does add a demo.html file (under "Bridge/www") that you can browse to (either by locating it in the file system or by just right-clicking on it and selecting "View in Browser").
With DuoCode, you have the option of splitting your work over multiple projects - the "runnable" project can reference one or more other DuoCode projects and everything will still work just fine. The problem may come, however, if you want your main project to also host some .net code - you might want to create a Web API interface, for example, for the client-side code to call. But it's not possible to have a single project that translates some of its C# into JavaScript but also leaves some of it running as .net on the server.
In Bridge, I would recommend creating a standard ASP.net MVC web project and then adding one or more Bridge class libraries that copy their generated JavaScript into a location in the front end project. Bridge makes this easy as each project that pulls in the NuGet package creates a file "Bridge/bridge.json" which allows you to specify where the output JavaScript should go. (Currently, adding the NuGet package always pulls in the "getting started" folders, such as "Bridge/www", but I'm hopeful that a just-the-basics package will be available before too long - there's a forum thread I started about it on their site: Refining the NuGet package).
I'm going to assume that you're familiar with React and, to make my life easier in writing this article as much as anything, I'm going to assume no knowledge of my DuoCode/React post. I've written before about writing React components in TypeScript and the same sort of concepts will be covered here - but, again, I'm going to assume you haven't read that and I'll start from scratch.
I'll start from the "React.render" method and work out from there. So first we need to write a binding to it. A binding is essentially a way to tell your C# at compile time about something that will be present at runtime.
To define "React.render" I'll create a class with an "Ignore" attribute on it - this instructs the translator that it does not need to generate any JavaScript from this C# class, it will be tied up to JavaScript already loaded in the browser (the assumption here, of course, is that the page that will execute the JavaScript translated from C# will also include the "React.js" library).
using Bridge.Html5;
namespace Bridge.React
{
[Name("React")]
[Ignore]
public static class React
{
[Name("render")]
public extern static void Render(ReactElement element, Element container);
}
}
Any references in the C# code to this class will try to access it within the "Bridge.React" namespace, so the C# compiler thinks its fully qualified class name is "Bridge.React.React"; in the JavaScript, though, this is not the case, the class is just called "React". The Bridge.net attribute "Name" allows us to tell the translator this - so when the C# code is translated into JavaScript, any references to "Bridge.React.React" will be rewritten to simply "React".
The same is applied to the method "Render" - in my C# code, I stick to the convention of pascal-casing function names, but the React library (like much other modern JavaScript) uses camel-cased function names.
Also note that the "Render" method does not have an implementation, it is marked as "extern". This is more commonly used in C# code to access unmanaged DLL functions but the bindings to JavaScript that we're describing are a similar mechanism - no body for this method needs to be included since the method itself is implemented elsewhere (in the React library, in this case). In order to be marked as "extern", a function must be decorated with an attribute (otherwise the compiler generates warnings). It doesn't matter what attribute is used, so the presence of the "Name" attribute is good enough.
For the function's arguments, the Element type is a Bridge.net class that represents DOM elements while the ReactElement is one that we must define as a binding -
namespace Bridge.React
{
[Ignore]
public sealed class ReactElement
{
private ReactElement() { }
}
}
This will never be explicitly instantiated, so it's a sealed class with a private constructor. It also does not need to be directly represented in the generated JavaScript, so it has an "Ignore" attribute on it too.
But, if ReactElement is never directly instantiated, how do we get instances of it? Well, there's the builtin React element creator functions (eg. "React.DOM.div", "React.DOM.span", etc..) and there are custom components. The builtin functions are nice and simple to writing bindings for, so I'll talk about them later and get to the interesting stuff..
Since React 0.13 was released earlier this year, it has been possible to write JavaScript "classes" that may be used as React components. I write "classes" in quotes because a class is an ES6 concept but the code that transpilers emit (that take ES6 JavaScript and create equivalent code that will work on browsers that don't support ES6) is similar enough to the code generated by Bridge.net (and DuoCode) that we can use classes written in C# and rely upon the emitted JavaScript working nicely with React.. so long as a couple of rules are followed:
The surgery that React carries out to create components based upon classes is quite invasive, not only in how it new's-up an instance but also in how it investigates the "props" data. For each initialisation, it tears apart the provided props reference and creates a new object of its own devising, copying over the object properties from the original reference onto its own version.
This is problematic if the props reference you give is a class translated from C# since any methods (including property getters / setters) will be ignored by this process (React only copies properties that report true for "hasOwnProperty" and so ignores any class methods since these will be declared on the prototype of the class, not on the instance itself). The way to get around this is to wrap the props data in another object, one that has only a single field "Props", which is a reference back to the original props data - then, when React copies the properties around, this value will be migrated over without issue. The only annoyance is that this data must be unwrapped again before you can use it.
However, because I'm so good to you, I've pulled most of this together into an abstract class that you may use to derive custom component classes from -
using System;
namespace Bridge.React
{
public abstract class Component<TProps>
{
public static ReactElement New<TComponent>(TProps props)
where TComponent : Component<TProps>
{
if (props == null)
throw new ArgumentNullException("props");
return Script.Call<ReactElement>(
"React.createElement",
typeof(TComponent),
new PropsWrapper { Props = props }
);
}
[Name("render")]
public abstract ReactElement Render();
[Name("unwrappedProps")]
protected TProps props
{
get { return wrappedProps.Props; }
}
[Name("props")]
private readonly PropsWrapper wrappedProps = null;
[ObjectLiteral]
private class PropsWrapper
{
public TProps Props;
}
}
}
There is a static "New" method that is used to create a ReactElement instance based upon the derived component class' type (using the "React.createElement" function). In the C# code, you call "New" with a "props" reference of the appropriate type and you get a ReactElement back. The "appropriate type" is the TProps generic type parameter of this base class. Making this a generic class means that calling code knows what data is required to create a new element instance (ie. a TProps instance) and the derived component class has a strongly-typed (again, TProps) reference to that props data.
There is a "Render" method that must be implemented on the derived class. It has a "Name" attribute to map my C# naming convention (of pascal-cased function names) onto React's convention (of camel-cased function names).
You can also see some messing about with the props data - the call to "React.createElement" uses a PropsWrapper which puts the "props" reference into a single property, as I described above. This will not be torn to shreds by React when it creates a new element. This data is then readable via the private "wrappedProps" property - since this is private to this base class, derived classes can not access it. They have to retrieve props data through the "props" property (which has type TProps, rather than PropsWrapper).
Now, there is some jiggery-pokery here, since the "wrappedProps" property has a "Name" attribute which indicates that this maps to a property called "props" in the JavaScript - but, as just described, this is a wrapper that keeps the real data safe from React's meddling. Similarly, the "props" property (which has "protected" accessibility and so is accessible by the derived type) has a "Name" attribute which maps it onto a property named "unwrappedProps" - this is not something that React has anything to do with, it's only to avoid clashing with the real "props" reference in the output JavaScript, while allowing C# code to be written that accesses a "props" property that is an unwrapped version of the props data.
Wow, that all sounds really confusing! But the upshot is that you can create a component as easily as:
using Bridge.React;
namespace BridgeExamples
{
public class WelcomeComponent : Component<WelcomeComponent.Props>
{
public static ReactElement New(Props props)
{
return New<WelcomeComponent>(props);
}
private WelcomeComponent() { }
public override ReactElement Render()
{
return DOM.Div(null, "Hi " + props.Name);
}
public class Props
{
public Props(string name)
{
Name = name;
}
public string Name { get; private set; }
}
}
}
Note that the "Render" method accesses "props.Name" directly - all of the wrapping / unwrapping is hidden away in the base class, the derived class is happily oblivious to all that PropsWrapper madness.
Also note that the constructor has been declared as private, which is important since these components should never be instantiated directly - they should only be brought into existence via that "React.createElement" function. To this end, a static "New" function is present (which calls the static "New" function on the base class).
(One more note - there's an attribute used by the Component base class that I haven't explained: "ObjectLiteral", I'll come back to this later..)
Bringing all this together, we could use this component in code such as:
React.Render(
WelcomeComponent.New(new WelcomeComponent.Props(name: "Ted")),
Document.GetElementById("main")
);
But before doing so, we'd better get back to the builtin React element creation functions..
Warning (July 2015): The code above will actually fail at runtime with the current version of Bridge (1.7) because of a bug I reported in their forums - however this has been fixed and will be included in the next release. I'm being optimistic and hoping that this article will be relevant in the future when this bug is just a distant memory :) Until the version after 1.7 is released, replace the line
return New<WelcomeComponent>(props);
with
return Component<WelcomeComponent.Props>.New<WelcomeComponent>(props);
and it will work fine. But in the future the less verbose version shown in the WelcomeComponent example will be safe.
I skirted around the DOM functions such as React.DOM.div, React.DOM.span, etc.. earlier, so let's deal with them now.
In order to do so, I need to teach the type system a little trick. A component's "Render" method must always return a genuine ReactElement, but when specifying child elements for use in a component we must be able to provide either a ReactElement or a simple string. This is what was happening in the line from the WelcomeComponent:
return DOM.Div(null, "Hi " + props.Name);
The null value is for the attributes of the div (and is interpreted as "this div does not need any html attributes"), while the second argument is a string that is the sole child element of that div.
So we need a way to say that the child element argument value(s) for a DOM.div call may be either a ReactElement or a string. In some languages (such as TypeScript and F#), we could consider a union type, but here I'll cheat a bit and declare the "React.DOM.Div" function as:
namespace Bridge.React
{
[Name("React.DOM")]
[Ignore]
public static class DOM
{
[Name("div")]
public extern static ReactElement Div(
HTMLAttributes properties,
params ReactElementOrText[] children
);
and then define a new ReactElementOrText type -
namespace Bridge.React
{
[Ignore]
public sealed class ReactElementOrText
{
private ReactElementOrText() { }
[Ignore]
public extern static implicit operator ReactElementOrText(string text);
[Ignore]
public extern static implicit operator ReactElementOrText(ReactElement element);
}
This is another class with an "Ignore" attribute since it does not require any JavaScript to be generated for it, it's just to provide information to the C# compiler. It means that anywhere that a ReactElementOrText type is specified as an argument type, it is valid to provide either a ReactElement or a string. (The "Ignore" attributes on the functions are only present so that they may be identified as "extern" - as described earlier, an "extern" function must be decorated with an attribute of some kind).
The DOM class makes further of the "Name" attribute, ensuring that any accesses in C# to "Bridge.React.DOM" are replaced with just "React.DOM" in the generated JavaScript.
The final piece of the puzzle is the HTMLAttributes class -
using Bridge;
namespace Bridge.React
{
[ObjectLiteral]
public class HTMLAttributes
{
public string className;
}
}
As you can probably tell, this is not the full-featured React interface ("className" is not the only attribute that you can add to a div!) - at this point in time, it's still early days for the bindings that I'm writing for Bridge.net / React, so I've cut some corners. You'll see further down that I've not done all of the bindings for the DOM elements either! But hopefully this article is enough to get you up and running, then you can add to it yourself as you need more. (I'll also link to a repo at the end of the post..)
HTMLAttributes is another type that we don't directly want to include in the generated JavaScript. If we have the C# line -
return DOM.Div(new HTMLAttributes { className = "welcome" }, "Hello!");
we want this to be translated into the following:
return React.DOM.div({ className = "welcome" }, "Hello!");
This is idiomatic React-calling code.
Bridge.net has support to instruct the compiler to interpret class constructors in this way; through use of the "ObjectLiteral" attribute. When a class that is decorated with this attribute has its constructor called in C#, the generated JavaScript will just use the object literal notation rather than actually creating a full class instance. Note that this means that no JavaScript will be generated for that class, so the HTMLAttributes class does not appear in the JavaScript at any point.
We saw this "ObjectLiteral" attribute used earlier, on the Component base class - it was used for the wrapper around the props data. That wrapper is only used to nest the real "props" reference inside a property so that React's internals don't try to mess with it. It would be unnecessary to wrap that reference up inside a full class instance, it is more sensible for the generated JavaScript to be simply -
return React.createElement(TComponent, { props: props });
as opposed to something like
return React.createElement(
TComponent,
Bridge.merge(
new Bridge.React.Component.PropsWrapper$1(TProps)(),
{ props: props }
)
);
(which is what Bridge would emit if the PropsWrapper type did not have the "ObjectLiteral" attribute on it).
As with the poorly-populated HTMLAttributes class, I also haven't written too many DOM element bindings yet. All I've got so far is "div", "h1", "input" and "span" - these have been enough for the sample projects I've started to experiment with integrating Bridge and React. However, the good news is that these few bindings illustrate enough principles that it should be clear how to write more as you need them.
The element creation functions that I have are as such:
using Bridge;
namespace Bridge.React
{
[Name("React.DOM")]
[Ignore]
public static class DOM
{
[Name("div")]
public extern static ReactElement Div(
HTMLAttributes properties,
params ReactElementOrText[] children
);
[Name("h1")]
public extern static ReactElement H1(
HTMLAttributes properties,
params ReactElementOrText[] children
);
[Name("input")]
public extern static ReactElement Input(
InputAttributes properties,
params ReactElementOrText[] children
);
[Name("span")]
public extern static ReactElement Span(
HTMLAttributes properties,
params ReactElementOrText[] children
);
}
}
"Span" and "H1" are just the same as "Div".
"Input" is interesting, though..
I started by following the same basic pattern for the InputAttribute type as you would find in the React bindings for TypeScript - there is an "onChange" callback which provides a FormEvent instance, which is a type that is derived from SyntheticEvent (names taken directly from the TypeScript binding).
The big difference is that the TypeScript bindings do not expose a "target" property on FormEvent to tie the event back to the element that it relates to, even though this information is available on the React event. I've exposed this information as the "target" property on FormEvent and exposed it as a known-type using the generic FormEvent class - the InputAttributes has an "onChange" type of Action<FormEvent<InputEventTarget>> which means that the type of "target" on the FormEvent will be an InputEventTarget. This allows us to query the input's value in the onChange callback, something that is a bit awkward with the TypeScript bindings.
(This is only possible because the React model exposes this information - bindings do not include their own logic, they only describe to the Bridge compiler how to interact with an existing library).
using Bridge;
namespace Bridge.React
{
[ObjectLiteral]
public class InputAttributes : HTMLAttributes
{
public Action<FormEvent<InputEventTarget>> onChange;
public string value;
}
[Ignore]
public class FormEvent<T> : FormEvent where T : EventTarget
{
public new T target;
}
[Ignore]
public class FormEvent : SyntheticEvent
{
public EventTarget target;
}
[Ignore]
public class SyntheticEvent
{
public bool bubbles;
public bool cancelable;
public bool defaultPrevented;
public Action preventDefault;
public Action stopPropagation;
public string type;
}
[Ignore]
public class InputEventTarget : EventTarget
{
public string value;
}
[Ignore]
public class EventTarget { }
}
There are obviously more properties that belong on the InputAttribute type (such as "onKeyDown", "onKeyPress", "onKeyUp" and many others) but for now I just wanted to write enough to prove the concept.
There's nothing that requires that you use the Flux architecture when you're using React, but I think it makes a huge amount of sense. When I was getting to grips with the React library, I found myself moving towards a similar solution before I'd read up on Flux - so when I did read about it, it was like someone had taken the vague ideas I'd had, tightened them all up and hammered out the rough edges.
The thing with it is, it really is just a set of architectural guidelines, it's not an actual library like React. It explains that you have "Stores" that manage state for the application - these Stores listen out for events that may require that they change their current state. This state is displayed using React, basically. The events that the Stores listen out for may be the result of a user interacting with the React elements or they may be raised by an API, informing the application that data that the user requested has arrived.
The piece in the middle is the "Dispatcher" and is effectively a queue that events can be pushed on (by user interactions or whatever else) and listened out for by the Stores - any events that a Store is not interested in, it ignores.
Asynchronous processes are quite easily handled in this arrangement because you can fire off an event as soon as an async request starts (in case some sort of loading spinner is desired) and then fire off another event when the data arrives (or when it times out or errors in some other way). Whatever happens, it's just events being raised and then received by interested Stores; Stores which alter their state and get React to re-render (if state changes were, in fact, required).
Using C# to write such a Dispatcher queue is really easy. Internally, it uses a C# event to make it easy to register (and then to call) as many listeners as required.
When compiled for the CLR, events are translated into IL that does all sorts of clever magic to behave well (and efficiently) in a multi-threaded environment. But here, it will be translated into JavaScript for the browser, which is single-threaded! So everything's very simple in the generated JavaScript and the C#.
using System;
namespace Bridge.React
{
public class AppDispatcher
{
private event Action<DispatcherMessage> _dispatcher;
public void Register(Action<DispatcherMessage> callback)
{
_dispatcher += callback;
}
public void HandleViewAction(IDispatcherAction action)
{
if (action == null)
throw new ArgumentNullException("action");
_dispatcher(new DispatcherMessage(MessageSourceOptions.View, action));
}
public void HandleServerAction(IDispatcherAction action)
{
if (action == null)
throw new ArgumentNullException("action");
_dispatcher(new DispatcherMessage(MessageSourceOptions.Server, action));
}
}
public class DispatcherMessage
{
public DispatcherMessage(MessageSourceOptions source, IDispatcherAction action)
{
if ((source != MessageSourceOptions.Server) && (source != MessageSourceOptions.View))
throw new ArgumentOutOfRangeException("source");
if (action == null)
throw new ArgumentNullException("action");
Source = source;
Action = action;
}
public MessageSourceOptions Source { get; private set; }
public IDispatcherAction Action { get; private set; }
}
public enum MessageSourceOptions { Server, View }
public interface IDispatcherAction { }
}
When you read about Flux events in JavaScript-based tutorials, you will see mention of "Actions" and "Action Creators". Actions will be JavaScript objects with some particular properties that describe what happened. Action Creators tend to be functions that take some arguments and generate these objects. In C#, I think it makes much more sense for Actions to be classes - that way the Action Creators are effectively just the Actions' constructors!
The AppDispatcher I've included code for above expects actions to be classes that share a common interface: IDispatcherAction. This is an "empty interface" and is just used to identify a class as being an action (you might want to search a solution for all Dispatcher actions, for example - you could do this by locating all implementations of IDispatcherAction). I wrote about this in terms of TypeScript earlier in the year and much of the same holds true here - so read more at TypeScript classes for (React) Flux actions if this sounds interesting (or confusing).
(Each action is wrapped in a DispatcherMessage, which describes it as being either a "Server" or "View" action. A Server action would tend to be a callback from an API event, as opposed to a key press that alters a particular editable field - which would be a View action. Differentiating between the two is a common practice but is optional, so you could drop the DispatcherMessage entirely if you wanted to and have the Dispatcher only deal with IDispatcherAction implementations. The only really compelling reasons I've encountered for differentiating between the two is that you might want to skip some forms of validation for Server actions since it may be presumed that data from the server is already known to be good).
If you want to try any of this out (particularly if you don't feel like trying to piece it all together from the various code samples in this post!) then you can find a complete sample in a Bitbucket repo: ReactBridgeDotNet.
It's a web project that follows the structure I outlined at the top of this post - an MVC project to "host" and then Bridge projects to generate JavaScript from C#; one for the React bindings and another for the logic of the app.
The app itself is very, very simple - it's just a text box that doesn't want to be empty. If you clear it then you get a validation message. Any changes that you make to the input box are communicated to the app as Dispatcher actions, each of which results in a full re-render (in a complex app, React's virtual DOM ensures that this is done in an efficient manner, but in such a small example as this it's probably impossible to tell). Meanwhile, a label alongside the input box is updated with the current time - this is done to illustrate how non-user events (such as API data-retrieved events, for example) may be dealt with in exactly the same way as user events; they go through the Dispatcher and a Store deals with applying these channels to its internal state. Have fun!
Posted at 21:17
26 May 2015
Back in January, I heard about DuoCode and signed up to be part of the beta program when they started it. It takes C# code, analyses it with Roslyn and then generates JavaScript to run in the browser. It promises to offer all of the benefits of statically-typed C# in Visual Studio for writing browser code. I didn't actually get round to doing any more than installing an early version during the closed beta period, but recently they opened up the beta product to the general public and I got around to trying it out properly. Trying to not go over the top here.. it's absolutely fantastic!
The compilation process has been really fast on the (admittedly small) projects I've been trying out and the translation itself seems faultless - I've tried to catch it out in a variety of ways, seeing if it could handle "ref" or "out" arguments, for example (since JavaScript has no concept of this), playing with generics, trying out LINQ statements, generating anonymous types.. it really does seem to do it all! Suffice to say, I'm very impressed with what I've seen.
But.. (there's always a but).. they currently say on their FAQ page, under "How much does DuoCode cost?" -
A pricing model will be introduced in the future, after the Beta period is over.
This is quite a concern since there's literally no indication as to whether they will be expecting to charge for personal use, for commercial use, per-seat or per-company for commercial use, whether there will be a freemium model where the "basics" are free but additional features are paid-for.. or something else entirely. And there's also no indication of when the beta period will actually be over. Bummer. Particularly since I'm so excited by it that I seriously want to push it for a project at work, but if it's going to be prohibitively expensive or if its stability is going to be a problem (or if there's a chance that it will never reach 1.0) then it's going to be difficult to do some in good conscience.
But let's brush all that aside for now and get back to some positives. Like I said, the way that this all works is phenomenal (even the JavaScript itself that is generated is really good - both from the source code you write and in the support library that recreates System, System.Collections, System.Linq, etc..) and I wanted to try it out in a few scenarios.. particularly with my other current technical loves; React and Flux.
Spoiler alert: DuoCode and React are an incredible match and I can't recommend highly enough that you try them out!
I've written previously about using TypeScript with React (in TypeScript / ES6 classes for React components and TypeScript classes for (React) Flux actions) so I've got some experience that I'd hoped to be able to apply. Integrating React with TypeScript, there are two major challenges: Firstly, creating a base "React Component" class that may be derived from to create custom components. And secondly, getting TypeScript definitions for the React library.
The same two challenges apply to working with React from DuoCode.
The biggest issue with using classes as React components is that React's render methods all expect a React "Element" and not just a generic instance of whatever. So we can't just define a class with a "render" method and pass it into React; the React library's "createElement" method must be used to prepare an instance for use as a React component.
In TypeScript, I addressed this by having each component file be an AMD module that defined a class for the component model but that actually exported a "Factory" function that would instantiate the class, given the "props" reference it would need - eg.
import React = require('react');
interface Props { name: string; role: string; }
class PersonDetailsComponent extends React.Component<Props> {
constructor(props: Props) {
super(props);
}
public render() {
return React.DOM.div(null, this.props.name + " is a " + this.props.role);
}
}
function Factory(props: Props) {
return React.createElement(PersonDetailsComponent, props);
}
export = Factory;
The "React.Component" class that it is derived from is a class within the React library, exposed as a generic class (whose type parameter is the data type of the "props" reference for the component). This base class is accessible due to the third party TypeScript definition (I'll talk about this more shortly).
The gist is that React library function "createElement" must be used to initialise Components - it gets passed the Component class' constructor and the "props" reference to pass into the constructor, but the actually calling of that constructor is not performed explicitly.
Doing exactly this in C# would be.. challenging. And not particularly idiomatic.
What I've ended up with instead is the following:
public class ExampleComponent : ComponentWrapper<ExampleComponent.Props>
{
public static Element New(Props props) { return Ele.Props(props).As<ExampleComponent>(); }
private ExampleComponent(ComponentProps<Props> props) : base(props) { }
public override Element Render()
{
return DOM.div(null, props.Props.Name);
}
public class Props
{
public string Name;
}
}
A Component class that inherits from a base class, again with a generic type parameter for the "props" type. The constructor is private since it will never be called directly from consuming code - instead there is a static "New" function that does.. magic. There's actually several things to walk through here, so let me take it one step at a time.
The "ComponentWrapper" is a DuoCode binding, meaning it's not a class that the DuoCode compiler has to translate into JavaScript. Instead, it's a way to tell the type system about a type that will be available at runtime. It looks like this:
[Js(Extern = true, Name = "ReactComponentWrapper")]
public abstract class ComponentWrapper<TProps>
{
protected readonly ComponentProps<TProps> props;
protected ComponentWrapper(ComponentProps<TProps> props) { }
[Js(Name = "render")]
public abstract Element Render();
}
[Js(Extern = true, Name = "ReactComponentWrapper")]
public static class ComponentWrapper
{
[Js(OmitGenericArgs = true)]
public extern static Element GetElement<TProps>(Type componentType, TProps props);
}
The C# extern keyword "is used to declare a method that is implemented externally" - usually this is used to import functions from dlls, but here it's used to indicate that it will be implemented by runtime JavaScript. The "Js" attribute is a DuoCode construct that allows structures to be identified as being implemented externally; in other words, that the DuoCode compiler need not try to generate corresponding JavaScript. It also allows for a different reference name to be used at runtime than the class would otherwise indicate - so instead of "MyDuoCodeProject.ComponentWrapper", it will use the name "ReactComponentWrapper" in the final JavaScript.
So what is this "ReactComponentWrapper" class that will be present at runtime? Well, you'll have to reference an additional JavaScript file in your index.html, with the following content:
window.ReactComponentWrapper = (function (_super) {
function Component(props) {
_super.call(this, props, null);
}
Component.ctor = Component;
Component.GetElement = function (componentType, props) {
return React.createElement(componentType.self.ctor, { Props: props });
};
return Component;
})(React.Component);
This base class hooks up the C# Component class so that it inherits from the "React.Component" class in the React library, just like how the TypeScript class is derived from the React.Component library class. There are two things to note; when DuoCode generates JavaScript that instantiates classes, it always does it through a "ctor" function, so a "ctor" is required on the "ReactComponentWrapper" that is an alias onto its primary constructor. And a "GetElement" function is defined that takes a DuoCode Component class and the props reference that should be passed into a constructor on that type - from these it returns a React "Element" by calling the library's "createElement" method. This method is also declared in the "ComponentWrapper" binding.
With all this, it would be possible to create a React "Element" from the Component class by calling
var element = ComponentWrapper.GetElement<ExampleComponent.Props>(
typeof(ExampleComponent),
new ExampleComponent.Props { Name = "test" }
);
.. but I thought that would be a bit unwieldy every time it was required - it needs "ExampleComponent" to be typed three times!
To make it a little less arduous, I've created a helper class -
public static class Ele
{
public static ElementFactory<TProps> Props<TProps>(TProps props)
{
if (props == null)
throw new ArgumentNullException("props");
return new ElementFactory<TProps>(props);
}
public class ElementFactory<TProps>
{
private readonly TProps _props;
public ElementFactory(TProps props)
{
if (props == null)
throw new ArgumentNullException("props");
_props = props;
}
public Element As<TComponent>() where TComponent : ComponentWrapper<TProps>
{
return ComponentWrapper.GetElement(typeof(TComponent), _props);
}
}
}
C# supports generic type parameter inference in method calls, so long as only a single type parameter needs to be resolved. This means that the call
Ele.Props<TProps>(props)
can be reduced to
Ele.Props(props)
and "TProps" will be inferred to be whatever the type of "props" is. Then the call
Ele.Props(props).As<TComponent>()
will know that "TProps" in the type constraint "TComponent : ComponentWrapper<TProps>" is whatever the type of "props" was.
It's a bit back-to-front, specifying the "props" before declaring the type of the Component class, but it's succinct at least! And it's what allows the Component classes' static "New" factory methods to be declared thusly:
public class ExampleComponent : ComponentWrapper<ExampleComponent.Props>
{
public static Element New(Props props) { return Ele.Props(props).As<ExampleComponent>(); }
private ExampleComponent(ComponentProps<Props> props) : base(props) { }
// .. rest of the Component goes here ..
In other words, instead of having to type "ExampleComponent" three times, you only to do so once :)
On the whole, I've found this to be a really good system. But there are a couple of small compromises. You need to explicitly reference the "ReactComponentWrapper.js" file in your page. I wish that there was a way to declare some JavaScript within .net code that will be included in the final output unaltered, this would make it really easy to have a separate project (or to create a NuGet package) for the React integration containing the base "ReactComponentWrapper" JavaScript class, the C# "ComponentWrapper" class, the helper classes (such as "Ele") and the bindings to the React library (again, more on this coming up). I haven't found a way to do this yet, though.
Secondly, due to the way that the "createElement" function works in the React library, the Component class constructors may only take a single argument (the "props" data). This means that all configuration data must be included in the "props" type (but this is the standard arrangement for React component classes, so it's no biggie).
The third and final compromise is that the "createElement" function does something a bit wonky with the "props" reference handed to it, it basically strips it down and rebuilds it, maintaining only properties declared on the object itself and not on a prototype of that object. So if your props object was a class with fields - eg.
public class Props
{
public string Name { get; set; }
}
then you would have problems, since DuoCode will generate a JavaScript object with get_Name and set_Name functions on the Props prototype (which is best practices for JavaScript, it means that the functions themselves are shared between all instance of the class, rather than there being identical copies of the functions present on every Props instance) - "createElement" will loop over the properties and anything that doesn't return true for "hasOwnProperty" will be lost, so the "get_Name" and "set_Name" functions will go astray. This will only become clear when code in your C# Components tries to access the Name property and fails at runtime.
The workaround for this is to wrap the "props" reference in a container object, since "createElement" only enumerates (and interferes with!) the top level properties. That's why the "GetElement" function in the "ReactComponentWrapper" looks like this:
Component.GetElement = function (componentType, props) {
return React.createElement(componentType.self.ctor, { Props: props });
};
It gets a "props" reference and it wraps it up so that React doesn't try to hurt it. And that's why the C# "ComponentWrapper" exposes the "props" data through a protected property thusly:
// Note: The "props" property is a wrapped ComponentProps<TProps> instance,
// rather than just being TProps
protected readonly ComponentProps<TProps> props;
The "ComponentProps" class looks like this:
[Js(Extern = true)]
public sealed class ComponentProps<T>
{
private ComponentProps() { }
[Js(Extern = true, Name = "Props")]
public T Props { get; }
}
It's just a way to tell the C# type system about this level of indirection around the "props" data, and it's why the "render" method in the example Component above looks like this:
public override Element Render()
{
return DOM.div(null, props.Props.Name);
}
But, really, you don't need to worry about this! With this system in place, you can just take it for granted that it works, and if you forget that you need to access "props.Props.Name" instead of "props.Name" then the compiler will give you an error reminding you that you've made a mistake! I do love static analysis :)
If you've got through the above, then you can probably imagine where I'm going next. We can define and create Components now, but we haven't got any way to actually call "React.render" or to use any of the builtin Component initialisers, such as "React.DOM.div".
To do this, more "bindings" are required - these are the classes and functions marked as "extern" / "[Js(extern = true)]" that tell the C# type system how to connect to JavaScript that is known to be present at runtime. As I said before, they're basically the equivalent of TypeScript type definitions but with the added bonus that it's possible to give the classes and functions aliases so that they fit more neatly into your project structure and naming convention (the DuoCode compiler will ensure that these aliases are mapped back to the original function names in the final JavaScript).
So let's deal with the most obvious one first:
[Js(Extern = true, Name = "React")]
public static class React
{
[Js(Extern = true, Name = "render")]
public extern static void Render(Element element, HTMLElement container);
}
There's a few things to talk about here. I've named the function "Render" and used the "Js" attribute to map it back on to the library function "render" - note the change in case. In the code that I write, C# functions are pascal-cased, so I wanted to be able to call "Render" from my C# code, rather than "render". The "HTMLElement" type is a DuoCode class, used to describe DOM elements (if you call "Global.window.document.getElementById(x)" then you get one of these back). This is just what React wants for the second argument of the "render" call; a DOM element, so that's great. But for the first argument, the "Element" type is a React type that needs a binding -
[Js(Extern = true)]
public sealed class Element
{
private Element() { }
public extern static implicit operator Element(string text);
}
This is not something that we're ever going to instantiate directly, so it is sealed and its constructor is private. This is used to describe the return type of the "createElement" function, and so is used as the return type from the "Ele.Props(props).As
There is one other way that it comes into play, however - there is an implicit conversion from the string type. This is required because strings are frequently used as child elements in React - eg.
React.DOM.span({ className: "greeting" }, "Hi!")
The implicit operator here, as part of an "extern" class, simply serves to tell the compiler that it's ok to use a string anywhere that a React Element is required - it doesn't have to do anything special, it just has to allow it. The consistency of the DuoCode translation process and the flexibility of the C# language really play together beautifully here and I was delighted to see how easily it was possible to dictate this to the type system.
Speaking of the React.Dom class.. So far, I've only covered a fraction of the total library, but hopefully it's enough to make it clear how it may be expanded on (and I intend to add support for element types as and when I need them).
[Js(Extern = true, Name = "React.DOM")]
public static class DOM
{
public extern static Element div(HTMLAttributes properties, params Element[] children);
public extern static Element h1(HTMLAttributes properties, params Element[] children);
public extern static Element input(InputAttributes properties, params Element[] children);
public extern static Element span(HTMLAttributes properties, params Element[] children);
}
public class HTMLAttributes
{
public string className;
}
public class InputAttributes : HTMLAttributes
{
public Action<FormEvent<InputEventTarget>> onChange;
public string value;
}
[Js(Extern = true)]
public class FormEvent<T> : FormEvent where T : EventTarget
{
[Js(Name = "target")]
public new T target;
}
[Js(Extern = true)]
public class FormEvent : SyntheticEvent
{
[Js(Name = "target")]
public EventTarget target;
}
[Js(Extern = true)]
public class SyntheticEvent
{
public bool bubbles;
public bool cancelable;
public bool defaultPrevented;
public Action preventDefault;
public Action stopPropagation;
public string type;
}
[Js(Extern = true)]
public class InputEventTarget : EventTarget
{
public string value;
}
[Js(Extern = true)]
public class EventTarget { }
Note that some of these types are external and some aren't. The simple rule is that if they must be explicitly created by C# code then they are not external, and if they are only received by C# code then the are external. To illustrate:
DOM.input(new InputAttributes {
className = "message",
onChange = ev => Global.console.log("Input.onChange: " + ev.target.value)
})
The "InputAttributes" class is explicitly instantiated by this C# code and so "InputAttributes" must not be an external class, it requires that there be an "InputAttributes" class generated as JavaScript for use at runtime. In the "onChange" callback, "ev" is an instance of "FormEvent
The reasoning behind the "FormEvent<T>" / "FormEvent" / "SyntheticEvent" hierarchy is that I thought it would make sense to try to imitate the TypeScript React definitions, and they use types with these names.
However, one of things that I dislike about the TypeScript version is that the typing could be even stronger. For example, in the "onChange" callback from a React input element in TypeScript, the "ev.target" has the non-specific type of "EventTarget" and so you have to skirt around the type system to access to input's value property:
// TypeScript
React.DOM.input({
className = "message",
onChange = ev => console.log("Input.onChange: " + (<any>ev.target).value)
})
With the DuoCode bindings above, the "ev.target" reference in the "onChange" callback is known to be an "InputEventTarget" and so the "value" property is explicitly declared as a string. This is significant improvement, I feel!
When I was first investigating all this, I encountered a problem with trying to have a "Props" class nested within the "ExampleComponent" class - it seemed to be a very specific combination of a class (Props) nested within another class (ExampleComponent) that was a non-generic specialisation of a generic base class (ComponentWrapper<TProps>). It's perfectly valid C# and there were no compiler / translation errors, but it would fail at runtime. I found that if I went to "Managed NuGet package" for my project and added the "DuoCode Compiler" package (selecting "Include Prelease", rather than "Stable Only", since DuoCode is still in beta) then a new version of the compiler was installed (0.6.1253.0 instead of 0.3.878.0). This gave me a confusing compile error that was cleared by closing the solution and opening it again. But rebuilding it then resulted in the problem going away, so clearly this is something that was fixed in the compiler at some point.
I just tried to recreate this earlier in order to get the precise error, but it seems that creating a new DuoCode project now gets the newer version of the compiler immediately, so I was unable to reproduce. It's possible that this was an artifact of an earlier installation of DuoCode that wasn't cleared properly - thinking about it now, it was a different PC where I had the problem, so this seems very feasible. I do kinda wish there was a more detailed changelog available for DuoCode - maybe they will be freer with details once they decide upon their licensing model!
One other oddity is that when I go to add a new C# class to a project, I have two options for "Class" - one is described as being "An empty class declaration" and one as an "An empty class definition". I'm not sure why I've got two, it may well be another leftover from something I've installed in the past. The annoying thing, though, is that one of them always tries to add the "System" reference to the project. When this happens, the project will no longer build since
Referenced assembly 'System' is not compatible with DuoCode
This error is entirely reasonable and descriptive, DuoCode translates C# into JavaScript but can't translate just any arbitrary .net binary. And the System library is re-implemented in the DuoCode "mscorlib" which does get translated into JavaScript. All you have to do, if you suffer this problem too, is remove the System reference from the project - and try to remember to use the "correct" C# class option next time! :)
If all of this sounds interesting to you, but you don't want to go through the hard work of piecing together the various code snippets in this article, then check out my sample ReactDuoCode Bitbucket project. It's a Visual Studio 2013 solution (the DuoCode site mentions Visual Studio 2015 in a few places but it's not a requirement).
Not only does it demonstrate using React with DuoCode but it also illustrates how you could use the Flux architecture! There's an implementation of a Dispatcher (which was incredibly easy using C# events, which are translated perfectly by DuoCode) and some actions implemented as C# classes. Then all changes to state are handled in a one-way-message-passing arrangement. There's a reason that this is the architecture recommended by Facebook for use with React; because it's awesome! :D I've not seen any other framework, library or approach which really makes such a concerted effort to manage state as this - it's why I've been going on about immutability all these years, since it's a way to keep the accidental complexity down since the essential complexity is hard enough work on its own. And now there's a well-used and well-supported UI framework based around this gaining real traction! Being able to use it with the C# language and with Visual Studio for the tooling just might make it the perfect combination.
But maybe I'm getting a bit carried away.. why don't you give it a try and let me know what you think :)
Posted at 11:42
29 January 2015
React 0.13 has just been released into beta, a release I've been eagerly anticipating! This has been the release where finally they will be supporting ES6 classes to create React components. Fully supported, no messing about and jumping through hoops and hoping that breaking API changes don't drop in and catch you off guard.
Back in September, I wrote about Writing React components in TypeScript and realised that before I had actually posted it that the version of React I was using was out of date and I would have to re-work it all again or wait until ES6 classes were natively supported (which was on the horizon back then, it's just that there were no firm dates). I took the lazy option and have been sticking to React 0.10.. until now!
Update (16th March 2015): React 0.13 was officially released last week, it's no longer in beta - this is excellent news! There appear to be very little changed since the beta so everything here is still applicable.
I've got my head around npm, which is the recommended way to get the source. I had a few teething problems a few months ago with first getting going (I need python?? Oh, not that version..) but now everything's rosy. So off I went:
npm install react@0.13.0-beta.1
I saw that the "lib" folder had the source code for the files, the dependencies are all nicely broken up. Then I had a small meltdown and stressed about how to build from source - did I need to run browserify or something?? I got that working, with some light hacking it about, and got to playing around with it. It was only later that I realised that there's also a "dist" folder with built versions - both production (ie. minified) and development. Silly boy.
To start with, I stuck to vanilla JavaScript to play around with it (I didn't want to start getting confused as to whether any problems were with React or with TypeScript with React). The online JSX Compiler can perform ES6 translations as well as JSX, which meant that I could take the example
class HelloMessage extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
React.render(<HelloMessage name="Sebastian" />, mountNode);
and translate it into JavaScript (this deals with creating a class, derives it "from React.Component" and it illustrates what the JSX syntax hides - particularly the "React.createElement" call):
var ____Class1 = React.Component;
for (var ____Class1____Key in ____Class1) {
if (____Class1.hasOwnProperty(____Class1____Key)) {
HelloMessage[____Class1____Key] = ____Class1[____Class1____Key];
}
}
var ____SuperProtoOf____Class1 = ____Class1 === null ? null : ____Class1.prototype;
HelloMessage.prototype = Object.create(____SuperProtoOf____Class1);
HelloMessage.prototype.constructor = HelloMessage;
HelloMessage.__superConstructor__ = ____Class1;
function HelloMessage() {
"use strict";
if (____Class1 !== null) {
____Class1.apply(this, arguments);
}
}
HelloMessage.prototype.render = function() {
"use strict";
return React.createElement("div", null, "Hello ", this.props.name);
};
React.render(
React.createElement(HelloMessage, { name: "Sebastian" }),
mountNode
);
I put this into a test page and it worked! ("mountNode" just needs to be a container element - any div that you want to render your content inside).
The derive-class code isn't identical to that you see in TypeScript's output. If you've looked at what TypeScript emits, this might be familiar:
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
I tried hacking this in, in place of the inheritance approach from the JSX Compiler and it still worked. I presumed it would, but it's always best to take baby steps if you don't understand it all perfectly - and I must admit that I've been a bit hazy on some of React's terminology around components, classes, elements, factories, whatever.. (despite having read "Introducing React Elements" what feels like a hundred times).
In the code above, the arrangement of the line
React.render(
React.createElement(HelloMessage, { name: "Sebastian" }),
mountNode
);
is very important. I must have spent hours earlier struggling with getting it working in TypeScript because I thought it was
React.render(
React.createElement(new HelloMessage({ name: "Sebastian" })),
mountNode
);
It's not.
It it not a new instance passed to "createElement"; it's a type and a properties object. I'm not sure where I got the idea from that it was the other way around - perhaps because I got all excited about it working with classes and then presumed that you worked directly with instances of those classes. Doh.
Like I said, I've been clinging to my hacked-about way to get TypeScript working with React until now (waiting until I could throw it away entirely, rather than replace it for something else.. which I would then have to throw away entirely when this release turned up). I took a lot of inspiration from code in the react-typescript repo. But that repo hasn't been kept up to date (for the same reason as I had, I believe, that the author knew that it was only going to be required until ES6 classes were supported). There is a link there to typed-react, which seems to have been maintained for 0.12. This seemed like the best place to start.
Update (16th March 2015): With React 0.13's official release, the DefinitelyTyped repo has been updated and now does work with 0.13, I'm leaving the below section untouched for posterity but you might want to skip to the next section "Writing a TypeScript React component" if you're using the DefinitelyTyped definition.
In fact, after some investigation, very little needs changing. Starting with their React type definitions (from the file typings/react/react.d.ts), we need to expose the "React.Component" class but currently that is described by an interface. So the following must be changed -
interface Component<P> {
getDOMNode<TElement extends Element>(): TElement;
getDOMNode(): Element;
isMounted(): boolean;
props: P;
setProps(nextProps: P, callback?: () => void): void;
replaceProps(nextProps: P, callback?: () => void): void;
}
for this -
export class Component<P> {
constructor(props: P);
protected props: P;
}
I've removed isMounted and setProps because they've been deprecated from React. I've also removed the getDOMNode methods since I think they spill out more information than is necessary and I've removed replaceProps since the way that I've been using React I've not seen the use for it - I think it makes more sense to request a full re-render* rather than poke things around. You may not agree with me on these, so feel free to leave them in! Similarly, I've changed the access level of "props" to protected, since I don't think that it should be public information. This requires TypeScript 1.3, which might be why the typed-react version doesn't specify it.
* When I say "re-render", I mean that when some action changes the state of the application, I call React.render again and let the Virtual DOM do it's magic around making this efficient. Plus I'm experimenting at the moment with making the most of immutable data structures and returning false from shouldComponentUpdate where it's clear that the data can't have changed - so the Virtual DOM has less work to do. But that's straying from the point of this post a bit..
Then the external interface needs changing from
interface Exports extends TopLevelAPI {
DOM: ReactDOM;
PropTypes: ReactPropTypes;
Children: ReactChildren;
}
to
interface Exports extends TopLevelAPI {
DOM: ReactDOM;
PropTypes: ReactPropTypes;
Children: ReactChildren;
Component: Component<any>;
}
Quite frankly, I'm not 100% sure why specifying "Component
So now we can write this:
import React = require('react');
interface Props { name: string; role: string; }
class PersonDetailsComponent extends React.Component<Props> {
constructor(props: Props) {
super(props);
}
public render() {
return React.DOM.div(null, this.props.name + " is a " + this.props.role);
}
}
function Factory(props: Props) {
return React.createElement(PersonDetailsComponent, props);
}
export = Factory;
Note that we are able to specify a type param for "React.Component" and, when you edit this in TypeScript, "this.props" is correctly identified as being of that type.
Update (16th March 2015): If you are using the DefinitelyTyped definitions then you need to specify both "Props" and "State" type params (I recommend that Component State never be used and that it always be specified as "{}", but that's out of the scope of this post) - ie.
class PersonDetailsComponent extends React.Component<Props, {}> {
The pattern I've used is to declare a class that is not exported. Rather, a "Factory" function is made available to the world. This is to prevent the problem that I described earlier - originally I was exporting the class and was trying to call
React.render(
React.createElement(new PersonDetailsComponent({
name: "Bob",
role: "Mouse catcher"
})),
mountNode
);
but this does not work. The correct approach is to export a Factory method and then to consume the component thusly:
React.render(
PersonDetailsComponent({
name: "Bob",
role: "Mouse catcher"
}),
this._renderContainer
);
Thankfully, the render method is specified in the type definition as
render<P>(
element: ReactElement<P>,
container: Element,
callback?: () => void
): Component<P>;
so, if you forget to apply the structure of non-exported-class / exported-Factory-method and tried to export the class and new-one-up and pass it to "React.render" directly, you would get a compile error such as
Argument of type 'PersonDetailsComponent' is not assignable to parameter of type 'ReactElement<Props>'
I do love it when the compiler can pick up on your silly mistakes!
Update (16th March 2015): Again, there is a slight difference between the typed-react definition that I was originally using and the now-updated DefinitelyTyped repo version. With DefinitelyTyped, the render method is specified as:
render<P, S>(
element: ReactElement<P>,
container: Element,
callback?: () => any
): Component<P, S>
but the meaning is much the same.
The hacky way I've been working until now did allow instances of component classes to be used, so migrating over is going to require some boring mechanical work to change them - and to add Factory methods to each component. But, since they all shared a common base class (the "ReactComponentBridge"), it also shouldn't be too much work to change those base classes to "React.Component" in one search-and-replace. And there aren't too many other breaking changes to worry about. I was using "setProps" earlier on in development but I've already gotten rid of all those - so I'm optimistic that moving over to 0.13 isn't going to be too big of a deal.
It's worth bearing in mind that 0.13 is still in beta at the moment, but it seems like the changes that I'm interested in here are unlikely to vary too much between now and the official release. So if I get cracking, maybe I can finish migrating not long after it's officially here - instead of being stuck a few releases behind!
Posted at 01:02
8 January 2015
I've been playing with React over the last few months and I'm still a fan. I've followed Facebook's advice and gone with the "Flux" architecture (there's so many good articles about this out there that I couldn't even decide which one to link to) but I've been writing the code using TypeScript. So far, most of my qualms with this approach have been with TypeScript rather than React; I don't like the closing-brace formatting that Visual Studio does and doesn't let you change, its generics system is really good but not quite as good as I'd like (not as good as C#'s, for example, and I sometimes wish generic type params were available at runtime for testing but I do understand why they're not). I wish the "Allow implicit 'any' types" option defaulted to unchecked rather than checked (I presume this is to encourage "gradual typing" but if I'm using TypeScript I'd rather go whole-hog).
But what I thought were going to be the big problems with it haven't been, really - type definitions and writing the components (though I am using a bit of a hack that relies upon an older version of React - I'm hoping to change this when 0.13 comes out and introduces better support for ES6 classes).
Writing the components in "pure" TypeScript results in more code than jsx.. it's not the end of the world, but something that would combine the benefits of both (strong typing and succint jsx format) would be wonderful. There are various possibilities that I believe people are looking into, from modifying the TypeScript compiler to support jsx to the work that Facebook themselves are doing around "Flow" which "Adds static typing to JavaScript to improve developer productivity and code quality". Neither of these are ready for me to integrate into Visual Studio, which I'm still using since I like it so much for my other development work.
What I want to talk about today, though, is one of the ways that TypeScript's capabilities can make a nice tweak to how the Flux architecture may be realised. Hopefully the following isn't blindly obvious and well-known, I failed to find any other posts out there explaining it so I'm going to try to take credit for it! :)
Here's the diagram that everyone who's looked into Flux will have seen many times before (since I've nicked it straight from the React blog's post about it) -
In the middle are the "Action Creators", which create objects that represent actions (and any associated data) so that the Dispatcher has something to send out. Stores listen for these actions - checking whether a given action is one that they're interested in and extracting the information from it as required.
As a concrete example, here is how actions are created in Facebook's "TODO" example (from their repo on GitHub):
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* TodoActions
*/
var AppDispatcher = require('../dispatcher/AppDispatcher');
var TodoConstants = require('../constants/TodoConstants');
var TodoActions = {
/**
* @param {string} text
*/
create: function(text) {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_CREATE,
text: text
});
},
/**
* @param {string} id The ID of the ToDo item
* @param {string} text
*/
updateText: function(id, text) {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_UPDATE_TEXT,
id: id,
text: text
});
},
/**
* Toggle whether a single ToDo is complete
* @param {object} todo
*/
toggleComplete: function(todo) {
var id = todo.id;
if (todo.complete) {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_UNDO_COMPLETE,
id: id
});
} else {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_COMPLETE,
id: id
});
}
},
/**
* Mark all ToDos as complete
*/
toggleCompleteAll: function() {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_TOGGLE_COMPLETE_ALL
});
},
/**
* @param {string} id
*/
destroy: function(id) {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_DESTROY,
id: id
});
},
/**
* Delete all the completed ToDos
*/
destroyCompleted: function() {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_DESTROY_COMPLETED
});
}
};
module.exports = TodoActions;
Every action has an "actionType" property. Some have an "id" property, some have a "text" property, some have both, some have neither. Other examples I've seen follow a similar pattern where the ActionCreator (or ActionCreators, since sometimes there are multiple - as in the chat example in that same Facebook repo) is what is responsible for knowing how data is represented by each action. Stores assume that if the "actionType" is what they expect then all of the other properties they expect to be associated with that action will be present.
Here's a snippet I've taken from another post:
var action = payload.action;
switch(action.actionType){
case AppConstants.ADD_ITEM:
_addItem(payload.action.item);
break;
case AppConstants.REMOVE_ITEM:
_removeItem(payload.action.index);
break;
case AppConstants.INCREASE_ITEM:
_increaseItem(payload.action.index);
break;
case AppConstants.DECREASE_ITEM:
_decreaseItem(payload.action.index);
break;
}
Some actions have an "item" property, some have an "index". The ActionCreator was responsible for correctly populating data appropriate to the "actionType".
When I first start writing code like this for my own projects, it felt wrong. Wasn't I using TypeScript so that I had a nice reassuring type safety net to protect me against my own mistakes?
Side note: For me, this is one of the best advantages of "strong typing", the fact the compiler can tell me if I've mistyped a property or argument, or if I want to change the name of one of them then the compiler can change all references rather than it being a manual process. The other biggie for me is how beneficial it can be in helping document APIs (both internal and external) - for other people using my code.. or just me when it's been long enough that I can't remember all of the ins and outs of what I've written! These are more important to me than getting worried about whether "static languages" can definitely perform better than "dynamic" ones (let's not open that can of worms).
Surely, I asked myself, if these objects have properties that vary based upon an "actionType" magic string, these would be better expressed as actual types? Like classes?
Working from the example above, there would be classes such as:
class AddItemAction {
constructor(private _index: number) { }
get index() {
return this._index;
}
}
export = AddItemAction;
I'm a fan of the AMD pattern so I would have a separate file per action class and then explicitly "import" (in TypeScript terms) them into Stores that reference them. The main reason I'm leaning towards the AMD pattern is that you can use require.js to load in the script required to render the first "page" and then dynamically load in additional script as more functionality of the application is used. This should avoid the risk of the dreaded multi-megabyte initial download (and the associated delays). I'm still proving this to myself - it's looking very promising so far but I haven't written any multi-megabyte applications yet!
I also like things to be immutable, otherwise the above could have been shortened even further to:
class AddItemAction {
constructor(public index: number) { }
}
export = AddItemAction;
But, technically, this could lead to one Store changing data in an action, which could affect what another Store does with the data. An effect that would only happen if that first Store received the action before the second one. Yuck. I don't imagine anyone would want to do something like that but immutability means that it's not even possible, even by accident (especially by accident).
So if there were classes for each action then the listening code would look more like this:
if (action instanceof AddItemAction) {
this._addItem(action);
}
if (action instanceof RemoveItemAction) {
this._removeItem(action);
}
if (action instanceof IncreaseItemAction) {
this._increaseItem(action);
}
if (action instanceof DecreaseItemAction) {
this._decreaseItem(action);
}
I prefer to have the functions receive the actual action. The AddItemAction instance is passed to the "_addItem" function, for example, rather than just the "index" property value - eg.
private _addItem(action: AddItemAction) {
// Do whatever..
}
This is at least partly because it makes the type comparing code more succinct - the "action" reference will be of type "any" (as will be seen further on in this post) and so TypeScript lets us pass it straight in to methods such as _addItem since it presumes that if it's "any" then it can be used anywhere, even as an function argument that has a specific type annotation. The type check that is made before _addItem is called gives us the confidence that the data is appropriate to pass to _addItem, the TypeScript compiler will then happily take our word for it.
Update (25th February 2015): A couple of people in the comments suggested that the action property on the payload should implement an interface to "mark" it as an action. This is something I considered originally but I dismissed it and I think I'm going to continue to dismiss it for the following reason: the interface would be "empty" since there is no property or method that all actions would need to share. If this were C# then every action class would have to explicitly implement this "empty interface" and so we could do things like search for all implementation of IAction within a given project or binary. In TypeScript, however, interfaces may be implemented implicitly ("TypeScript is structural"). This means that any object may be considered to have (implicitly) implemented IAction, if IAction is an empty interface. And this means that there would be no reliable way to search for implementations of IAction in a code base. You could search for classes that explicitly implement it, but if you have to rely upon people to follow the convention of decorating all action classes with a particular interface then you might as well rely on a simpler convention such as keeping all actions within files under an "action" folder.
Another concept that this works well with is one that I think I first read at Atlassian's blog: Flux Step By Step - the idea of identifying a given action as originating from a view (from a user interaction, generally) or from the server (such as an ajax callback).
They suggested the use of an AppDispatcher with two distinct methods, each wrapping an action up with an appropriate "source" value -
var AppDispatcher = copyProperties(new Dispatcher(), {
/**
* @param {object} action The details of the action, including the action's
* type and additional data coming from the server.
*/
handleServerAction: function(action) {
var payload = {
source: 'SERVER_ACTION',
action: action
};
this.dispatch(payload);
},
/**
* @param {object} action The details of the action, including the action's
* type and additional data coming from the view.
*/
handleViewAction: function(action) {
var payload = {
source: 'VIEW_ACTION',
action: action
};
this.dispatch(payload);
}
});
Again, these are "magic string" values. I like the idea, but TypeScript has the tools to do better.
I have a module with an enum for this:
enum PayloadSources {
Server,
View
}
export = PayloadSources;
and then an AppDispatcher of my own -
import Dispatcher = require('third_party/Dispatcher/Dispatcher');
import PayloadSources = require('constants/PayloadSources');
import IDispatcherMessage = require('dispatcher/IDispatcherMessage');
var appDispatcher = (function () {
var _dispatcher = new Dispatcher();
return {
handleServerAction: function (action: any): void {
_dispatcher.dispatch({
source: PayloadSources.Server,
action: action
});
},
handleViewAction: function (action: any): void {
_dispatcher.dispatch({
source: PayloadSources.View,
action: action
});
},
register: function (callback: (message: IDispatcherMessage) => void): string {
return _dispatcher.register(callback);
},
unregister: function (id: string): void {
return _dispatcher.unregister(id);
},
waitFor: function (ids: string[]): void {
_dispatcher.waitFor(ids);
}
};
} ());
// This is effectively a singleton reference, as seems to be the standard pattern for Flux
export = appDispatcher;
The IDispatcherMessage is very simple:
import PayloadSources = require('constants/PayloadSources');
interface IDispatcherMessage {
source: PayloadSources;
action: any
}
export = IDispatcherMessage;
This allows me to listen for actions with code thusly -
AppDispatcher.register(message => {
var action = message.action;
if (action instanceof AddItemAction) {
this._addItem(action);
}
if (action instanceof RemoveItemAction) {
this._removeItem(action);
}
// etc..
Now, if I come across a good reason to rename the "index" property on the AddItemAction class, I can perform a refactor action that will fix it everywhere. If I don't use the IDE to perform the refactor, and just change the property name in one place, then I'll get TypeScript compiler errors about an "index" property that no longer exists.
One thing I skimmed over in the above is what the "third_party/Dispatcher/Dispatcher" component is. The simple answer is that I took the Dispatcher.js file from the Flux repo and messed about with it a tiny bit to get it to compile as TypeScript with my preferred disabling of the option "Allow implicit 'any' types". In case this is a helpful place for anyone to start, I've put the result up on pastebin as TypeScript Flux Dispatcher, along with the required support class TypeScript Flux Dispatcher - invariant support class.
I'm still experimenting with React and Flux but this is one of the areas that I've definitely been happy with. I like the Flux architecture and the very clear way in which interactions are handled (and the clear direction of flow of information). Describing the actions with TypeScript classes feels very natural to me. It might be that I start grouping multiple actions into a single module as my applications get bigger, but for now I'm fine with one per file.
The only thing I'm only mostly happy with is my bold declaration in the AppDispatcher class; "This is effectively a singleton reference, as seems to be the standard pattern for Flux". It's not the class that's exported from that module, it's an instance of the AppDispatcher which is used by everything in the app. This makes sense in a lot of ways, since it needs to be used in so many places; there will be various Stores that register to listen to it but there are likely to be many, many React components, any one of which could accept some sort of interaction that requires an action be created (and so be sent to the AppDispatcher). One alternative approach would be to use dependency injection to pass an AppDispatcher through every component that might need it. In fact, I did try that in one early experiment but found it extremely cumbersome, so I'm happy to settle for what I've got here.
However, the reason (one of, at least!) that singletons got such a bad name is that they can making unit testing very awkward. I'm still in the early phases of investigating what I think is the best way to test a React / Flux application (there are a lot of articles out there explaining good ways to tackle it and I'm trying to work my way through some of their ideas). One thing that I'm contemplating, particularly for testing simple React components, is to take advantage of the fact that I'm using AMD everywhere and to try changing the require.js configuration for tests - for any given test, when an AppDispatcher is requested, some sort of mock object could be provided in its place.
This would have the two main benefits that it could expose convenient methods to confirm that a particular action was raised following a given interaction (which may be the main point of that particular test) but also that there would be no shared state that needs resetting between tests; each test would provide its own AppDispatcher stand-in. I've not properly explored this yet, it's still in the idea phase, but I think it also has promise. And - if it all goes to plan - it's another reason way for me to convince myself that AMD loading within TypeScript is the way to go!
Posted at 22:24
24 September 2014
Whoops.. I started writing this post a while ago and have only just got round to finishing it off. Now I realise that it applies to React 0.10 but the code here doesn't work in the now-current 0.11. On top of this, hopefully this will become unnecessary when 0.12 is released. I talk about this in the last part of the post. But until 0.12 is out (and confirmed to address the problem), I'm going to stick to 0.10 and use the solution that I talk about here.
Update (29th January 2015): React 0.13 beta has been released and none of this workaround is required any more - I've written about it here: TypeScript / ES6 classes for React components - without the hacks!
I've been playing around with React recently, putting together some prototypes to try to identify any pitfalls in what I think is an excellent idea and framework, with a view to convincing everyone else at work that we should consider it for new products. I'm no JavaScript hater but I do strongly believe in strongly typed code being easier to maintain in the long run for projects of significant size. Let's not get into an argument about whether strong or "weak" typing is best - before we know it we could end up worrying about what strongly typed even means! If you don't agree with me then you probably don't see any merit to TypeScript and you probably already guessed that this post will not be of interest to you! :)
So I wanted to try bringing together the benefits of React with the benefits of TypeScript.. I'm clearly not the only one since there is already a type definition available in NuGet: React.TypeScript.DefinitelyTyped. This seems to be the recommended definition and appears to be in active development. I'd love it even more if there was an official definition from Facebook themselves (they have one for their immutable-js library) but having one here is a great start. This allows us to call methods in the React library and know what types the arguments should be and what they will return (and the compiler will tell us if we break these contracts by passing the wrong types or trying to mistreat the return values).
However, there are a few problems. Allow me to venture briefly back to square one..
This is a very simple component in React -
var MyButton = React.createClass({
_clickHandler: function() {
alert('Clicked MyButton with message "' + this.props.message + '"');
},
render: function() {
return <button onClick={this._clickHandler}>{this.props.message}</button>;
}
});
It's pretty boring but it illustrates a few principles. Firstly, it's written in "jsx" - a format like JavaScript but that needs some processing to actually become JavaScript. The <button> declaration looks like html, for example, and needs altering to become real JavaScript. If we're going to write components in TypeScript then we can't use this format since Visual Studio doesn't understand it (granted I'm making a bit of a leap assuming that you're using Visual Studio for this - it's not necessary, but I suspect most people writing TypeScript will use it since the TypeScript support is so good).
The good news is that the translation from "jsx" to JavaScript is not a complex one*. It results in slightly longer code but it's still easily readable (and writable). So the above would be, written in native JavaScript -
var MyButton = React.createClass({
_clickHandler: function() {
alert('Clicked MyButton with message "' + this.props.message + '"');
},
render: function() {
return React.DOM.button({ onClick: this._clickHandler }, this.props.message);
}
});
* (It can do other clever stuff like translate "fat arrow" functions into JavaScript that is compatible with older browsers, but let's not get bogged down with that here - since I want to use TypeScript rather than jsx, it's not that relevant right now).
This simple example is illustrating something useful that can be taken for granted since React 0.4; autobinding. When "_clickHandler" is called, the "this" reference is bound to the component instance, so "this.props.message" is accessible. Before 0.4, you had to use the "React.autoBind" method - eg.
var MyButton = React.createClass({
_clickHandler: React.autoBind(function() {
alert('Clicked MyButton with message "' + this.props.message + '"');
}),
render: function() {
return React.DOM.button({ onClick: this._clickHandler }, this.props.message);
}
});
but these days it just works as you would expect (or as you would hope, perhaps). This happened back in July 2013 - see New in React v0.4: Autobind by Default.
If we naively try to write TypeScript code that starts off with the JavaScript above then we find we have no intellisense. The editor has no idea about "this.props" - no idea that it is defined, certainly no idea that it has a property "message" that should be a string. This shouldn't really be a surprise since the "this" in this case is just an anonymous object that we're passing to "React.createClass"; no information about the type has been specified, so it is considered to be of type "any".
If we continue like this then we're going to miss out on the prime driver for using TypeScript in the first place - we might as well just write the components in JavaScript or "jsx"! (In fairness, this is something that I considered.. with React, and particularly the recommended Flux architecture, the "view components" are a relatively thin layer over components that could easily be written in TypeScript and so benefit from being strongly typed.. the view components could remain "more dynamic" and be covered by the class of unit tests that are often used to cover cases that are impossible with the guarantees of strong typing).
The obvious thing to try was to have a TypeScript class along the lines of
class MyButton {
props: { message: string };
private _clickHandler() {
alert('Clicked MyButton with message "' + this.props.message + '"');
}
public render() {
return React.DOM.button({ onClick: this._clickHandler }, this.props.message);
}
}
var MyButtonReactComponent = React.createClass(new MyButton());
This would solve the internal type specification issue (where "this" is "any"). However, when the "React.createClass" function is called at runtime, an error is thrown..
Error: Invariant Violation: createClass(...): Class specification must implement a
render
method.
I'm not completely sure, but I suspect that the React framework code is expecting an object with a property that is a function named "render" while the class instance passed to it has a function "render" on its prototype rather than a property on the reference itself.
When I got to this point, I figured that someone else must have had encountered the same problem - particularly since there exists this TypeScript definition for React in the first place! I came across a GitHub project React TypeScript which describes itself as a
React wrapper to make it play nicely with typescript.
An example in the README shows
import React = require('react');
import ReactTypescript = require('react-typescript');
class HelloMessage extends ReactTypescript.ReactComponentBase<{ name: string; }, {}> {
render() {
return React.DOM.div(null, 'Hello ' + this.props.name);
}
}
React.renderComponent(new HelloMessage({ name: 'Jhon' }), mountNode);
which looks like exactly what I want!
The problems are that it clearly states..
warning: ReactTypescript can actually only be used with commonjs modules and browserify, if someone does want AMD I'll gladly accept any PR that would packages it for another format.
.. and I'm very interesting in using AMD and require.js to load modules "on demand" (so that if I develop a large app then I have a way to prevent the "megabyte-plus initial JavaScript download").
Also, I'm concerned that the maintained TypeScript definition that I referenced earlier claims to be
Based on TodoMVC sample by @fdecampredon, improved by @wizzard0, MIT licensed.
fdecampredon is the author of this "React TypeScript" repo.. which hasn't been updated in seven months. So I'm concerned that the definitions might not be getting updated here - there are already a lot of differences between the react.d.ts in this project and that in the maintained NuGet package's react.d.ts.
In addition to this, the README states that
In react, methods are automatically bound to a component, this is not the case when using ReactTypeScript, to activate this behaviour you can use the autoBindMethods function of ReactTypeScript
This refers to what I talked about earlier; the "auto-binding" convenience to make writing components more natural. There are two examples of ways around this. You can use the ReactTypeScript library's "autoBindMethods" function -
class MyButton extends ReactTypeScript.ReactComponentBase<{ message: string}, any> {
clickHandler(event: React.MouseEvent) {
alert(this.props.message);
}
render() {
return React.DOM.button({ onClick: this.clickHandler }, 'Click Me');
}
}
// If this isn't called then "this.props.message" will error in clickHandler as "this" is not
// bound to the instance of the class
ReactTypeScript.autoBindMethods(MyButton);
or you can use the TypeScript "fat arrow" to bind the function to the "this" reference that you would expect:
class MyButton extends ReactTypeScript.ReactComponentBase<{ message: string}, any> {
// If the fat arrow isn't used for the clickHandler definition then "this.props.message" will
// error in clickHandler as "this" is not bound to the instance of the class
clickHandler = (event: React.MouseEvent) => {
alert(this.props.message);
}
render() {
return React.DOM.button({ onClick: this.clickHandler }, 'Click Me');
}
}
The first approach feels a bit clumsy, that you must always remember to call this method for all component classes. The second approach doesn't feel too bad, it's just a case of being vigilant and always using fat arrows - but if you forget, you won't find out until runtime. Considering that I want to use to TypeScript to catch more errors at compile time, this still doesn't feel ideal.
The final concern I have is that the library includes a large-ish react-internal.js file. What I'm going to suggest further down does unfortunately dip its toe into React's (undocumented) internals but I've tried to keep it to the bare minimum. This "react-internal.js" worries me as it might be relying on a range of implementation details, any of which (as far as I know) could potentially change and break my code.
In case I'm sounding down on this library, I don't mean to be - I've tried it out and it does actually work, and there are not a lot of successful alternatives out there. So I've got plenty of respect for this guy, getting his hands dirty and inspiring me to follow in his footsteps!
So I want a way to
I'd better say this up-front, though: I'm willing to sacrifice the support for mixins here.
fdecampredon's "React TypeScript" library does support mixins so it's technically possible but I'm not convinced at this time that they're worth the complexity required by the implementation since I don't think they fit well with the model of a TypeScript component.
The basic premise is that you can name mixin objects which are "merged into" the component code, adding properties such as functions that may be called by the component's code. Since TypeScript wouldn't be aware of the properties added by mixins, it would think that there were missing methods / properties and flag them as errors if they were used within the component.
On top of this, I've not been convinced by the use cases for mixins that I've seen so far. In the official React docs section about mixins, it uses the example of a timer that is automatically cleared when the component is unmounted. There's a question on Stack Overflow "Using mixins vs components for code reuse in Facebook React" where the top answer talks about using mixins to perform common form validation work to display errors or enable or disable inputs by directly altering internal state on the component. As I understand the Flux architecture, the one-way message passing should result in validation being done in the store rather than the view / component. This allows the validation to exist in a central (and easily-testable) location and to not exist in the components. This also goes for the timer example, the logic-handling around whatever events are being raised on a timer should not exist within the components.
What I have ended up with is this:
import React = require('react');
// The props and state references may be passed in through the constructor
export class ReactComponentBase<P, S> {
constructor(props?: P, state?: S) {
// Auto-bind methods on the derived type so that the "this" reference is as expected
// - Only do this the first time an instance of the derived class is created
var autoBoundTypeScriptMethodsFlagName = '__autoBoundTypeScriptMethods';
var autoBindMapPropertyName = '__reactAutoBindMap'; // This is an internal React value
var cp = this['constructor'].prototype;
var alreadyBoundTypeScriptMethods = (cp[autoBoundTypeScriptMethodsFlagName] === true)
&& cp.hasOwnProperty(autoBoundTypeScriptMethodsFlagName);
if (!alreadyBoundTypeScriptMethods) {
var autoBindMap = {};
var parentAutoBindMap = cp[autoBindMapPropertyName];
var functionName;
if (parentAutoBindMap) {
// Maintain any binding from an inherited class (if the current class being dealt
// with doesn't directly inherit from ReactComponentBase)
for (functionName in parentAutoBindMap) {
autoBindMap[functionName] = parentAutoBindMap[functionName];
}
}
for (functionName in cp) {
if (!cp.hasOwnProperty(functionName) || (functionName === "constructor")) {
continue;
}
var fnc = cp[functionName];
if (typeof (fnc) !== 'function') {
continue;
}
autoBindMap[functionName] = fnc;
}
cp[autoBindMapPropertyName] = autoBindMap;
cp[autoBoundTypeScriptMethodsFlagName] = true;
}
this['construct'].apply(this, arguments); // This is an internal React method
}
props: P;
state: S;
}
ReactComponentBase.prototype = React.createClass({
// The component must share the "componentConstructor" that is present on the prototype of
// the return values from React.createClass
render: function () {
return null;
}
})['componentConstructor'].prototype; // Also an internal React method
// This must be used to mount component instances to avoid errors due to the type definition
// expecting a React.ReactComponent rather than a ReactComponentBase (though the latter is
// able to masquerade as the former and when the TypeScript compiles down to JavaScript,
// no-one will be any the wiser)
export function renderComponent<P, S>(
component: ReactComponentBase<P, S>,
container: Element,
callback?: () => void) {
var mountableComponent = <React.ReactComponent<any, any>><any>component;
React.renderComponent(
mountableComponent,
container,
callback
);
}
This allows the following component to be written:
import React = require('react');
import ReactComponentBridge = require('components/ReactComponentBridge');
class MyButton extends ReactComponentBridge.ReactComponentBase<{ message: string }, any> {
myButtonClickHandler(event: React.MouseEvent) {
alert('Clicked MyButton with message "' + this.props.message + '"');
}
render() {
return React.DOM.button({ onClick: this.myButtonClickHandler }, 'Click Me');
}
}
export = MyButton;
which may be rendered with:
import ReactComponentBridge = require('components/ReactComponentBridge');
import MyButton = require('components/MyButton');
ReactComponentBridge.renderComponent(
new MyButton({ message: 'Click Me' }),
document.getElementById('container')
);
Hurrah! Success! All is well with the world! I've got the benefits of TypeScript and the benefits of React and the Flux architecture (ok, the last one doesn't need any of this or even require React - it could really be used with whatever framework you chose). There's just one thing..
Like I said at the start of this post, as I got to rounding it out to publish, I realised that I wasn't on the latest version of React (current 0.11.2, while I was still using 0.10) and that this code didn't actually work on that version. Sigh.
However, the good news is that it sounds like 0.12 (still in alpha at the moment) is going to make things a lot easier. The changes in 0.11 appear to be paving the way for 0.12 to shakes things up a bit. Changes are documented at New React Descriptor Factories and JSX which talks about how the problem they're trying to solve with the new code is a
Simpler API for ES6 classes
.. and there is a note in the react-future GitHub repo ("Specs & docs for potential future and experimental React APIs and JavaScript syntax") that
A React component module will no longer export a helper function to create virtual elements. Instead it's the responsibility of the consumer to efficiently create the virtual element.
Languages that compile to JS can choose to implement the element signature in whatever way is idiomatic for that language:
TypeScript implements some ES6 features (such as classes, which are how I want to represent React components) so (hopefully) this means that soon-to-hit versions of React are going to make ES6-classes-for-components much easier (and negate the need for a workaround such as is documented here).
The articles that I've linked to (I'm not quite sure how official that all is, btw!) are talking about a future version since they refer to the method "React.createFactory", which isn't available in 0.11.2. I have cloned the in-progress master repo from github.com/facebook/react and built the 0.12-alpha code* and that does have that method. However, I haven't yet managed to get it working as I was hoping. I only built it a couple of hours ago, though, and I want to get this post rounded out rather than let it drag on any longer! And, I'm sure, when this mechanism for creating React components is made available, I'm sure a lot of information will be released about it!
* (npm is a great tool but it still can't make everything easy.. first I didn't realise that the version of node.js I was using was out of date and it prevented some dependencies from being installed. Then I had to install Python - but 2.7 was required, I found out, after I'd installed 3.4. Then I didn't have Git installed on the computer I was trying to build React from. Then I had to mess about with setting environment variables for the Python and Git locations. But it did work, and when I think about how difficult it would have been without a decent package manager I stop feeling the need to complain about it too much :)
Posted at 23:12
Dan is a big geek who likes making stuff with computers! He can be quite outspoken so clearly needs a blog :)
In the last few minutes he seems to have taken to referring to himself in the third person. He's quite enjoying it.