[SOLVED] Meteor 1.3's createContainer, and Redux dispatch, event handlers, etc

I’m used to a pattern where the React container not only interfaces with Meteor data, but also connects to Redux and handles dispatching actions in event handlers, e.g.:

class ThingContainer extends Component {
  handleSomeEvent() {
    this.props.dispatch(doTheThing());
  }

  render() {
    return <Thing onSomeEvent={_ => this.handleSomeEvent()} />
  }
}

export default connect()(ThingContainer)

If we use the new createContainer, is this no longer possible? It seems I’d now have to have Thing in charge of dispatching actions. So in the createContainer call, I would somehow wire that up… I haven’t wrapped my head around it yet.

If anyone else has successfully done this, please share! The way things are now, it sort of breaks the container paradigm, since only containers are supposed to talk to Redux.

5 Likes

Well, I at least figured out how to wire in Redux and allow the Meteor container to access Redux state too:

const Container = createContainer(_ => {
  // Pretty much the same stuff you'd put in getMeteorData()
  console.log('Here is some Redux state:', chatUI);

  return {
    // dispatch is automatically passed by the connect() below
    // add any Meteor stuff. Redux is automatically added by connect()
  }
}, Thing);

export default connect(({ chatUI }) => ({ chatUI }))(Container)
3 Likes

Ok. Not sure why I got so easily thrown off by this! Really easy.

import Tests from '../lib/collections';

import { Meteor } from 'meteor/meteor';
import React, { Component } from 'react';
import { createContainer } from 'meteor/react-meteor-data';

class TestContainer extends Component {
  renderTests() {
    return this.props.tests.map(test => {
      return <div key={test._id}>{test.name}</div>
    });
  }

  render() {
    return <div>
      {this.renderTests.call(this)}
    </div>
  }
}

export default createContainer(_ => {
  return {
    tests: Tests.find().fetch(),
  }
}, TestContainer)

And in concert with Redux:

const MeteorContainer = createContainer(_ => {
  const handle = Meteor.subscribe('tests');
  const tests = Tests.find().fetch();

  // no need to return someValue, Redux passed it in and it's being passed along automatically
  return {
    dataReady: handle.ready(),
    tests,
  }
}, TestContainer);

export default connect(({ appstate }) => ({
  someValue: appstate.someValue,
}))(MeteorContainer)
4 Likes

Hi @ffxsam, thanks for the post!

Just one question here: apart from Redux, how would you manage dataReady with a simple container?
Because I still have the good old
> if (this.props.isLoading) { return ; }

else{//this.props.tests.map}

which forces my full component to re-render (everytime I update a limit for example) :slight_smile:

Thanks for your help

I’ve actually modified my structure since these posts. I now just use containers only for Redux & Meteor hookups, and I move my methods into whatever component is being wrapped by the container. Much cleaner looking.

dataReady can be handled any way you want, it really depends on the structure of your components and data. You might even have a need to split it up into two booleans (scheduleReady and membersReady for example), so you can have individual loading spinners.

1 Like

With this can we get the redux state synced with network state ? Is createContainer() updating the props and redux connect is updating redux state , which is a separate variable ? .

Or can we send a redux dispatch from the argument function , of createContainer ?

redux connect is passing props into the result of createContainer. So, for example, given this:

const MeteorContainer = createContainer(_ => {
  return {
    availableTracks: Tracks.find().fetch(),
    reelDraft: Reels.findOne({ draft: true }),
  }
}, ReelDraft);

export default connect(({ reelBuilder }) => ({
  editingTrackId: reelBuilder.get('editingTrackId'),
  editingTrackUrl: reelBuilder.get('editingTrackUrl'),
  reelTracks: reelBuilder.get('tracks').toJS(),
  waveEditorOpen: reelBuilder.get('waveEditorOpen'),
}))(MeteorContainer)

Now ReelDraft has all props from both Meteor and Redux.

Thank you.I have the following basic doubt.may be I am missing some
fundamental aspects.Does such a setup (updating props from createContainer
)means we are storing the application state in two places,in server and
on Redux state variable?if my application architecture is in such a way
that all state changes are triggered using redux actions, does updation of
props from createContainer() is having a separate path of data flow
from updation of app state as a result of redux actions .Does this will
lead to inconsistency?To unify both the state from network and state from
redux can we channel the network data through redux .?

You might be overthinking it. :slight_smile: createContainer is doing the exact same thing the getMeteorData mixin used to do, but now you’re accessing Meteor data via this.props.someData instead of this.data.someData. Redux is passing in props via connect, but it’s completely unrelated to createContainer. In short:

• Both createContainer and connect are higher-order functions that wrap a component and pass props into it
createContainer handles reactive variables and data sources
connect handles Redux state

There’s no redundancy. You can even have Meteor’s createContainer reference Redux state which could come in handy. Let’s say you have a web app where you’re an admin, and you can view the site as other users in order to see their photos. You have a dropdown menu that lets you choose a user (which is handled by Redux). You could set it up like this:

const MeteorContainer = createContainer(({ actingUserId }) => {
  const handle = Meteor.subscribe('photos', actingUserId || Meteor.userId());

  return {
    isDataReady: handle.ready(),
    photos: Photos.find({}, { sort: { createdAt: -1 } }).fetch(),
  }
}, MyComponent);

export default connect(({ actingUserId }) => ({ actingUserId }))(MeteorContainer)

So connect takes the Redux state variable actingUserId and passes it into MeteorContainer, which then uses that user ID to subscribe to photos only owned by that user.

1 Like

This is exactly what we do, with one small difference. We wrap the Redux container with the Meteor container. This is because we have a lot of temporary, fast changing UI state stored in Redux (like user input). We don’t want the Meteor container (which has potentially “complex” minimongo queries, etc) to be re-run every time the user types or triggers some other redux action.

Overall, we have found this hierarchy to be more performant than wrapping Meteor with Redux.

1 Like

One observation . I tried to dispatch a redux action from createContainer() . It was going in an infinite loop .
Have you experienced similar behaviour .

Whoa, friend! You definitely don’t want to be dispatching actions from within createContainer. Dispatches are probably best placed in methods that are called in reaction to user events, e.g.:

class MyThing extends React.Component {
  doThing() {
    this.props.dispatch(someActionCreator(arg1, arg2));
  }

  render() {
    return <div onClick={this.doThing.bind(this)}>Click here</div>
  }
}
1 Like

Sorry to dig this out again but why is that so bad? I mean if I want to have dumb components I try to pass in as much as I can via props. Even something like handleToggle() which just dispatches a toggle boolean action.
Other than that what if add some logic after dispatching. If so I want that even more in my container.

Could you explain why I shouldn’t dispatch actions in my container? Maybe my thinking is wrong with “try to put all my logic in my container and be good”

If this is the case then we should be careful when naming the props because they might collide and MeteorContainer will always take precedence, correct?

That makes sense! Glad I dug through this thread, by the way, I have 2 questions I hope you can answer:

  1. Do you persist your Store somewhere, if so where do you place it: LocalStorage, Mongo, Session, etc? I’m having a problem where I refresh the page my App state is all gone because the Store is back to empty.
  2. Do you keep the records you fetch from Mongo to your Store? Or you just strictly use Store separate for the UI State as suggested by most users?

Sorry for digging this up. I am trying to subscribe to a publication based on a filter set in redux. However, I can not seem to figure out how to access the redux props in my createContainer. Any explanation why this does not work?


class Home extends Component {
	render () {
		return (
			<div>
				SomeText
			</div>
		)
	}
}

const mapStateToProps = state => {
	return {
		filter: state.filter
	}
}

let connectedHomeComponent = connect(mapStateToProps,)(Home)

export default connectedHomeContainer = createContainer(_ => {
	console.log(_);
	let eventsSub = Meteor.subscribe('getEventsByFilter');
	let loading = !eventsSub.ready();
  	let events = null;
  	loading ? (events = null) : (events = Events.find().fetch());
  	let eventsExists = !loading && !!events;
	return {
    	loading,
    	events,
    	eventsExists,
	}
}, connectedHomeComponent)