A demo of a "Cart & Checkout" done with React-View-Model, CanJS and StealJS
For each stage below switch to the corresponding branch to start (for Stage 1 switch to stage-1
branch).
Steal is the easiest way to start making a react app
- starts with empty
index.html
andindex.js
npm install steal steal-tools react react-dom serve
(serve for serving static)- add
script.start
topackage.json
and just make valueserve
- run
npm start
and just point browser to blank HTML page - create
script
tag in index.html and setsrc
to/node_modules/steal/steal.js
- set
main
attribute onscript
tag toindex
meaning module name forindex.js
- add basic Hello world react app to
index.js
- add
<div id="app"></div>
to index.html to contain react hello world - show Hello world in browser
- Talk about how JSX is just built in because Steal uses babel to transpile
- Marvel at the simplicity, comment about no build step or Create-React-App
- show how easy it is to demo with just an HTML page by setting
main
to@empty
and moving the Hello World inside the script tag - after showing that, talk about how for future goodness main should include the project name from package.json and put it back
A Basic React Cart & Checkout
- show a basic react app (
stage-2
branch), it usessetState()
and some preset data, along with 2 components - you can see here in
App.js
that we are importing someless
styles, steal can do that, and import pretty much most things, it just needs thesteal-less
plugin to be installed and configured in thepackage.json
under thesteal
property. You can see some other configuration there too. - mention that we've imported bootstrap and react bootstrap here jsut to make it look a little nicer
Modlets
- A BREIF ASIDE about Modlets
- show the
ProductList
components directory (zoom in if you can) - We believe in a design pattern called modlets, co-locating all the files a component needs to be modular
- Has a source file, any styles, documentation, test and test files and a demo page
- Can be nested (as seen here with the
ProductCard
)
- talk about the benefits TDD has when it comes to the “design of your code”, you encapsulate better, you think in terms of a consumer first api, you build a piece at a time instead of all at once… Modlets have that same kind of effect and more
-Encourages best practices
- Easy to catch missing tests or docs in a Code Review
- Encourages good maintenance practices
- Developers are more likely to update tests and documentation if they are sitting right next to the module they are editing, not hidden away in a test or docs folder
- Aids in debugging
- Is the problem in the demo page? No: look up, yes: look down
- Streamlines development
- You don’t have to load the whole app, or click around to get in the right state just to work on this small ui peice
- Encourages good maintenance practices
- Steal makes Modlets easy, if you want to talk more about Modlets, come talk to me after the presentation
Using Models for stores
- We're going to use observable Models for our "store", React components for our View, and use observable ViewModels for our views state and a reactive connection to the models
CanJS Observables
- In CanJS we have this concept of observables, values, objects and list that emit events, that you can subscribe to, when their properties change (Just like MobX or the something like the data objects in Vue.js)
- CanJS has a few but the 2 we are going to use today are
DefineMaps
which act as observable objects, andDefineLists
which are like observable Arrays - So let's set up the Models, we're going to have
Products
which are going to have aid
,name
,image
and description(desc
) properties - Create a
product.js
file, insrc/models/
npm install can-define
- create a
Product
class by importing and extendingDefineMap
, give itid
,name
,image
anddesc
properties, all of typestring
, and export it as default - "But what we really want is a list of products, so we create a typed list like this"
- import and extend a
DefineList
and assign it to theProduct.List
property, mark all it's elements"#"
as typeProduct
- "We have a library called can-connect, that let's you connect models to data-sources... and by convention we give
Product
a staticgetList
method that returns a ProductList of Products - Implement
Product.getList({})
- "...Now one cool thing you can do do aid development is give DefineMaps and Lists a name, passed as the first argument" - name both classes
- "Okay, so now we want a
Cart
Model, and really, when you think about it, aCart
is just a list of products..." - Create a file name
Cart.js
insrc/models/
- import and extend
Product.List
for aCart
model. - implement a static
getCart()
singleton method, and explain this should probably be something else, but this is just for a demo, so we'll just have it return a singleton instance ofCart
- implement the
total
computed property (times by a hundred to remove JS Floating-Point problems) - implement a
has(product)
method (to be used in the ProductList later)
ViewModels
- "So now we've got some models, and we're using React for our views, so now we're going to take this app and convert it to use react-view-model to add CanJS View-Models to our React components..."
- show
App.js
and how the root app handles theproducts
and thecart
in it's state, and passes inaddToCart
andremoveFromCart
- We want the
ProductList
component to be more self contained, it should get it's own for it's products (from theProduct
model), and the cart, so that it works on it's own, and will work on its modlet demo page - delete the code in
App.js
that passes all the props toProductList
, as well as the App component methods and the initial state in the constructor (the whole constructor method), also changecart.length
to0
in theCartIcon
props
Add a ViewModel to ProductList component
-
npm install react-view-model
-
In the
ProductList
component, switch the ReactComponent
import for areact-view-model
import (usereact-view-model/component
) -
import and extend
DefineMap
to create aViewModel
class, which can be exported and assign astatic
class propertyViewModel
equal toViewModel
-
checkout the ProductList demo page for the rest of this development
-
import
Product
andCart
from~/src/models/
and talk about steals~
feature -
in
ViewModel
create aproducts
property, make it a computed property that uses the asyncSetValue argument getters get. Set theType
to prodcuct list, and say this is nessecary for React-View-Model to work fully. Fetch products withgetList()
and async-ly assign withsetValue
argument. Explain how this combined with the default value get us what we want -
if there is time, briefly touch on the
Promise Helper
included with React-View-Model that could give you pending and error props -
implement the
cart
property on theViewModel
using theType: Cart
definition and thegetCart()
singleton method with thevalue
definition (explain again, it's just for the demo and cart would propably be more complicated than this) -
"Now because
this.cart
is just an observable array it's trivial to implementaddToCard
andremoveFromCart
methods on the ViewModel -
in the
render()
method changethis.props
tothis.viewModel
and checkout the demo page HTML, you can remove all the boilerplate here, and just render theProductList
component now, because it's resonsible for it's own stuff, and it's own data store. If this demo page were to make requests, you could usecan-fixture
to mock the request-responses and keep this isolated here. -
If there is time: convert the
Product
model to use ajax and the current fixtures to be a fixture-store and addcan-fixture
to the demo page and main app.
Add a ViewModel to ProductList component
- Now you can go back to the main app, and of course, the
App
component is much simpler, but of course, theCartIcon
count is hard coded to0
- since the
CartIcon
component is conceptually connected to the state of theCart
we'll give it a viewmodel too and connect it to our cart model. - import and extend
DefineMap
to create aViewModel
class, which can be exported and assign astatic
class propertyViewModel
equal toViewModel
- import the
Cart
model, and implement thecart
property on theViewModel
using theType: Cart
definition and thegetCart()
singleton method - in the
render()
method changethis.props
tothis.viewModel
- now when you look back at the App page, you'll see the
CartIcon
still says0
- talk about how props get passed actually into the
viewModel
, so there's no error here, because theviewModel
actually has a zero value (change it to 12 to show) - talk about how you can access it on the viewModel, and show how implementing a getter, using the
lastSetValue
arg, can give you access to it, even if you want to override it for your components - then fix it up to just use
this.cart.length
, remove the prop assignment fromApp.js
and show it working as expected. - comment on how simple the
App
component is now, and how the other components can now be worked on independantly because they are truly componentized - note how you don't need a spectial
<Provider />
component to provide Reacts "context" object to the components, that they connect to their state throughModels
, and their viewModel holds the view's state and does any conversions nessecary from Model to View.
Implement the CartList view with React View Model
- If we wanted to keep some state in React, but still keep interoperability with React-View-Model
- "So we've got this premade
CartList
component..." - Pull in the
CartList
component (andCart.html
demo page), put it insrc/components/Cart
- show the coponent and the demo page, that produces random cart items
- modify it to use React-View-Model and DefineMap
- import
react-view-model
,can-define/map/map
and the~/src/model/cart
and create aViewModel
like before that has acart
property with a valueCart.getCart()
(don't forgetType:Cart
)
- import
- check out the demo, see that the Total is now working, and talk about how CanJS observables with a
Type
definition perform automatic conversion - "But now, what if we only want to show the carlist when someone click on the CartIcon..."
- add an onClick prop callback to CartIcon (show demo)
- "Now, this doesn't belong in the viewModels, this should be in the
App
components view model really, but just to show that you don't have to make everything with RVM..." - hook up the
App
state to toggle theCartList
component based on that state - show how it all works together great still, emphasize that because the Cart model is the single source of truth, eveything just React-ivly updates
Bundling for production
- "now you are probably frustrated with how long lading is taking after refresh, and that's because it is individually requesting each script for all your node_modules and source files over ajax. This is actually something we're currently working on for development, but you need faster builds for production..."
- explain steal-tools bundling
- show temporary workaround needed for class properties, explain configuring babel with steal
- setup
npm build
to use envify and PRODUCTION env-var - add
"build": "NODE_ENV=production steal-tools build --envify"
to package.jsonbuild
- add a
production.html
for demo - copy the contents of
index.html
intoproduction.html
and change thesrc
path to/node_modules/steal/steal.production.js
(add.production
) - navigate to
production.html
, show the speed difference, and the network tab
Server-Side Rendering done right
-
"Time is Money: we’ve all seen the reports where for E-Commerce sites milliseconds can be worth millions" (site)
-
"SSR is important for performance, especially on mobile and in areas with slow connections"
-
"current React SSR solutions expect you to change how you build your SPAs, you need to gather all your initial state first then render your app component tree, and send that to the client as a string, but that’s not how we traditionally build SPAs"
-
when we architect apps the CanJS way, each of our components fetch their own data ,mostly asynchronously, how can we get server side rendering to work with this crazy set up
-
Describe how our Done-SSR works with zones to trap async events, actually server side render, and stream the resulting HTML to the server along with any pushes
- We run the rendering for each request in its own context
- We track any fetches, XHRs, or setTimeouts or any async events and wait until they’ve settled or timeout
- Using HTTP/2 we push response streams immediately, removing the latency time (and there’s a fallback for HTTP/1 too!)
- When everything settled we push the HTML to the client
-
Highlight: You don’t need to change your code for SSR, it just works
-
DO WHATEVER NEEDS TO BE DONE TO GET SSR WORKING
Incremental SSR
-
"But, when every millisecond counts, and you need that little ewxtra boost, to show your users something a little bit faster... introducing our experimental super boost: Incrememntal SSR"
-
"Now with the normal SSR, you have to wait for all of your data requests to either resolve or timeout, but what if your query took a long time, or certain items in the page just took a lot longer to get the data for than others, you want your users to see something as fast as possible... so we got this crazy idea"
-
"To help explain the point, let's slow down the API server artificially, so we might actually see the difference"
-
Slow down server API artificially with a
setTimeout
-
Explain the technique
- Show how it pushes the “initial state” html before requests are made, along with the mutation code
- As results come in, push mutation instructions in the stream
- talk about how the requests for the js, css and other resources are all pushed with H/2 push, so the client starts as soon as possible, but so are any API requests
- so the request starts on the server, but then just get's passed up as a stream to the client
- Talk about how when the client catches up, it just takes over, it's the same response stream, so when it catches up, it just takes over
- this technique is specifically for squeezing those extra milliseconds of load time out before the page’s JavaScript takes over (could be worth millions if you are at a large enough scale)
-
"Now what code do we need to change to get this done"
-
Nothing on the client (WIN!)
-
DO WHATEVER NEEDS TO BE DONE TO GET SSR WORKING