Complex interaction between React and Meteor : the autocomplete case


#1

Hi everybody,

I have been surfing the web for a while now, looking for the best pattern for my use case. It might interest people that suffer, like me, from the #TodoAppFatigue and who like complex example.

Here it is : I am building a “CityAutocomplete” module. That’s it, an autocomplete that complete the city name while you type it.

Obviously, there are a lot of cities in France, because we like our villages and our countryside. And to make things easier, I don’t want to be dependant on any existing API (critical feature).

Here is the structure of my MongoDb collection containing all my city names :

...
 {_id:XXX, citiesIdx:"mar", cities:["marseille", "marseillan","marly-gomont",...]},
 {_id:XXX, citiesIdx:"mon", cities:["montpellier", "montélimar",...]},
...

This way, I can start autocompleting only after 3 characters without loading the whole database. I only load the city names that match the first 3 letters of the input, using my citiesIdx index.
Okay, now things get complicated : since I subscribe only to the relevant data, I must update my subscription depending on the user input.

  • I cannot pass the citiesIdx as a prop of the component, since it depends on the user input
  • I cannot simply use createContainer, since I must update the subscription based on state, not on props

I have working solutions, but I’d like to find a satisfying pattern. What’s your opinion on this ?

Here is my current sample code, using only one component:

 import AutoComplete from 'material-ui/AutoComplete'
 import Cities from '../../../../collections/Cities'

import React, { Component, PropTypes } from 'react'

export class CityAutocomplete extends Component{
  constructor(){
    super()
    this.state = {currentSub:null, cities:[]}
    this.updateCities = this.updateCities.bind(this)
    this.updateSubscription = this.updateSubscription.bind(this)
  }
  updateSubscription(cityName){
    const {currentSub} = this.state
    const { updateCities } = this
    // remove existing subscription
    if (currentSub && currentSub.stop) currentSub.stop()
    // update the subscription
    this.setState({
      currentSub: Meteor.subscribe('cities.list', cityName.toLowerCase(),{
        onReady(){
          updateCities()
        }
      })
    })
  }
  updateCities(){
    // update the cities when subscription is ready
    this.setState({
      cities: Cities.findOne().cities || []
    })
  }
  render(){
    const { cities } = this.state
    return(
      <div>
        <AutoComplete
          dataSource={cities}
          onUpdateInput={this.updateSubscription}
        />
      </div>
    )
  }
}
export default CityAutocomplete

#2

I use a session variable for these situations. The input sets the variable, and I (using komposer in my case) then just grad the session variable from inside my komposer container and pass it to the relevant subscription. Redux fiends will scoff, but it works fine for most basic situations.