- Notifications
You must be signed in to change notification settings - Fork11
ServiceStackApps/Chat-React
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This version of React Chat was built using our Legacy React App Template based on React 0.12, ES5 and the nowdeprecated JSXTransformer.This legacy template is still available in our archivedServiceStackVS.Archive.vsixVS.NET Extension.
A newer version ofReact Chat based on our recommended and more modernReact AppVS.NET Template using TypeScript, React v15, JSPM and Redux is available at:https://github.com/ServiceStackApps/ReactChat
is a port ofServiceStack Chat Server Events demo toReact built withServiceStackVS's new node.js-basedReactJS App VS.NET Template enabling an optimal iterative dev experience for creating optimized React.js Apps. This guide also includes an intro to Facebook'sReact library andFlux pattern and walks through how to use them to put together a React-based App.
Live Demo:http://react-chat.servicestack.net
ModernReact.js Apps with .NET
The new ServiceStackVSReactJS App template shares the same approach for developing modern Single Page Apps in VS.NET as theAngularJS App template by leveraging thenode.js ecosystem for managing all aspects of Client App development using the best-in-class libraries:
- npm to manage node.js dependencies (bower, grunt, gulp)
- Bower for managing client dependencies (angular, jquery, bootstrap, etc)
- Grunt as the primary task runner for server, client packaging and deployments
- Gulp used by Grunt to do the heavy-lifting bundling and minification
The templates conveniently pre-configures the above libraries into a working out-of-the-box solution, including high-level grunt tasks to take care of the full-dev-cycle ofbuilding,packaging anddeploying your app:
- 01-run-tests - Runs Karma JavaScript Unit Tests
- 02-package-server - Uses msbuild to build the application and copies server artefacts to
/wwwroot - 03-package-client - Optimizes and packages the client artefacts for deployment in
/wwwroot - 04-deploy-app - Uses MS WebDeploy and
/wwwroot_buld/publish/config.jsonto deploy app to specified server
Fast dev iterations are one of the immediate benefits when developing JavaScript-based Apps, made possible since you're editing the same plain text files browsers execute, so they get quickly rendered after each refresh without needing to wait for the rebuilding of VS.NET projects or ASP.NET's AppDomain to restart.
This fast dev cycle also extends toServiceStack Razor dynamic server pages which supports live-reloading of modified
.cshtmlRazor Views so they're view-able on-the-fly without an AppDomain restart.
For minimal friction, the Gulp/Grunt build system takes a non-invasive approach that works around normal web dev practices of being able to reference external css, js files - retaining the development experience of a normal static html website where any changes to html, js or css files are instantly visible after a refresh.
Then to package your app for optimal deployment to production,Gulp's useref plugin lets you annotate existing references with how you want them bundled. This is ideal as the existing external references (and their ordering) remains the master source for your Apps dependencies, reducing the maintenance and friction required in developing and packaging optimized Single Page Apps.
We can look at React Chat's dependencies to see how this looks:
<!--build:css css/app.min.css--><linkrel="stylesheet"href="css/app.css"/><!-- endbuild --><!-- build:js lib/js/jquery.min.js --><scriptsrc="bower_components/jquery/dist/jquery.js"></script><!-- endbuild --><!-- build:js lib/js/react.min.js --><scriptsrc="bower_components/react/react.js"></script><!-- endbuild --><!-- build:js lib/js/reflux.min.js --><scriptsrc="bower_components/reflux/dist/reflux.js"></script><!-- endbuild --><!-- build:remove --><scriptsrc="bower_components/react/JSXTransformer.js"></script><!-- endbuild --><scriptsrc="js/ss-utils.js"></script>...<!-- build:js js/app.jsx.js --><scripttype="text/jsx"src="js/components/Actions.js"></script><scripttype="text/jsx"src="js/components/User.jsx"></script><scripttype="text/jsx"src="js/components/Header.jsx"></script><scripttype="text/jsx"src="js/components/Sidebar.jsx"></script><scripttype="text/jsx"src="js/components/ChatLog.jsx"></script><scripttype="text/jsx"src="js/components/Footer.jsx"></script><scripttype="text/jsx"src="js/components/ChatApp.jsx"></script><!-- endbuild -->
During development the HTML comments are ignored and React Chat runs like a normal static html website. Then when packaging the client app for deployment (i.e. by running the03-package-client task), the build annotations instructs Gulp on how to package and optimize the app ready for production.
As seen in the above example, each build instruction can span one or multiple references of the same type and optionally specify the target filename to write the compressed and minified output to.
Gulp also supports design-time vs run-time dependencies with thebuild:remove task which can be used to remove any unnecessary dependencies not required in production like react'sJSXTransformer.js:
<!-- build:remove --><scriptsrc="bower_components/react/JSXTransformer.js"></script><!-- endbuild -->
React'sJSXTransformer.js is what enables the optimal experience of letting you directly reference.jsx files in HTML as if they were normal.js files by transpiling and executing.jsx files directly in the browser at runtime - avoiding the need for any manual pre-compilation steps and retaining the fastF5 reload cycle that we've come to expect from editing.js files.
<!-- build:js js/app.jsx.js --><scripttype="text/jsx"src="js/components/Actions.js"></script><scripttype="text/jsx"src="js/components/User.jsx"></script><scripttype="text/jsx"src="js/components/Header.jsx"></script><scripttype="text/jsx"src="js/components/Sidebar.jsx"></script><scripttype="text/jsx"src="js/components/ChatLog.jsx"></script><scripttype="text/jsx"src="js/components/Footer.jsx"></script><scripttype="text/jsx"src="js/components/ChatApp.jsx"></script><!-- endbuild -->
Then when the client app is packaged, all.jsx files are compiled and minified into a single/js/app.jsx.js with the reference toJSXTransformer.js also stripped from the optimized HTML page as there's no longer any need to transpile and execute.jsx files at runtime.
React is a new library from Facebook that enables a new approach to rendering dynamic UI's, maintaining state and modularizing large complex JavaScript Apps.
Rather than attempting to be a full-fledged MVC framework, React is limited in scope around theV inMVC where it can even be used together as a high-performance view renderer with JavaScript's larger MVC frameworks.
React benefits from its limited focus by having a simple but very capable API with a small surface area requiring very few concepts to learn - essentially centered around everything being a Component.
Conceptually speaking React Components are similar toCustom Elements inWeb Components where they encapsulate presentation, state and behavior and are easily composable using a declarative XML-like syntax (JSX), or if preferred can be constructed in plain JavaScript.
In contrast toAngularJS which popularized 2-way data-binding for the web, React offers a controlled one-way data flow that encourages composing your app into modular components that are declaratively designed to reflect how they should look at any point in time.
Whilst on the surface this may not appear as useful as a traditional 2-way data-binding system, React Apps are naturally easier to reason-about as instead of having to think about the serial effects of changes to your running App, your focus is instead on how your App should look like for a particular given state and React takes care of the hard work of calculating and batching all the imperative DOM mutations required to transition your App into its new State.
In order to change the UI, you just update a Components State and React will conceptually re-render the entire app so that it reflects the new state. Whilst this may sound inefficient, React's clever use of a Virtual DOM to capture a snapshot of your UI and its diff algorithm ensures that only the DOM elements that needs updating are changed - resulting in a responsive and high-performance UI.
Components are at the core of React, they're the primary way to encapsulate a modular unit of functionality and can be composed together as we're used to doing with HTML.
AComponent is simply aReact class that implements arender method returning how it should look. As they're just lightweight JS classes they're suitable for encapsulating any level of granularity from a single HTML element:
varHelloMessage=React.createClass({render:function(){return<div>Hello{this.props.name}</div>;}});
to React Chat's entire application, which is itself comprised of more Components:
varChatApp=React.createClass({//...render:function(){return(<div><Headerchannel={this.props.channel}isAuthenticated={this.props.isAuthenticated}activeSub={this.state.activeSub}/><divref="announce"id="announce">{this.state.announce}</div><divref="tv"id="tv">{this.state.tvUrl}</div><Sidebarusers={this.state.users}/><ChatLogref="chatLog"messages={this.state.messages}users={this.state.users}activeSub={this.state.activeSub}/><Footerchannel={this.props.channel}users={this.state.users}activeSub={this.state.activeSub}/></div>);}});
To reference a component, the instance of the React class just needs to be in scope. Once available, components can be rendered into the specified DOM element withReact.render(), e.g:
React.render(<HelloMessagename="John"/>,document.body);
Whilst the components above look like they're composing and rendering HTML fragments, they're instead building up a JavaScript node graph representing the Virtual DOM what it should look like.
The mapping is a simple transformation enabled with Facebook'sJSX JavaScript syntax extension letting you use an XML-like syntax to define JS object graphs, where our earlier<HelloMessage/> React component:
varHelloMessage=React.createClass({render:function(){return<div>Hello{this.props.name}</div>;}});
Gets transpiled to:
varHelloMessage=React.createClass({displayName:'HelloMessage',render:function(){returnReact.createElement("div",null,"Hello ",this.props.name);}});
React relies on the built-in convention for usinglowercase names to reference HTML elements andPascalCase names to reference React Components.
From this we can see that JSX just compiles to a nested graph of JS function calls. Knowing this we can see how to write components in plain JavaScript directly, without JSX. Whilst this is always possible, JSX is still preferred because of its familiar HTML-like syntax whose attributes and elements provide a more concise and readable form than the equivalent nested JavaScript function calls and object literals.
Its close appearance to HTML may mistakenly give the impression that it also behaves like HTML, but as its instead a representation of JavaScript, it does have some subtle differences that you need to be aware of.
The modifiedHelloMessage component below illustrates some of these key differences:
varHelloMessage=React.createClass({handleClick:function(event){//...},render:function(){return(<divclassName="hello"style={{marginLeft:10}}onClick={this.handleClick}> Hello{this.props.name}</div>);});
Firstly JavaScript keywords such asclass andfor shouldn't be used. Instead, React expects JavaScript DOM property names likeclassName andhtmlFor, respectively.
Nextstyle accepts a JavaScript object literal for which styles to set, and requires identifiers to be in JScamelCase likemarginLeft instead of CSS'smargin-left.
There are afew exceptions, but in most cases integer values get converted intopx e.g:
{marginLeft:10} => margin-left:10pxThe{} braces denotes a JavaScript expression, so when double-braces are used like:
{ {marginLeft:10} }It just denotes a JavaScript expression that returns the{marginLeft:10} object literal.
Unlike HTML which has string attributes, Component attributes can be assigned any JavaScript object which is howonClick can be assigned a direct reference to thethis.handleClick method.
The other difference ofonClick is that it uses aSynthetic Event system which mimics (and wraps) a native browser event, but uses a virtual implementation that behaves consistently across all browsers.
Like other attributes, event attributes are exposed using a JS camelCase convention prefixed withon so instead ofclick you'd useonClick. By default event handlers are triggered in the normal bubbling phase and can be instead be registered to trigger in the capture phase by appendingCapture to the event name, e.g:onClickCapture.
The exercise of portingChat to a React-based app highlights some of the notable differences and benefits of a React-based App over a vanilla JavaScript or jQuery based App.
As web apps go, Chat does a great job of implementing thecore features of a chat application in just170 lines of JavaScript, it's able to achieve this by leveraging a number of features in ss-utils.js likeDeclarative Events eliminating the boilerplate and book-keeping typically involved with firing and handling DOM events.
Sometimes rewriting your app to use a framework will result in a net savings in lines of code, typically due to replacing redundant code and bespoke implementations with built-in framework features, but in this case as there was no such boilerplate or repetitive code, so the lines of HTML and JS in the React port ended up doubling.
The increase in code is effectively the price paid for modularity. The original Chat is essentially a monolithic App developed in asingle intertwined block of HTML + JS which is also the smallest unit of encapsulated functionality. The results in any change having the potential to impact any other part of the App so the entire context of the App needs to be kept into consideration with every change.
There's also very limited potential for re-use, with practically no opportunity to extract any code fragments in isolation and re-use as-is elsewhere. Whilst a monolithic App design is manageable (and requires less effort) in smaller code bases, it doesn't scale well in a larger and constantly evolving code-base.
The first step into creating a React app is deciding how to structure the app, as React Components are just lightweight classes the number of components shouldn't affect the granularity in partitioning your app so you can happily opt for the most logical separation that suits your App.
For Chat, this was easy as it was already visually separated by different regions, allowing it to be naturally partitioned into the following distinct logical components:
As well as a top-levelChatApp, used to compose the different components together:
The first step in porting to React is getting the layout and structure of your App right, so for the first pass just the HTML markup was extracted into each Component, where the initial cut ofFooter.jsx before adding any behavior just looked like:
varFooter=React.createClass({render:function(){return(<divid="bottom"><inputref="txtMsg"id="txtMsg"type="text"/><buttonid="btnSend"style={{marginLeft:5}}>Send</button></div>);}});
React has nice support of this gradual design-first dev workflow where the visual appearance of your App remains viewable after each stage of extracting the just HTML markup into its separate Components.
The important concept to keep in mind when reasoning about React's design is that components are a projection for a given State. To enforce this, data flows unidirectionally down from the top-most Component (Owner) to its children via properties. In addition, Components can also maintain its own State which together with properties, are all that's used to dynamically render its UI on every state change.
We can visualize how data flows throughout a React App by tracking the flow ofusers through to the different React Chat components.
The list of active users for the entire App is sourced from theusers collection in the top-levelChatApp component's State. It's then passed down to its childSidebar andChatLog components via properties:
varChatApp=React.createClass({//...render:function(){return(<div> ...<Sidebarusers={this.state.users}/><ChatLogref="chatLog"messages={this.state.messages}users={this.state.users}activeSub={this.state.activeSub}/> ...</div>);}});
Each component has access to all properties assigned to it via itsthis.props collection. TheSidebar then usesthis.props.users to generate a dynamic list of childUser components for each currently subscribed user.
In addition to properties,Sidebar maintains its ownhideExamples state (invisible to other components) which it uses to determine whether to show theExample Commands or not, as well as what appropriateshow orhide action label to display.
TheSidebar example below also shows how children are able to modify its parent state via theonClick={this.toggleExamples} callback, invoking the Sidebar'stoggleExamples method and modifying thehideExamples state withthis.setState() - which is the API used to modify a Components State and re-render its UI:
varSidebar=React.createClass({getInitialState:function(){return{hideExamples:false};},toggleExamples:function(e){this.setState({hideExamples:!this.state.hideExamples});},render:function(){varheight=this.state.hideExamples ?'25px' :'auto',label=this.state.hideExamples ?'show' :'hide';return(<divid="right"><divid="users">{this.props.users.map(function(user){return<Userkey={user.userId}user={user}/>;})}</div><divid="examples"style={{height:height}}><spanonClick={this.toggleExamples}>{label}</span> ...</div></div>);}});
Finally user data makes its way down to the childUser Component, but this time only gets passed a singleuser object which it uses to render the users avatar and name:
varUser=React.createClass({handleClick:function(){Actions.userSelected(this.props.user);},render:function(){return(<divclassName="user"><imgsrc={this.props.user.profileUrl||"/img/no-profile64.png"}/><spanonClick={this.handleClick}>{this.props.user.displayName}</span></div>);}});
What's telling about Components is that they don't have a dependency on its parent Component as it's able to render everything it needs by just looking at its assigned properties and localized state. This decoupling is what allows Components to be re-usable and much easier to reason-about as its behavior can be determined in isolation, unaffected by any other external State or Component.
React's strict one-way data flow does present a challenge with how a child component can communicate with the rest of the application and vice versa. To tackle this Facebook has released aguidance architecture called flux which introduces the concept of a central Dispatcher, Actions and Stores.
In theUser component we start to see a glimpse of this with theActions.userSelected that's triggered whenever a users name is clicked. Effectively this is just raising an event visible to the rest of the application which any component can listen to and handle.
In this case theFooter component is interested whenever a user is clicked so it can pre-populate the Chat TextBox with the users name, ready to send a private message. For this we useReflux's conveniencelistenTo mixin to listen to a specific action:
varFooter=React.createClass({mixins:[Reflux.listenTo(Actions.userSelected,"userSelected"),Reflux.listenTo(Actions.setText,"setText")],userSelected:function(user){this.setText("@"+user.displayName+" ");}setText:function(txt){ ...}, ...});
This can be made even shorter with theReflux.listenToMany convenience mixin which lets you listen to ALL actions for which a Component provides implementations for, e.g:
varFooter=React.createClass({mixins:[Reflux.listenToMany(Actions)],userSelected:function(user){this.setText("@"+user.displayName+" ");}setText:function(txt){ ...}, ...});
Whilst this does save boilerplate, it's less explicit than declaring which specific actions your Component listens to, with the potential for accidental naming clashes in larger apps.
Earlier we introducedFlux - Facebook's guidance for designing large React apps by adopting a unidirectional data flow architecture in favor of a traditional MVC pattern. Whilst Flux is primarily meant to serve as an architectural pattern, Facebook also provide implementations for Flux concepts in theFlux repo and referenceTodoMVC application.
They also provide the diagram below to help illustrate the structure and data flow of a Flux application and how its different concepts fit together:
In contrast to React's small and beautifully simple, purpose-specific API, Flux introduces a lot of concepts, boilerplate, moving pieces and indirection for relatively little value. Whilst the primaryActions andStores concepts in Flux are sound, the implementation inFlux TodoMVC wont be winning any awards for simplicity, code-size, clarity or readability.
Luckily Flux is an optional external library and React has a thriving community who are able to step in with more elegant solutions, one of the standouts in this area isReflux, a simpler re-imagination of the Flux architecture that's streamlined down to just:
Simply,Components listen toStore events to get notified when their state has changed and can invokeActions whichStores (and anything else) can listen and react to.
Reflux also includes a number of convenience API's to reduce the boilerplate required when hooking the different pieces together. More information about the inspiration, benefits and differences ofReflux vs Flux can be found in the informative posts below:
Creating actions is an example of one the areas that's much improved with Reflux which is able to create multiple actions in a single function call, which made it trivial to define allActions used throughout React Chat with just:
varActions=Reflux.createActions(["didConnect","addMessages","removeAllMessages","recordMessage","refreshUsers","showError","logError","announce","userSelected","setText"]);
Depending on number of actions in your App, you may prefer to logically group actions in multiple variables.
Once defined, actions can be triggered like a normal function call, e.g:
Actions.userSelected(user);
Which as a result can be invoked from anywhere (i.e. not just limited to Components). E.g. React Chat invokes them inServer Event Callbacks to trigger Store updates:
$(this.source).handleServerEvents({handlers:{onJoin:Actions.refreshUsers,onLeave:Actions.refreshUsers,chat:function(msg,e){Actions.addMessages([msg]);}}, ...});
This is the essence of howChatApp works, with any Chat messages sent inFooter.jsx posted using ajax to the back-endServerEvents Services which in turn fires local JavaScript ServerEvent handlers that invokes the globalrefreshUsers andaddMessages Actions in order to update the App's localmessages andusers State.
Stores are a Flux concept used to maintain application state, I like to think of them as Data Controllers that own and are responsible for keeping models updated, who also notify listeners whenever they're changed.
Typically you would setup a different Store to manage different model and collection types, e.g. React Chat has aMessagesStore which holds all the messages that should appear in theChatLog as well as aUsersStore to maintain the list of currently subscribed users which gets updated when anyone calls theActions.refreshUsers Action. OnceUsersStore gets back an updated list of users it then notifies all its listeners with the updatedusers collection with thethis.trigger() API:
varUsersStore=Reflux.createStore({init:function(){this.listenTo(Actions.refreshUsers,this.refreshUsers);this.users=[];},refreshUsers:function(){var$this=this;$.getJSON(AppData.channelSubscribersUrl,function(users){varusersMap={};$.map(users,function(user){usersMap[user.userId]=user;});$this.users=$.map(usersMap,function(user){returnuser;});$this.trigger($this.users);});}});
AsUsersStore only needs to listen to a single action I'll prefer to use the explicitthis.listenTo(action, callback) API. Although for Stores likeMessagesStore that needs to listen to multiple actions, you can make use of the convenientthis.listenToMany(actions) API to reduce the boilerplate for registering multiple individual listeners, e.g:
varMessagesStore=Reflux.createStore({init:function(){this.listenToMany(Actions);this.messages=[];},logError:function(){ ...},addMessages:function(msgs){ ...},removeAllMessages:function(){ ...}, ...});
Listening toStore events is similar to listening toActions which can be registered using theReflux.listenTo() convenience mixin, defining which method to call whenever aStore is updated.
ChatApp uses this to listen for updates from theMessagesStore andUsersStore, it's also the only Component that listens directly toStore events which it uses to updates its local state. The updatedmessages andusers then flow down to child components via normal properties:
varChatApp=React.createClass({mixins:[Reflux.listenTo(MessagesStore,"onMessagesUpdate"),Reflux.listenTo(UsersStore,"onUsersUpdate")],onMessagesUpdate:function(messages){this.setState({messages:messages});},onUsersUpdate:function(users){this.setState({users:users});},render:function(){varshowTv=this.state.tvUrl ?'block' :'none';return(<div> ...<ChatLogmessages={this.state.messages}users={this.state.users}/> ...<Footerusers={this.state.users}/></div>);}...});
TheFlux Application Architecture refers to these top-level Components which connect toStores and passes its data down to its descendants asController Views which is a recommended practice in order to be able to reduce dependencies and keep Child Components as functionally pure as possible.
Once your app is in a state that's ready for deployment we can make use of the grunt tasks to package and optimize the App and deploy it viaWeb Deploy.
The/wwwroot_build folder contains the necessary files required for deployments including:
/wwwroot_build /deploy # copies all files in folder to /wwwroot appsettings.txt # production appsettings to override dev defaults /publish config.json # deployment config for WebDeploy deployments 02-package-server.bat # copies over server assets to /wwwroot 03-package-client.bat # optimizes client assets and copies to /wwwroot 04-deploy-app.bat # deploys /wwwroot using remote settings in config.json package-and-deploy.bat # runs build tasks 02 - 04 in 1 stepThe minimum steps to deploy an app is to fill inconfig.json with the remote IIS WebSite settings as well as a UserName and Password of a User that has permission to remote deploy an app:
{"iisApp":"AppName","serverAddress":"deploy-server.example.com","userName":"{WebDeployUserName}","password" :"{WebDeployPassword}"}Then just run thepackage-and-deploy grunt task (or.bat script) which copies over the the server and client assets into the/wwwroot folder which contain the physical files of what gets deployed.
Thepackage-server task will copy over the required .NET.dll and ASP.NET files as well as any files in/wwwroot_build/deploy which can be used to customize the production website.
Thepackage-client task also optimizes any.jsx,.js and.css so only the bundled and minified versions get deployed. The resulting website that ends up getting deployed looks something like:
/wwwroot /css /img /js app.jsx.js /lib jquery.min.js react.min.js reflux.min.js appsettings.txt default.cshtml Global.asax web.configThis folder is then packaged into awebdeploy.zip file and deployed to the remote server, after it's finished running you will be able to run your app on the remote server which for React Chat is:http://react-chat.servicestack.net/
This is just a brief overview of packaging and deploying with the VS.NET templates Grunt/Gulp build systems, more in-depth documentation can be found in:
About
ServiceStack Chat rewritten into ES5 0.12 React and Reflux
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.



