Passing data from a React.Component to its Container


#1

Hi there,

I’m in need of a simple full-text search feature. Unfortunately I fail to find a way, due to the need of passing the search input upwards. Any help would be much appreciated.

Page:

export default class Welcome extends React.Component {
  constructor (props) {
    super(props);
    this.handleSearchInput = this.handleSearchInput.bind(this);
  }
  handleSearchInput(event) {
    console.log("event.target.value", event.target.value)
    // How do I get event.target.value to the Container, to pass it further to the publications for server side full-text search?
  }
  render() {
    return (
      <Content {...this.props}>
            <IonItem input>
              <input ref="searchBox" type="text" placeholder="Search..." onChange={this.handleSearchInput} />
            </IonItem>
      </Content>
    );

Container

export default createContainer(() => {  
  // How do I get access to the searchTerm from the wrapped component here?
  const sitesHandle = SitesPubs.getSites.subscribe(searchTerm);
  const sitesDoc = Sites.find({}).fetch();
  const sitesDocExists = !!sitesDoc;
  return {
    isLoading: !sitesHandle.ready(),
    sites: sitesDocExists ? sitesDoc : null,
  };
}, Page);

Publication (full text search needs to be server side)

export const getSites = new ValidatedPublication({
  name: 'sites.getSites',
  validate: null,
  run(searchTerm) {
  	if (this.userId) {
      return Sites.find({owner: this.userId});
    }
    if (searchTerm) {
      return Sites.find(
        // In the end, I need it server side, here. Because Minimongo does not support full-text search.
        { $text: { $search: searchTerm } },
        {
          fields: Object.assign({}, Sites.publicFields, scoreField),
          sort: scoreField,
        });
    }
    return Sites.find({}, {
      fields: Sites.publicFields,
    });
  },
});

scoreField = {
  score: { $meta: "textScore" },
};

#2

Hi @Slind

Quick answer. You have at least 2 options:

1- use Redux or some other tool (mobX, or even a reactive dictionary) to keep track of your state in a global variable so that it can be accessed from your containers as well as from your components. In case you haven’t tried Redux yet I suggest you to check https://egghead.io/courses/getting-started-with-redux. It takes some time to get started but is more than worth it :slight_smile:

2- a second approach (simpler for this task but less general) would be to set the search input value that you get inside handleSearchInput as a query parameter in your route:

// Example using FlowRouter
handleSearchInput(event) {
   console.log("event.target.value", event.target.value)
   // How do I get event.target.value to the Container, to pass it further to the publications for server side full-text search?
   FlowRouter.setQueryParams({ query: event.target.value });
   // The last line of code will modify the url by adding '?query=<the-value-enetered-by-the-use>'
}

Then, inside your router you can read the query parameter and inject it back into page container:

// the route will have the following shape: http://localhost:3000/pageName?query=<the-value-enetered-by-the-use>
FlowRouter.route('/pageName', {
  name: 'pageName',
  action(params, queryParams) {
    mount(AppContainer, {
      content: () => <PageContainer
        query={queryParams.query}
      />,
    });
  },
});

Then back into page container you will expect ‘query’ as a parameter:

export default createContainer((**{ query }**) => {  
  // Do whatever you want with the query :slight_smile: 

  // How do I get access to the searchTerm from the wrapped component here?
  const sitesHandle = SitesPubs.getSites.subscribe(searchTerm);
  const sitesDoc = Sites.find({}).fetch();
  const sitesDocExists = !!sitesDoc;
  return {
    isLoading: !sitesHandle.ready(),
    sites: sitesDocExists ? sitesDoc : null,
  };
}, Page);

In case you are not using FlowRouter you can still use the same idea. In any case, I would suggest you to learn Redux and also play with the second approach; it might be handy in some other context!


#3

Hi @orcprogramming
those look like good approaches. Redux is something I want to get into at some point. For this use case it is over powered I think, especially since I would probably need to change the entire project structure.

I really like the query param way for this case. Do you know if this works with the React Router, too?


#4

I haven’t used React Router yet but for sure you’ll be able to use the same approach :slight_smile:


#5

no need for redux.

three more simpler options:

  • wrap your container in a React.Component that has a state which keeps your query
  • wrap your container in recomposewithState("query", "setQuery") (see https://github.com/acdlite/recompose) This is basically the same as option1, needs a library though, but will look much simpler (and recompose is useful for other tasks as well)
  • Use Session inside your container and update the Session value in the callback. This is pretty similar to what kadira’s mantra did for local state and also not so much different from keeping your state in redux.

#6

Doing this with the react router doesn’t seem to work. Because while it updates the params, it does not supply them to the props in the container until a full page refresh.


#7

Do you have a short example for option 1 or 2? I’m having trouble understanding on how to implement it.


#8

Potentially may help below sample may help (MIT license)
Kurounin
(thanks Kurounin)

Had enhanced the sample for some meteor-react-paging versus full plain deep list performance test (added fields mongoimport > 4000 Glossary items. Works fast on local or lan remote PC. Sample has regex ‘meta search’ feature which works well and fast too).


#9

I feel weird about this topic. Am I missing something?


class Container ...  {
   passInfo = (info) => {
        Meteor.call('doSmthWithInfo', {info});
   }}
  render(){
        return <FilthyChild passInfo={this.passInfo}/>

  }

const FilthyChild = ({passInfo}) => (
        <input value="Container component had sex with an apple" onBlur={(e) => {passInfo(e.target.value)}} />
)