Meteor Blaze - single state helper for ReactiveDict

jankapunkt

Jan Küster

Posted on June 1, 2023

Meteor Blaze - single state helper for ReactiveDict

Today, I optimized a few older Blaze templates. I found to use a helper for each simple state access. A very inconvenient and unnecessary issue that led to bloated and less reasonable code.

Take a look at the following example:

<template name="welcome">
  {{#if loadComplete}}
    <h1>Welcome, {{name}}</h1>
    <p>Today's date is {{date}}</p>
  {{else}}
     ...loading
  {{/if}}
</template>
Enter fullscreen mode Exit fullscreen mode
import { ReactiveDict } from 'meteor/reactive-dict'

Template.welcome.onCreated(function () {
  const instance = this
  instance.state = new ReactiveDict()
  instance.state.set('date', new Date().toLocaleString())

  instance.autorun(() => {
    const user = Meteor.user()
    if (user) {
      instance.state.set({
        name: `${user.firstName} ${user.lastName}`,
        loadComplete: true
      })
    }
  })
})
Enter fullscreen mode Exit fullscreen mode

This template would require three helpers in order to render the given logic from the HTML part:

Teplate.welcome.helpers({
  loadComplete () {
    return Template.instance().state.get('loadComplete')
  },
  name () {
    return Template.instance().state.get('name')
  },
  date () {
    return Template.instance().state.get('date')
  }
})
Enter fullscreen mode Exit fullscreen mode

Why is this a problem?

The above code has issues on many levels. First, it does not scale at all. Think about having a template that requires not three but 10+ variables.

This would require 10 helpers for simple state access. What a bloated code! The consequence is higher maintenance effort, refactoring effort (think about renaming things) and increased proneness to errors.

It also screams for an abstraction as you can see there is simply the same pattern for each helper.

Fortunately, we can merge this into a single helper and I'd like to demonstrate two different approaches:

Approach A - pass the name of the state-variable to access as argument to the helper

Teplate.welcome.helpers({
  state (name) {
    return Template.instance().state.get(name)
  }
})
Enter fullscreen mode Exit fullscreen mode

This approach is straight forward and easy to implement. The helper is called with the name in quotes as parameter to state.

<template name="welcome">
  {{#if state "loadComplete"}}
    <h1>Welcome, {{state "name"}}</h1>
    <p>Today's date is {{state "date"}}</p>
  {{else}}
     ...loading
  {{/if}}
</template>
Enter fullscreen mode Exit fullscreen mode

A downside of this approach is a decreased readability and the need for nested function calls, in case you want to pass the accessed state to another helper from the HTML part.

Approach B - return a proxy to the state to accesses the state-variable by property name

This approach creates a new Proxy to state that wraps the .get(name) call in a get-trap:

import { ReactiveDict } from 'meteor/reactive-dict'

Template.welcome.onCreated(function () {
  const instance = this
  instance.state = new ReactiveDict()
  instance.state.set('date', new Date().toLocaleString())

  instance.autorun(() => {
    //...
  })

  instance.stateProxy = new Proxy( {}, {
    get: function (target, p, receiver) {
      return instance.state.get(p)
    }
  })
})

Teplate.welcome.helpers({
  state (name) {
    return Template.instance().stateProxy
  }
})
Enter fullscreen mode Exit fullscreen mode

Here, you access variables by dot notation, wich looks readable and intuitive:

<template name="welcome">
  {{#if state.loadComplete}}
    <h1>Welcome, {{state.name}}</h1>
    <p>Today's date is {{state.date}}</p>
  {{else}}
     ...loading
  {{/if}}
</template>
Enter fullscreen mode Exit fullscreen mode

A potential downside of this approach is that you need a basic understanding about Proxies in order to implement more complex cases. Furthermore, the dot notation can trick you (or others) into thinking, that you are dealing with a plain object and not a state-proxy.

Summary

This little article demonstrates two ways to scale your templates or to reduce bloat from existing templates. Which one to select is up to you and your team.

Both approaches allow further abstractions, like embedding state and the state () helper into each of your Templates by default. I will demonstrate this approach in the next post as a follow-up.


About me

I regularly publish articles here on dev.to about Meteor and JavaScript.

You can also find (and contact) me on GitHub, Twitter and LinkedIn.

If you like what you are reading and want to support me, you can sponsor me on GitHub or send me a tip via PayPal.

Keep up with the latest development on Meteor by visiting their blog* and if you are the same into Meteor like I am and want to show it to the world, you should check out the Meteor merch store*.

💖 💪 🙅 🚩
jankapunkt
Jan Küster

Posted on June 1, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related