Extend meteor todos example with search


#1

Hi, i am currently playing around with the complex meteor todos app with react https://github.com/meteor/todos/tree/react and trying to learn meteor. What i am trying to achieve now is to add a function to filter the todos based on name (.e.g. a search function)

I went through the whole code of the app and i am totally at loss where to start and would appreciate some help. The todos are passed down as a prop from the ListPageContainer to the listpage where they are displayed. I now tried to add an input field to the listpageheader which calls a function upon change.

Normally i would just add a property as a callback function which would go back to the listpagecontainer and execute a find there with the search param. But in container classes i cant execute functions. I would appreciate any help if you could point me into the right direction of what to do. Not asking for code sample or anything, just a rough idea so i can find out the rest myself.

Thanks in advance :slight_smile:


#2

Here’s a really quick example showing how you can add a filter box at the top of a list. Note - I just threw this together quickly; it will work, but it can definitely be made much more robust. When done it will look/work like:

Steps:

  1. Create a new file: /imports/ui/components/Search.jsx:
import React from 'react';

const Search = ({ filter }) => (
  <div className="search">
    <input
      placeholder="Filter ..."
      onChange={(event) => {
        filter.set(event.target.value);
      }}
    />
  </div>
);

Search.propTypes = {
  filter: React.PropTypes.object,
};

export default Search;
  1. Create a new file: /imports/ui/components/Search.less:
.search {
  margin: 10px 5px 0 0;
  display: inline;
}
  1. Adjust /client/main.less to reference the new styles by adding the following to the bottom of the file:
@import "{}/imports/ui/components/Search.less";
  1. Adjust the /imports/api/lists/lists.js collection helpers to allow search filtering on associated todos:
...
Lists.helpers({
  ...
  todos(filter) {
    const selector = {
      listId: this._id,
    };
    if (filter) {
      selector.text = {
        $regex: new RegExp(`.*${filter}.*`, 'i'),
      };
    }
    return Todos.find(selector, { sort: { createdAt: -1 } });
  },
});
  1. Adjust the /imports/ui/containers/ListPageContainer.jsx to use a ReactiveVar to keep track of the current search filter, and make sure the current search filter is used to filter todos. Also make sure the filter ReactiveVar is passed into the ListPage component:
...
const filter = new ReactiveVar(null);

export default ListPageContainer = createContainer(({ params: { id } }) => {
  ...
  return {
    loading,
    list,
    listExists,
    todos: listExists ? list.todos(filter.get()).fetch() : [],
    filter,
  };
}, ListPage);
  1. Adjust /imports/ui/pages/ListPage.jsx to accept the filter prop and make sure it gets passed into the ListHeader:
...
export default class ListPage extends React.Component {
  ...
  render() {
    ...
    return (
      <div className="page lists-show">
        <ListHeader list={list} filter={this.props.filter} />
        ...
      </div>
    );
  }
}

ListPage.propTypes = {
  ...
  filter: React.PropTypes.object,
};
  1. Adjust /imports/ui/components/ListHeader.jsx to reference the new Search component and specify that a filter (search) property can be passed in:
...
import Search from './Search.jsx';
...
  renderDefaultHeader() {
    ...
          <div className="options-web">
            <Search filter={this.props.filter} />
              ...
          </div>
    ...
  }
...
ListHeader.propTypes = {
  ...
  filter: React.PropTypes.object,
};
...

#3

Wow! Thanks for that response. This is so much more detailed and helpful than i ever expected :grinning: This is a great help for me i’ll go and play around with that right now!


#4

It works! This was a real eye opener for me before i’ll continue playing around it seems i really need to have a closer look at the docs. It make so much sense now! I almost tried the same, but instead of the reactiveVar i tried getting a function in there. But this is way cleaner and cooler :slight_smile: