Mantra composer: trigger subscription change when LocalState is modified?


#1

I’m using the Mantra architecture, and I’m relatively new to it. I’m trying to develop a simple filtering application that modifies the page content (let’s call them Posts for now) as you change the filters. For example, one of my filters is a checkbox group for Categories. Clicking on one of the checkboxes will result in a view that only shows Posts tagged with that category. I have the logic worked out; my problem is reactively updating the Posts shown in a sane (and scalable) way.

I have a dumb wrapper component around the filters and the Post list, as follows:

<Wrapper>
    <Filter1 />
    <Filter2 />
    <Filter3 />
    [...]
    <PostList />
</Wrapper>

Each filter has its own container and component, for example, this is a “cost” slider that waits for the Posts subscription to complete (in the PostList composer, which sets the LocalState variable) before it sets the slider’s high and low values in the component:

export const composer = ({context}, onData) => {
	const {Meteor, Collections, LocalState} = context();

	if (LocalState.get('POSTS_READY')) {
		const max = Math.ceil(Collections.Posts.findOne({},{sort: {cost: -1}}).cost);
		const min = Math.floor(Collections.Posts.findOne({},{sort: {cost: 1}}).cost);
		onData(null, {max, min});
	}
};

export const depsMapper = (context, actions) => ({
	set_filters: actions.posts.set_filters,
	get_filters: actions.posts.get_filters,
	context: () => context
});

export default composeAll(
	composeWithTracker(composer),
	useDeps(depsMapper)
)(CostSlider);

Here’s the CostSlider component

<div className="filter-block">
	<br/>
	<h2 className="text-white">Cost: <span className="cost-amt text-white inline"></span></h2>
	<div className="filter-content">
		<Slider range allowCross={false} value={this.state.cost_range} min={this.state.min} max={this.state.max} onChange={_.throttle(this.costFilter, 1000)} />
	</div>
</div>

the costFilter function sets the local state (this.setState) and the global state (the LocalState Reactive-Dict through the set_filter action), i.e.

costFilter = (cost_range) => {
		// local state management
		this.setState({cost_range})
		this.setSpan(cost_range); // sets the number in the UI, i.e. Cost:$120 - $150
		const {set_cost_range, get_filters} = this.props;
		// global state management
		set_cost_range({cost_range});
	}

Calling set_cost_range above calls the following function contents in my actions file:

 LocalState.set('COST_FIELD': cost_range);

This works properly, I’ve done my console.log() s :slight_smile:

Now, in my PostList container I have the following code:

export const composer = ({context}, onData) => {
	const {Meteor, Collections, LocalState} = context();
	if (Meteor.subscribe('posts.list', {
           cost: LocalState.get('COST_FIELD'),
           [...] // other arguments to query the collection on the publication side
         }).ready() ) {
		LocalState.set('POSTS_READY', true); 
		posts = Collections.Posts.find().fetch();
		onData(null, {posts});
		}
	}
};

export default composeAll(
	composeWithTracker(composer),
	useDeps()
)(PostList);

I’ve tried using Tracker.autorun() here, but the subscription does not auto-update as I want it to. I’ve also tried using the Search-Source package in a hacky way(by stringifying and objectifying the filters on the client and server, respectively), but that requires subscribing to all the posts and I don’t think that’s scalable for my application.

Am I approaching this problem correctly? Should I try something different? Should I be passing data through the parallel components differently? Any advice is appreciated :slight_smile:

Thanks!