Manage the state in the parent and provide to children templates is very often something that needs to be handled in frontend applications.
In React there is a standard called “Lift up the state” and this approach is also adopted in other frontend libraries/frameworks.
To make the code easier we can use some utilities:
// utility code
Blaze.TemplateInstance.prototype.parentTemplate = function (levels) {
var view = Blaze.currentView;
if (typeof levels === "undefined") {
levels = 1;
}
while (view) {
if (view.name.substring(0, 9) === "Template." && !(levels--)) {
return view.templateInstance();
}
view = view.parentView;
}
};
const getFromParent = (template, propName) => {
if(template.parentTemplate()[propName]) {
return template.parentTemplate()[propName];
}
return getFromParent(template.parentTemplate(), propName)
}
Then in the parent component you can create a ReactiveDict
as source of truth for your state:
const STATE_NAME = 'statePropName';
Template.parent.onCreated(function () {
this[STATE_NAME] = new ReactiveDict();
});
In the children you can access like:
const STATE_NAME = 'statePropName';
Template.children.helpers({
myHelper: function () {
return getFromParent(Template.instance(), STATE_NAME);
},
});
Let's see an actual example, imagine that you need to create the Notification options like Google Calendar
Take a look in the code below as the state is well defined and centralized in the parent componente, there is also a updateState
utility function to help updating the state only when necessary.
import { Random } from "meteor/random";
import { CalendarAutomatedReminderNotificationType } from "./CalendarAutomatedReminderNotificationType";
import { CalendarAutomatedReminderUnit } from "./CalendarAutomatedReminderUnit";
const DEFAULT_CALENDAR_AUTOMATED_REMINDERS = [
{
type: CalendarAutomatedReminderNotificationType.EMAIL.name,
value: 1,
unit: CalendarAutomatedReminderUnit.DAYS.name,
},
{
type: CalendarAutomatedReminderNotificationType.EMAIL.name,
value: 60,
unit: CalendarAutomatedReminderUnit.MINUTES.name,
},
];
const updateState = ({ state, automatedReminders }) => {
if (
JSON.stringify(state.get("automatedReminders")) ===
JSON.stringify(automatedReminders)
) {
return;
}
state.set("automatedReminders", automatedReminders);
Users.update(Meteor.userId(), {
$set: { "profile.automatedReminders": automatedReminders },
});
};
Template.calendarAutomatedReminders.onCreated(function () {
this.state = new ReactiveDict();
this.state.setDefault({
automatedReminders: Tracker.nonreactive(function () {
const user = Users.findOne(
{ _id: Meteor.userId() },
{ fields: { "profile.automatedReminders": 1 } }
);
const automatedReminders =
user && user.profile && user.profile.automatedReminders;
return (
automatedReminders ||
DEFAULT_CALENDAR_AUTOMATED_REMINDERS.map((item) => ({
_id: Random.id(),
...item,
}))
);
}),
});
});
Template.calendarAutomatedReminders.helpers({
automatedReminders: function () {
return Template.instance().state.get("automatedReminders");
},
});
Template.calendarAutomatedReminders.events({
'click [data-action="addAutomatedReminder"]': function (event, template) {
event.stopPropagation();
event.preventDefault();
updateState({
state: template.state,
automatedReminders: [
...template.state.get("automatedReminders"),
{ _id: Random.id(), ...DEFAULT_CALENDAR_AUTOMATED_REMINDERS[0] },
],
});
},
});
Template.automatedReminder.helpers({
types: function () {
return Object.values(CalendarAutomatedReminderNotificationType);
},
units: function () {
return Object.values(CalendarAutomatedReminderUnit);
},
selectedType: function () {
return Template.instance().data.type === this.name ? "selected" : "";
},
selectedUnit: function () {
return Template.instance().data.unit === this.name ? "selected" : "";
},
});
Template.automatedReminder.events({
'click [data-action="removeAutomatedReminder"]': function (event, template) {
event.stopPropagation();
event.preventDefault();
const parent = template.parentTemplate();
updateState({
state: parent.state,
automatedReminders: parent.state
.get("automatedReminders")
.filter(({ _id }) => _id !== template.data._id),
});
},
'change [data-action="trackAutomatedReminder"]': function (event, template) {
event.stopPropagation();
event.preventDefault();
const parent = template.parentTemplate();
const { name, value } = event.target;
updateState({
state: parent.state,
automatedReminders: parent.state
.get("automatedReminders")
.map((ar) =>
ar._id !== template.data._id
? ar
: { ...ar, [name]: name === "value" ? +value : value }
),
});
},
});