Svelte: $m reactive not working

All,
I’m trying to build a simple search on text list.
The filtered resultset gives write number of rows but incorrect data rows.
Any help is greatly appreciated. Thanks

Sample code:

import Program from './Program.svelte';
import { ProgramsCollection } from '../api/ProgramsCollection';
	let programs = [];
	let filter = {};
	let search = '';
	$m: programs = ProgramsCollection.find(filter).fetch();

	const handleSubmit = () => {
		if (search != '') {
			filter = { title: new RegExp(search, 'i') };
		} else {
			filter = {};
		}
};
<div class="panel">
		<input class="program-title" type="text" name="search" placeholder="Type to search programs" bind:value={search} />
		<button on:click={() => handleSubmit()}>Filter Now </button>
</div>

{#each programs as program, i}
	<Program {program} />
{/each}

I think you need to subscribe to programs before you set up $m: programs =

$m: Meteor.subscribe(/* name of your programs publication */)

Also, as an aside, you shouldn’t need let programs = []

As Jam said you need to subscribe first.
To do a text search you need a text index in your programs collection.

As for usability. You could make the “Search Now” button inactive until the Search input has any value.

Can you give us an example of your output? By “right amount but incorrect” you mean that there are eg. 4 programs but the search string is not applied correctly?

Of course you need to subscribe, but I assume you either have a global subscription or the autopublish package installed. If the collection is big, you should reimplement the current solution in a more efficient way, but that does not answer your question now.

You don’t need a text index as the filtering is happening locally at the moment. And as for the database, the regex search works fine, but is generally slow (again, it depends on the collection size).

You can try to display the filter value on the page. I remember having issues that the regex had been considered the same although the string inside has changed. I don’t know what was the exact situation though. You can also doublecheck that the programs var does get recalculated:

$m: {
   programs = ProgramsCollection.find(filter).fetch();
   console.log(programs, filter)
}

Please see my code below.
When I run the programs and search for ‘Show’. it returns Program1 and Program2.
Any help is much appreciated.

Programs.svelte

<script>
	import Program from './Program.svelte';
	import { ProgramsCollection } from '../api/ProgramsCollection';

	let text = '';
	let filter = {};
	let isLoading = true;

	$m: {
		const handler = Meteor.subscribe('programs', filter);
		isLoading = !handler.ready();
	}

	let programs;
	$m: programs = ProgramsCollection.find(filter).fetch();

	const handleSubmit = (e) => {
		console.log('text:' + text);
		if (!text) {
			filter = {};
		} else {
			filter = { title: new RegExp(text, 'i') };
		}
	};
</script>

<div class="container">
	<form class="task-form" on:submit|preventDefault={handleSubmit}>
		<input type="text" name="text" placeholder="Type to search" bind:value={text} />
		<button type="submit">Search</button>
	</form>
	{#if isLoading}
		<div class="loading">loading...</div>
	{/if}
	{#each programs as program}
		<Program {program} />
	{/each}
</div>

<style>
	.panel {
		display: flex;
		padding: 10px;
		/* margin: 5px; */
	}
</style>

ProgramsCollections.js

import { Mongo } from 'meteor/mongo';
export const ProgramsCollection = new Mongo.Collection('programs');

ProgramsPublications.js

import { Meteor } from 'meteor/meteor';
import { ProgramsCollection } from '../api/ProgramsCollection';

Meteor.publish('programs', function publishPrograms(searchValue) {
	console.log('searchValue:' + searchValue);
	if (searchValue === '') {
		return ProgramsCollection.find({});
	} else {
		return ProgramsCollection.find({ title: new RegExp(searchValue, 'i') });
	}
});

Program.svelte

<script>
	export let program;

	const { title, _id } = program;
</script>

<div class="program">
	<div class="program-title">{title}</div>
</div>

server/main.js

import { Meteor } from 'meteor/meteor';
import { ProgramsCollection } from '/imports/api/ProgramsCollection';
import '/imports/api/ProgramsPublications';

const insertProgram = (program) =>
	ProgramsCollection.insert({
		title: program.title,
		createdAt: new Date(),
	});

Meteor.startup(() => {
	if (ProgramsCollection.find().count() == 0) {
		[
			{
				title: 'Program 1',
			},
			{
				title: 'Program 2',	
			},
			{
				title: 'Program 3',	
			},
			{
				title: 'Show 1',
			},
			{
				title: 'Show 2',
			},
		].forEach(insertProgram);
	}
	ProgramsCollection.createIndex({ title: 1 });
});

Try this

  1. Delete let programs. It’s not needed.
  2. In your Meteor.subscribe use text instead of filter. The way you structured your publication function, it looks like it expects the searchValue to be a simple string not an object.

I made the changes as suggested. Now I don’t see any results.
What do I put in for filter below? I only want to get data collection when I click Submit.

$m: programs = ProgramsCollection.find(filter).fetch();

What do you see in the console if you add this before and after you click submit?

$: {
  console.log('filter', filter)
  console.log('programs', programs)
}

I have changed my code as follows

let text = '';
let searchText ='';
let filter = {};
let programs;
$m: {
		const handler = Meteor.subscribe('programs', searchText);
		isLoading = !handler.ready();
		programs = ProgramsCollection.find(filter).fetch();
		console.log('filter', filter);
		console.log('programs', programs);
	}

const handleSubmit = (e) => {
		console.log('text', text);
                searchText = text;
		if (!text) {
			filter = {};
		} else {
			filter = { title: new RegExp(text, 'i') };
		}
	};

When I make search ‘Show’ it returns 2 program records. 3 calls r made. But data is correct.
Console.logs
filter - {title:/Show/i}
programs -
filter - {title:/Show/i}
programs - [2] records
filter - {title:/Show/i}
programs - [2] records

again if search for ‘Program’, i get correct data but calls are 3.

I have changed the code to fire only when I click button. The program is making multiple calls.
I’m just trying to write very basic search functionality here.

Ok then I suggest you change your publication function to accept a filter object as the argument and change your Meteor.subscribe to pass in the filter object as you had it before.

I couldn’t get it to work with new Regex but I was able to get it to work by doing this:

Programs.svelte

<script>
  import Program from './Program.svelte';
  import { ProgramsCollection } from '../api/ProgramsCollection';

  let text = '';
  let filter = {};
  let isLoading = true;

  $m: {
    const handler = Meteor.subscribe('programs', filter);
    isLoading = !handler.ready();
  }

  $m: programs = ProgramsCollection.find(filter).fetch();

  const handleSubmit = (e) => {
    console.log('text:' + text);
    if (!text) {
      filter = {};
    } else {
      filter = { title: {$regex: text, $options: 'i'} }
    }
  };
</script>

<div class="container">
  <form class="task-form" on:submit|preventDefault={handleSubmit}>
    <input type="text" name="text" placeholder="Type to search" bind:value={text} />
    <button type="submit">Search</button>
  </form>
  {#if isLoading}
    <div class="loading">loading...</div>
  {/if}
  {#each programs as program}
    <Program {program} />
  {/each}
</div>

<style>
  .panel {
    display: flex;
    padding: 10px;
    /* margin: 5px; */
  }
</style>

ProgramsPublications.js

import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { ProgramsCollection } from '../api/ProgramsCollection';

Meteor.publish('programs', function (filter={}) {
  // you'll need to check the arguments assuming you don't have the insecure package installed for prototyping only
  // this is a quick and dirty check, you'll want to check them more robustly
  check(filter, Object);

  return ProgramsCollection.find(filter);
});

You might want to look into https://www.mongodb.com/docs/manual/core/index-case-insensitive/ if you want better performance in the future.

Thanks so much. It’s giving the expected results. See only one call to subscription call…
But I see 3 log messages when I console.log(‘programs’, programs)

  1. empty array
  2. 3 objects
  3. 3 objects.

I see right count of rows in collection and right data but UI does not refresh to show it sometimes. If I change the search criteria where number of rows change then UI refreshes.