4

I'm working in an existing codebase that uses the React, Meteor, and react-meteor-data combo.

Everything has been going relatively fine up until I tried implementing a search feature using withTracker, React Select, and Meteor's subscription functionality.

import { CollectionAPI } from '../arbitrary_meteormongo_collection';

export const WriteableConnectionToCollection = withTracker(props => {
    let connection = Meteor.subscribe('COLLECTION_NAME.searchByName', SEARCH_TEXT_HERE); 
    let isLoading = connection.ready();

    return {
        ...props,
        isLoading: isLoading,
        collection: CollectionAPI.find().fetch()
    }
})(PRESENTATIONAL_COMPONENT);

I've googled around and saw that a common solution for getting data to Meteor.subscribe is to use things like URL parameters, though as I am working in an existing codebase, this change would also need to be implemented in various locations.

Another way I have found is to pass the input field's value to the parent component by keeping track of the input field state in the parent component's state, though this is clearly breaking the principal of separation of concerns:

Parent Component

export const ParentComponent = React.createClass({
    getInitialState() {
        return {
            inputFieldValue: undefined
        }
    },

    onChange(change) {
        this.setState(inputFieldValue);
    },

    render() {
        return (
            <Search 
                onChange={this.onChange}
                inputFieldValue={this.state.inputFieldValue}
            />
    }
}

withTracker HOC

import { CollectionAPI } from '../arbitrary_meteormongo_collection';

export const WriteableConnectionToCollection = withTracker(props => {
    let connection = Meteor.subscribe('COLLECTION_NAME.searchByName', this.props.inputFieldValue); 
    let isLoading = connection.ready();

    return {
        ...props,
        isLoading: isLoading,
        collection: CollectionAPI.find().fetch()
    }
});

InputField Component

import { WriteableConnectionToCollection } from './connections/writeableconnection.js';

const InputFieldComponent = React.createClass({

    render() {
        <InputField 
            onInputChange={this.props.onChange}
        />
    }
}

export default WritableConnectionToCollection(InputFieldComponent);

Is this the only way to do things with this particular package/framework combo or is there a simpler way that I'm just not seeing?

iPwnPancakes
  • 106
  • 9
  • 1
    My first attempt would be to use a ReactiveVar. But I haven't tried using them in `withTracker` yet. Have you tried that already? I think it should work, since all reactive data sources in withTracker should trigger a re-run of the function. – Christian Fritz Aug 15 '18 at 15:38
  • I tried using ReactiveVar, though I am a little confused about where the ReactiveVar should live. Since withTracker runs the provided function every time something changes, if I initialize a ReactiveVar inside of the withTracker function then it gets reinitialized back to its initial value (Undefined, null, ' ', etc.). If I pass ReactiveVar.set functionality down to the lower order component, then it changes, triggers withTracker, then gets reinitialized. At least as far as I can see. – iPwnPancakes Aug 15 '18 at 17:00
  • 2
    You should initialize it on load (i.e., directly in the file where the withTracker call is made, but outside of it's scope. All changes that happen to it afterwards should be those that you intent to cause changes. – Christian Fritz Aug 15 '18 at 18:04

1 Answers1

4

As Christian Fritz had mentioned in a comment under my original question, I can use ReactiveVar to be able to pass input in and out of my connection component:

export const WritableConnection = function (subscriptionName, collectionAPI) {
    /**
     *  ReactiveVar must be outside of withTracker. If the it was inside withTracker's scope, 
     *  anytime a user would use .set(ANY_VALUE), it would overwrite whatever was in it first,
     *  and then re-initialize.
     **/
    const input = new ReactiveVar(undefined);

    return withTracker(props => {
        const connection = Meteor.subscribe(subscriptionName, input.get());
        const isLoading = connection.ready();

        return {
            ...props,
            isLoading: isLoading,
            collection: collectionAPI.find().fetch(),
            setSearchText: (text) => input.set(text),
            getSearchText: () => input.get()
        }
    })
}
iPwnPancakes
  • 106
  • 9