ViewModel for React Alpha


#61

Sure, but they’ll be bringing VM with them.


#62

Hi Manuel, I am amazed by your work and sad that it doesn’t get more attention.
I believe you should add this section in your guide because it may be a common use case for developers.

On another note I would say you need to talk about how you would typically handle routing and animations with VM (I believe that’s the only thing that makes Vue way more appealing than the React ecosystem, but maybe VM has something to do here?)

Thanks for your availability and dedication to this amazing project


#63

I’m updating the docs.

As for routing and animations, the beauty of VM is that it sits on top of React, that means you can use any tool from the React ecosystem. That includes routing, animations, SSR, caching, etc.

I’ll see if I can get you an example of an animation.


#64

Added a few sections to the docs:

Can you give an example of animations which can’t be dealt with styles?
For instance, take an example from the MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions

You can write it with ViewModel like this:

App({
  showBox1: false,
  innerBoxStyle() {
    const styles = this.styles()
    return this.showBox1() ? styles.box1 : styles.box;
  },
  styles: { 
    parent: `
      width: 250px;
      height:125px;`,
    box: `
      width: 100px;
      height: 100px;
      background-color: red;
      font-size: 20px;
      left: 0px;
      top: 0px;
      position:absolute;
      WebkitTransitionProperty: width height background-color font-size left top transform -webkit-transform color;
      WebkitTransitionDuration: 0.5s;
      WebkitTransitionTimingFunction: ease-in-out;
      transition-property: width height background-color font-size left top transform -webkit-transform color;
      transition-duration: 0.5s;
      transition-timing-function: ease-in-out;`,
    box1: `
      transform: rotate(270deg);
      WebkitTransform: rotate(270deg);
      width: 50px;
      height: 50px;
      background-color: blue;
      color: yellow;
      font-size: 18px;
      left: 150px;
      top: 25px;
      position: absolute;
      WebkitTransitionProperty: width height background-color font-size left top transform -webkit-transform color;
      WebkitTransitionDuration: 0.5s;
      WebkitTransitionTimingFunction: ease-in-out;
      transition-property: width height background-color font-size left top transform -webkit-transformv color;
      transition-duration: 0.5s;
      transition-timing-function: ease-in-out;`
  },
  render() {
    <div>
      <div b="style: styles.parent">
        <div b="style: innerBoxStyle, toggle: showBox1">Click to animate</div>
      </div>
    </div>
  }
});

Also, dunno if you’ve seen this example I wrote recently:


#65

Hi Manuel, thanks for your real quick answer and update in the doc ! (is it christmas? ^^)

Regarding CSS transitions, as I see your code it is true we can achieve a lot with styles.
I believe what makes Vue so good is that the most common transitions are easily integrated and do not make the code so verbose.(name=‘fade’ , mode = ‘in-out’ … you can also find these most common transitions in semantic-ui)
The real benefit of their easy integration in Vue is that you can use them when a component is loaded, or when changing routes, see:
https://router.vuejs.org/en/advanced/transitions.html

I believe this is really harder to do in React (but maybe I am mistaken, I’ve never used react-router, just got headaches looking at the doc), that’s why I was thinking this could be nice to show example of routing and animating in VM.

Otherwise I’m pretty sure your doc is already very nice and this is a good thing it is small and fits in one page (really easier doc than other frameworks I’ve seen)

What would really help to get started would be some easy step by step tutorial for your phonebook app, and another one for a complex use case using third party libraries (router with SSR and animated routes, google map integration, and so on…)

Thanks for the nice job

PS: I saw in your help section that you have no doc
for the delay binding yet, and I casually see this code:

autorun(c) {
    if (c.firstRun) return; // Skip first time

But I believe firstRun is not documented either. Am I missing something ?
EDIT: I see that you use the same Tracker as in Meteor, it may be worth it to either explain your own tracker in the doc or link to meteor doc for Tracker, as this framework is decoupled from meteor.

See you


#66

I’ve been thinking about animations but it’s still not clear to me what the problem is. Can you give me a concrete example of a Vue component with an animation? Something that can’t be easily done in ViewModel.

btw, I’ll update the docs with delay, firstRun, and ViewModelExplorer later on.


#67

I believe CSS transitions are hard to learn and generate a whole lot of boilerplate code when you just want to go easy.

Vue has a transition wrapper component that can be used easily with either javascript or CSS transitions and animations (with hooks making it compatible with most animations libraries)

It provides some nice animations out of the box without any configuration e.g. to animate when your element is rendered for the first time, you could use

<transition appear>
  <!-- ... -->
</transition>

And you can set the timing directly in the transition wrapper:
<transition :duration="{ enter: 500, leave: 800 }">...</transition>

Then you have all the transitions of type enter-leave, let’s say your component looks like that:
<transition name="my-transition" mode ="in-out">
Vue lets you define your classes for each state of the transition (at start, during transition, once it is ended) for the 2 movements (enter and leave).
The modes are explained like that:

in-out: New element transitions in first, then when complete, the current element transitions out.
out-in: Current element transitions out first, then when complete, the new element transitions in.

Then you have transitions linked to Vue router so you can easily animate route transitions.
I won’t go too much into details because I am not an expert myself.

I don’t know if this is really in the scope of Viewmodel, but my dream VM would let me define simple transitions without any boilerplate at first, with ability to go deeper and define my own transitions with CSS or javascript and let me reapply them among different components, with different triggers (linked to VM properties)
On a side note, I believe there should be a simple router that is deeply integrated with these transitions (and that can work with SSR) so that I can setup my routes, my transitions between all my routes, and using my browser back button would play the animations in reverse.

I believe we could build UI way more easily if all of this could be at reach: I would use existing animations or I would easily customize and export some, then I would define my routes, components and I could use any animations for transitionning between 2 components or 2 routes easily.

Even though I admit Vue has made a nice work, I still find it is too difficult to handle.
I dream that someday all the designers in my team could be able to read the UI code…

PS: going to Peru for 2 weeks holidays, so slower inputs from me. See you later !


#68

Hello here, I was just thinking about ViewModel and I would love to use it with my next project. It is still hard to convince people that haven’t tried it yet so I believe what could be interesting to do is:

  • Add a perf benchmark vs standard react+ redux to show that it is not too ugly (I believe)
  • ViewModel adds something like 10kb to the build but this info is very hard to find in the docs (I am not sure about this)
  • make a starter project with inferno (I tried and failed miserably)+ decouple inferno vs react docs
  • make more integration examples with standard solutions (semantic ui, bootstrap, server side rendering, routing, with nextjs and meteor for example…)
    -One big concern is that people tend to think easy-to-use frameworks are lacking functionalities and that makes them a problem in the long run. We should show that this very possible to use all react possibilities (give many example of integration with third party react components)

List all the caveats that we can find in this thread, and all over meteor forum
(By the way is it possible to use Airbnb’s react to sketch with VM components?)

And decouple Viewmodel docs from meteor, and communicate to more react places

PS: I tried universal-router and it seems perfect with Viewmodel ,I am soon ready to share a starter project with client side routing as their SSR is not working with meteor)


#69

Performance:
I don’t have benchmarks for the React version but the Blaze blaze one can be up to 5 times slower than plain Blaze depending on the test. I haven’t done any performance optimizations to VM yet. The primary goal was to optimize developer productivity and happiness. Performance hasn’t been a priority because it’s just fine 99% of the time. The worst case scenario is that you have a performance critical component which you write in plain React, not with VM.

Size
As with performance, there has been no attempt to reduce the size (yet). I honestly don’t know how much it adds but ~10K gzipped sounds about right.

Inferno
If you give me a hello world with Inferno I’d be happy to see if I can use VM with it.

Examples
There’s TodoMvc and and MS JsServices with a live example. You can use any CSS framework the way you’re used to, it has practically nothing to do with VM. The same can be said about SSR and other setups, you start with an SSR setup which uses React and you just write the components “VM style” instead.

Concerns
Biggest (IMO): It’s a one man show. It’s not that popular so I’m the sole maintainer. Kind of a chicken and egg problem there.
It won’t be able to do X: Again, the worst case scenario if that you end up writing a component in plain React. I can’t think of scenario where that’s true (aside for performance), but it’s an option.

Meteor coupling
It’s just not. The VM-React docs are stack agnostic. It only mentions Meteor in a few places like “if you’re using Meteor set it up this way”.

PR
I clearly don’t know how to promote VM but one thing I know is that if VM ever becomes popular it won’t be in the React community. Everything VM stands for is anathema to the React community. VM is all about being declarative in the name of reducing boilerplate. The react community is all about being imperative in the name of being explicit. That’s why VM typically ends up cutting code in half.


#70

Regarding Inferno, for what it’s worth I just took a Meteor + React + ViewModel sample app and all I had to do to switch to Inferno was modify main.js and .babelrc:

main.js

import { Meteor } from "meteor/meteor";
import { Tracker } from "meteor/tracker";
import Inferno from "inferno";
import { App } from "../imports/App";
import ViewModel from 'viewmodel-react';

// Use Meteor's dependency management
ViewModel.Tracker = Tracker;

Meteor.startup(() => {
  Inferno.render(<App />, document.getElementById("app"));
});

.babelrc

{
  "presets": ["es2015"],
  "plugins": [
    [
      "viewmodel-react-plugin",
      {
        "useInferno": true
      }
    ],
    "babel-plugin-syntax-jsx",
    "transform-object-rest-spread",
    "transform-es2015-spread",
    [
      "babel-plugin-inferno",
      {
        "imports": true
      }
    ]
  ]
}

Since the App component and all children are written “VM style” I didn’t have to change anything else.


#71

Hi Manuel

Thought of the day:

I don’t get why VM has not a different proposed syntax for IF / ELSE blocks and FOR loops in React (to get closer to the blaze experience)
Have you seen https://github.com/AlexGilleran/jsx-control-statements ?
It looks like this package could be a good fit in VM (as an additional proposed syntax?)

Also, it would be useful to have a dynamic binding that would extend the class binding to any attribute.
For example it would be very useful for aria-attributes and accessibility.

<button b="aria-hidden: { isRendered: false, !isRendered: true}" >The Button that screen reader can see once loaded</button>

I know I can do it with custom bindings but this doesn’t feel right to redo the wheel right?

Regards


#72

I think it adds boilerplate for little to no benefit. For example:

      <If condition={true}>
        <span>IfBlock</span>
      </If>

vs

      <span b="if: true">IfBlock</span>

The choose is interesting but again I don’t see it as a better solution deserving to be baked into the framework:

<Choose>
  <When condition={ test1 }>
    <span>IfBlock</span>
  </When>
  <When condition={ test2 }>
    <span>ElseIfBlock</span>
  </When>
  <Otherwise>
    <span>ElseBlock</span>
  </Otherwise>
</Choose>

vs

      <span b="if: test1">IfBlock</span>
      <span b="if: test2">ElseIfBlock</span>
      <span b="if: testsFailed">ElseBlock</span>

There’s too much noise in the Choose version. And yes, in the VM version you have to add the testsFailed method but it’s not that bad.

As for properties, you can specify which properties you want to use as bindings: http://viewmodel.org/#BasicsAttributes

It can’t be arbitrary because for better or worse VM takes the stance of “if it can’t find a binding it will treat it as an event”. The Blaze version didn’t have that problem because, until now, there was no SSR for Blaze. With React you need to use attribute={this.property()} so it works with SSR. That means VM needs to know if it’s a property or an event at compile time.

In your case you can use:

    "plugins": [
      [
        "viewmodel-react-plugin",
        {
          "attributes": ["aria-hidden"]
        }
      ]
    ]

And then:

<button b="aria-hidden: !isRendered" >The Button</button>

#73

As always this is brilliant.

I would argue this should go in the docs, but maybe I can send a Pull Request ? xD


#74

But it is in the docs (see the link above). It’s even in the basics section.


#76

I don’t know if it has something to do with Meteor, but I exactly copied your .babelrc file in my Meteor + VM project.
I then changed my dependencies for inferno’s and removed react , react-dom, your Viewmodel Explorer…

  "dependencies": {
    "babel-polyfill": "^6.23.0",
    "babel-preset-es2015": "^6.24.1",
    "babel-runtime": "^6.23.0",
    "core-js": "^2.4.1",
    "history": "^4.7.2",
    "inferno": "^3.9.0",
    "inferno-component": "^3.9.0",
    "inferno-server": "^3.9.0",
    "meteor-node-stubs": "~0.2.4",
    "universal-router": "^3.2.0",
    "viewmodel-react": "^2.4.1",
    "viewmodel-react-plugin": "^3.1.0"
  }

I replaced all my React and React-dom references by Inferno, I can compile without error, but then I get a client-side error

Uncaught (in promise) ReferenceError: React is not defined
    at Object.action (app.js:979)
    at resolveRoute (modules.js:18394)
    at next (modules.js:18467)
    at modules.js:18472
    at meteor.js:1109
    at <anonymous>

And looking into the app.js file and searching for React, I found this kind of stuff for each VM component:

var Fourofour = function (_Component) {                                                                                //
	(0, _inherits3.default)(Fourofour, _Component);                                                                       //
	(0, _createClass3.default)(Fourofour, [{                                                                              //
		key: "render",                                                                                                       //
		value: function () {                                                                                                 //
			function render() {                                                                                                 //
				return React.createElement(                                                                                        //
					"h1",                                                                                                             // 3
					null,                                                                                                             // 3
					"404 Error: What do you try to do ? I don't know but try to remove some stuff in the URL"                         // 3
				);                                                                                                                 // 3
			}                                                                                                                   //
                                                                                                                       //
			return render;                                                                                                      //
		}()                                                                                                                  //
	}]);                              

As you can see VM is still on React for rendering my components…

I also tried to set babel like I found here, so I ran
meteor npm i --save-dev babel-plugin-inferno

At which point my package.json now also contains

  "devDependencies": {
    "babel-plugin-inferno": "^3.2.0"
  }

But it didn’t work. (PS: I don’t know much about babel, so I may be doing something wrong…)


#77

There was an error in the docs. The configuration should be:

{
  "presets": ["es2015"],
  "plugins": [
    [
      "viewmodel-react-plugin",
      {
        "useInferno": true
      }
    ],
    "babel-plugin-syntax-jsx",
    "transform-object-rest-spread",
    "transform-es2015-spread",
    [
      "babel-plugin-inferno",
      {
        "imports": true
      }
    ]
  ]
}

Note that as of now ViewModelExplorer only works with React.


#78

Thank you it worked ! now I see that defer binding doesn’t work on Inferno Components. Do you have a place to store inferno-related issue or your react repo on github will do?


#79

Same repo will do…


#80

Hi there,

I want to use a ViewModel repeat loop in Inferno, very good performance out of the box for static data, but when I try to apply it on the result of a mongoCollection.find(), I run into very serious performance issues

import { AdsCollection } from '/imports/_Collections/_Collections'

//uncomment below and replace fake2Ads by adlist to try mongo collection loop
//const adlist= AdsCollection.find({})
const fake2Ads = [
  { id: 1, title: 'Rendered', description: 'From Client' },
  { id: 2, title: 'Rendered', description: 'From Client' },
  { id: 3, title: 'Rendered', description: 'From Client' },
  { id: 4, title: 'Rendered', description: 'From Client' },
  { id: 5, title: 'Rendered', description: 'From Client' },
  { id: 6, title: 'Rendered', description: 'From Client' },
  { id: 7, title: 'Rendered', description: 'From Client' },
  { id: 8, title: 'Rendered', description: 'From Client' },
  { id: 9, title: 'Rendered', description: 'From Client' }
]
const styles = {
  li: {
    color: 'blue',
    'font-weight': 'bold'
  }
}

Ads({
  ads: [],
  fakeAds: [
    { id: 1, title: 'Loading', description: 'please wait' },
    { id: 2, title: 'Loading', description: 'please wait' },
    { id: 3, title: 'Loading', description: 'please wait' },
    { id: 4, title: 'Loading', description: 'please wait' },
    { id: 5, title: 'Loading', description: 'please wait' },
    { id: 6, title: 'Loading', description: 'please wait' },
    { id: 7, title: 'Loading', description: 'please wait' },
    { id: 8, title: 'Loading', description: 'please wait' },
    { id: 9, title: 'Loading', description: 'please wait' }
  ],
  created() {
    console.log('Ads created')
    if(Meteor.isClient) {
      console.log('Ads loading')
      this.ads(fake2Ads) //Set Ads to the value in the DB
      console.log('Ads loaded')
    }

  },
  isRendered: false,
  rendered() {
    this.isRendered(true)
    console.log('Ads rendered')
  },  
  styles: styles,
  render() {
    <div>
      <ul b="if: !isRendered">
        <li b="repeat: fakeAds, style: styles.li">
          <span b="text: repeatObject.title + ' ' +repeatObject.description"></span>
        </li>
      </ul>

      <ul b="if: isRendered">
        <li b="repeat: ads, style: styles.li">
          <span b="text: repeatObject.title + ' ' +repeatObject.description"></span>
        </li>
      </ul>
    </div>
  }
});

The behavior of rendering is not what a user would expect here:

  • The repeat: fakeAds will happen first on server-side, because server-side will never set isRendered to true
  • The different console.log that were supposed to run on the client will get triggered console.log('Ads loading'), console.log('Ads loaded'), console.log('Ads rendered'), so isRendered is now true
  • At this point,my fakeAds list is not displayed anymore (see my b=“if…”) but the new ads list is still not displayed either !
  • The repeat: ads loop will display results long after ( a few seconds on my tests )

What I would expect is that iterating over a mongo collection would be as fast as iterating on a static array.

Do you have any idea what is causing this behavior? Am I importing my collections or using them in a way that could hurt performance or is it something more technical that has to do with either Meteor or Viewmodel ?

Would there be a workaround to prevent displaying empty list for a few seconds ?

EDIT: I found the solution, it has Nothing to do with ViewModel , but with isRendered props. I should not have tested this, but should’ve tested if the collection is not empty instead. I will add a code snippet later

Ads({
  ads: function () {
    if (Meteor.isClient) {
      //We can load the collection from MongoDB in the client-side
      const result = AdsCollection.find({}).fetch()
      //Wait for result to contain something, otherwise display loadingAds
      return result.length ? result : this.loadingAds();
    }
      //I cannot find a way to render from mongoDB server-side, so I use fake data instead
      return this.loadingAds();
  },
  loadingAds: [
    { _id: 1, title: 'Loading', description: 'please wait' },
    { _id: 2, title: 'Loading', description: 'please wait' },
    { _id: 3, title: 'Loading', description: 'please wait' },
    { _id: 4, title: 'Loading', description: 'please wait' },
    { _id: 5, title: 'Loading', description: 'please wait' },
    { _id: 6, title: 'Loading', description: 'please wait' },
    { _id: 7, title: 'Loading', description: 'please wait' },
    { _id: 8, title: 'Loading', description: 'please wait' },
    { _id: 9, title: 'Loading', description: 'please wait' }
  ],
  render() {
    <div>
      Below you can find the ads
      <ul>
        <Ad b="repeat: ads, key:_id" id={repeatObject._id}/>
      </ul>
    </div>
  }
});

#81

@manuel you’re a real genius. Thanks for your framework.

I can’t say how thankful I am to use this instead of redux !!! You planned everything, it is so enjoyable. full control of HTML here. It plugs so well with everything else, I feel like I can build a lot without much.

You already anticipated the problems to come. It really feels futuristic to use ViewModel.
Thanks !
:clap: