3

New to Mithril but really want to like it. I coded demo app a few weeks ago using React and Redux for state management. I also used a library to connect the two, but it all works and can be seen at http://curiousercreative.com/demos/bankDemo/. After reading up on Mithril and liking a lot of what I read, I decided to translate the same demo app into Mithril + Redux, but I can't seem to get my Mithril mounted components to update/redraw and so updates to my Redux store (state) aren't ever reflected in the UI. From my app's js/app.js lines ~144-162, I have my Redux callback and my two top level Mithril components being mounted and passed our state.

// redraws Mithril whenever an action is dispatched
    store.subscribe(function () {
      console.log('happening');
      m.redraw.strategy('all');
      m.redraw(true);
    });

// Render Mithril
    // content
    m.mount(
      document.getElementById('content'),
      m.component(App, store.getState())
    );

    // nav
    m.mount(
        document.getElementById('navContainer'),
        m.component(Nav, store.getState())
    );

If you open a console at http://curiousercreative.com/demos/bankDemo-mithril/, you'll notice a log entry every time you click on a new link (which changes the hash which updates the Redux state store). You'll also notice in the code above that every time that log occurs, a forced redraw of Mithril is supposed to occur, but of course the UI doesn't change. Compare it's behavior to the React demo app further above. In the app's js/components.js file, our two top level components take this state object as an argument and pass it down to their child components.

  var App = {
    controller: function (args) {
      this.activePageId = args.activePageId;
      this.accounts = args.accounts;
      ...

  var Nav = {
    controller: function (args) {
      this.activePageId = args.activePageId;
      this.accounts = args.accounts;
      ...

Why isn't Mithril redrawing?

curiouser
  • 970
  • 2
  • 12
  • 22
  • 1
    The components should access store state internally when they redraw. The code you've written grabs a static snapshot of the store on application initialisation and binds that to the components. Whenever the components redraw, they are reading the same original snapshot. – Barney Nov 15 '15 at 13:41
  • OK, that makes sense. I tried getting the state internally in the top level component controllers as well with similar results. Where would you recommend reaching into the state store? Would you still recommend explicitly calling a force redraw or is there a more elegant way for Mithril to intelligently update? – curiouser Nov 15 '15 at 15:05
  • So now I've got it working properly but I'm not sure that it's within best practices or to my liking. I'm now passing a reference to the store in the mount of the top level components and their the view methods are now calling getState, which I don't like, but works. Based on this, I can assume that mounted components' controller methods are not called on redraws, just the view method. Do I understand that correctly? You can see the updated code at https://github.com/curiousercreative/bankDemo-mithril/blob/master/js/components.js – curiouser Nov 15 '15 at 16:37
  • 1
    This looks like a deeper conversation is needed, fancy posting to the Mithril Google group? Happy to help further but StackOverflow isn't the best format for delving into these issues. Controllers only run on initialisation, so redraws will not re-trigger them. – Barney Nov 15 '15 at 20:28
  • If you'd like to answer with approximately "Mithril doesn't call the controller function when redrawing, it only calls the view function" I'll accept that answer. Is there Mithril component lifecycle documentation? I'd love to see what Mithril calls when for each component to better understand how best to work with it. – curiouser Nov 15 '15 at 22:08
  • Right, your code looks just about right now :) – Barney Nov 16 '15 at 13:08

1 Answers1

4

The problem is that getState() is invoked just once, on initialisation, so the component will always redraw with references to the same original static data store snapshot.

When top level components have been mounted, their controllers execute, their views are compiled and built with access to the controller and any passed in arguments. Controllers execute just once, on initialisation - when a redraw triggers, the view functions are re-executed. This means nested components can be passed new arguments which their views will have access to, but mounted (top level) components have their arguments bound, and because they aren't the descendants of any view themselves, they have to determine their own logic for updating references.

A few ideas jump out when looking at your code:

You could pass in the getState method (without calling it) in mount - Mithril is a great proponent of using functions for data input and retrieval in views (as seen with m.prop, an internal tool for simple model management):

var component = {
  view : function( ctrl, accessor ){
    return m( 'p', accessor().value )
  }
}

m.mount( document.body, m.component( component, store.getState ) )

Dynamic selector attributes tend to read far better in an attribute map. Note that you can combine selector string classes with attribute map classes. The activePage function declared in the controller looks like a separation of concerns, but since the view needs to have a hook to that specific method, the function is explicitly for producing a className, and the logic is so simple, you'd significantly reduce complexity and improve readability by putting that logic directly in the view:

m('div.page', {
  id    : args.id,
  class : args.id === args.activePageId ? 'active' : ''
}, /* ... */ )

Note that you don't need to explicitly wrap virtual DOM children in arrays: you can include them sequentially. Effectively, this means square brackets are redundant in Mithril virtual DOM syntax:

m( '.parent',
  m( '.child1' ),
  m( '.child2' )
) 
Barney
  • 16,181
  • 5
  • 62
  • 76
  • Thanks for the thorough answers and great Mithril best practices and syntax tips! – curiouser Nov 18 '15 at 20:31
  • How have you guys found the Mithril-Redux component pattern compared to React-Redux? @curiouser – Anson Kao Dec 12 '15 at 15:25
  • 2
    @AnsonKao I wish I could tell you, but unfortunately I haven't been working with Mithril and Redux together since this question was asked because a project at work is a React-Redux project. I believe there a Mithril-Redux project that behaves very similar to the React-Redux project. You have lots of options really, just not so many examples. – curiouser Dec 14 '15 at 15:36