meteor / react-packages

Meteor packages for a great React developer experience
http://guide.meteor.com/react.html
Other
571 stars 157 forks source link

Document best practises for handling subscriptions/data across components #19

Closed dburles closed 3 years ago

dburles commented 9 years ago

It would be nice to have some information on this side of things. I know @tmeasday has been digging into it a little bit.

AdamBrodzinski commented 9 years ago

@dburles

For subscriptions and data fetching i've been using a data container which puts all of the subscription and data fetching logic in one component and then renders a child and pushes data downward using props. This makes any other components re-usable and very easy to test since they're just expecting props.

Here's an example of the FeedListContainer and FeedList in a 'Social Wall' example app. (note, this is using the older MeteorReact mixin)

I'm also experimenting on a more 'Relay' type of system that will allow children to specify what fields they need and the parent just needs to subscribe (above the parent picks all fields for children).

As far as sharing data across components, Flux doesn't really fit in well with Meteor since there are so many overlapping parts with core. Meteor has a lot more in common with Relay than Flux.

So far i've mostly been using @petehunt 's method of using domains. This takes some of the logic out of the component and allows for multiple components to consume it easily. This example is using his mini-mongo cache outside of Meteor but the principles are still the same: https://github.com/petehunt/minimongo-cache#domains-example

stubailo commented 9 years ago

I think for now we are focusing on delivering the right building blocks for a good React solution, so we probably won't provide too much guidance on best practices in the near future, but it's definitely something we plan to do in the longer term. I love all of the discussion that's happening around this already, I'm excited to see what people come up with!

dburles commented 9 years ago

Thanks for sharing your ideas @AdamBrodzinski the container approach is one I had considered, however I wasn't able to decide on how distinct parent components might share data coming from the same collections (likely from different publications) in a correct one-way-data-flow style.

@stubailo maybe best practises isn't the exact term I was looking for but I think it's important to have some information as working with Meteor seems to make it quite easy to step outside of the one way data flow approach that React is all about.

stubailo commented 9 years ago

If you think about it, Meteor is all about one way data flow - you render all of your UI based on a global store called Minimongo; and you update it through Methods, which are much like Flux actions. We don't do too much right now to enforce this flow, but if you would like to you can enforce that convention in your own app.

AdamBrodzinski commented 9 years ago

Yea I was just going to say that :smiley:

The way i'm sharing the data coming from the same collection is via MiniMongo. Both parents can query MiniMongo for their data, and both can subscribe to get the data they each need. If they overlap, they will be merged in MiniMongo as usual.

Both parents could call PostsDomain.update(....) which wouldn't be locked in each component.

Would the Posts.find(...) query from a parent be considered two way data flow? I'm not sure if the fetching counts?

This only shows one parent component but you could image two:

img

stubailo commented 9 years ago

Have you seen this blog post? There is a similar diagram there: http://info.meteor.com/blog/optimistic-ui-with-meteor-latency-compensation

AdamBrodzinski commented 9 years ago

@stubailo I have! Great article by the way :+1:

ccorcos commented 9 years ago

@AdamBrodzinski, How do you traverse down the component tree like Relay does to determine what to subscribe to? I like this approach but I haven't figured out how to do that...

stubailo commented 9 years ago

What's wrong with doing subscriptions from inside components? Then the subscription will automatically follow the component's lifecycle.

It's a very similar idea to Relay, but instead of the subscriptions bubbling up the component tree they just go directly to the subscription manager code, which actually does deduplicate them.

AdamBrodzinski commented 9 years ago

@ccorcos my pseudo solution so far was to just have each component register their keys after declaring the class. Traversing down the tree sounds too complicated for the issue it solves. The fields would have to be in statics in order to retrieve them before it's instantiated. Then you could do something like this:

FeedItemHeader = React.createClass({
  fieldsNeeded: {
    feed: {
      posts: { userName: 1, createdAt: 1 }
    }
  },

  render() {
    return (
      <div className='name-date'>
        <div className="name">{this.props.userName}</div>
        <div className="date">{this.props.createdAt}</div>
      </div>
    );
  }
});

// register fields needed before render
MeteorRelay.registerFields(FeedItemHeader);

@stubailo I think the topmost parent component would still subscribe to the subscription but wouldn't worry about what fields all of the children need. IMHO Meteor overlaps a lot with Relay, like you mentioned.

I think the idea is to prevent underfetching/overfetching of fields in a large project. FB talked about this being a problem when people would change the components and an implicit field was taken away causing the UI to not render a certain area (because the parent didn't add the field to subscribe to). By having the child specify what fields it needs, you'll always have exactly what it needs.

In this example the fields are determined by the parent. This works but if you have 50 children it could get a bit time consuming to safely remove a field.

My thought for a MeteorRelay experiment would be to create a thin wrapper that uses subs-manager to cache and a few methods like MeteorRelay.registerFields(FeedItemHeader); to get a child's fields and something like MeteorRelay.subscribeTo("feed");

I haven't thought too hard about this yet, but it might be helpful for large projects and too much overhead for small projects (I think something like the example link above is the sweet spot for most?).

stubailo commented 9 years ago

@AdamBrodzinski I see, it might just be about avoiding the overhead of having lots and lots of subscriptions, since actually Meteor does correctly deduplicate lots of small subscriptions but it might not be super efficient.

I think having a generic publication that just takes a huge query constructed from many children might be a good idea, but when you optimize too much to avoid overfetching you actually lose some of the benefits of caching data on the client in the first place. If you overfetch a little bit, then you can avoid having to load more data when the user switches views.

If for every single page of the app you only load the exact data that the user is looking at, then in some ways you fall back to good old AJAX, where you need a new HTTP request for every page; you still have some speed improvements over loading a server-side-rendered page, but it won't be instant.

We have some ideas for how to make subscriptions more relay-like, but we haven't gotten to implementing it (gotta iron out the basics first). Really excited to see what you guys end up with.

ccorcos commented 9 years ago

Its hard for me to explain, but if you're subscribing and and using data mixins within the components, then you're entangling the control flow of you application with the view/render cycle. This gets very hard to reason about.

Try doing this with mixins. You might find that this is a huge pain:

I like using react as a pure render engine and a view-model. It takes a object -- the global state of the application -- every piece of information that matters to the state of the application -- and renders it. The application is thus pure and immutable. This is so much easier to reason about and debug. You can take the state of an application and copy paste it to another computer, I'll get the exact same results! Currently this won't work due to login tokens and everything being integrated into minimongo, but other than that, we can do this. Using data mixins, thing are changing under the hood which inevitably get more and more tangled.

Once you build all your react components based on some state structure, then you can focus all your attention on how the state changes and the control flow of your application. This is really nice because you can create a controller object with a ton of methods that are actions that change the state and maybe trigger a new render. These functions have constrained side-effects -- changing state and rendering. But other than that, they are pure.

This is a pattern more than anything and its subjective. Its hard for me to really demonstrate my point. But I've been down the road of data-mixins. It was awesome at first. But then it started to suck.

Also, check this out if you haven't: https://forums.meteor.com/t/react-without-react-mixins/6013/8

AdamBrodzinski commented 9 years ago

@stubailo

If you overfetch a little bit, then you can avoid having to load more data when the user switches views.

Yea I agree, you should prefetch if you can. I think in the ideal case you're not sending down fields that will never be used on any page. I guess there's always a fine balance :smiley:

We have some ideas for how to make subscriptions more relay-like, but we haven't gotten to implementing it (gotta iron out the basics first).

Super excited to hear that it's in the works!

One thing i'm going to change in the example link above is to make two separate subscriptions, one for posts and another for postComments.... would that be the best way?

I initially tried to send down the posts and comments at the same time, but doing so broke the reactivity. Currently it's sending down two cursors but doesn't seem very efficient. So currently it sends down 5 posts, gets the ids for the posts on the client and re-subscribes again with the new postIds to get the comments.

arunoda commented 9 years ago

We re-wrote Kadira UI recently with components (very similar to React). In there we come up with two types of components.

1) App Components Components Specific to the app 2) Reusable Components

App Components basically interact with Router and do subscriptions. But reusable components expects data from the input when we create them.

We pass data from top components to the components in the bottom. App Components may interacts with the Router and do data managements. But not the re-usable components.

We use something called State Functions to pass data to child components. We can use it here with react too.

I am playing a bit with these and let's see what can come up with.

arunoda commented 9 years ago

@AdamBrodzinski I wrote some serious app with React and I think I follow (without knowing) your diagram. But I subscribed directly from the Data fetching component. In there, I modify the data as needed for child components and send them.

Currently, all my actions are links. So, child components get a URL and they show it as a link. So, URL is manage the actions for me now.

I have to some other actions to be added, for them I'm going to pass a prop of the data fetching component to the individual component. With that, most of the props get data via props and never touch the state. I thought there may be some performance issues at it first. But, it was okay and didn't get any issues.

rclai commented 9 years ago

I think this discussion on React's childContext might also be relevant is accessing data.

AdamBrodzinski commented 9 years ago

I just released an example on how to use any flux architecture with React and Meteor. This replaces the need for the ReactData mixin and instead relies on Tracker to trigger collection update actions to refresh the UI. I don't think it's inherently better than the mixin for re-rendering but it certainly adds a solid architecture that is well used at this point.

I've been using flux in a React Native app and it has been really nice to work with. Once you know the flow it's very easy to track down state related bugs. It also scales well with many components/actions.

https://github.com/AdamBrodzinski/meteor-flux-leaderboard https://forums.meteor.com/t/flux-example-app-with-react-meteor/7416 (This example is using Alt, one of the many flux libraries)

@arunoda great to hear! I've been doing this as well for several weeks and it's been working out well.

ccorcos commented 9 years ago

Nice example Adam. I haven't worked with Flux very much but it looks like there's a lot of unnecessary fluff/boilerplate. It seems the me, that the main benefit of using event listeners is just to separate your view from your data. I think that's probably a good idea, especially when it comes to starting, stopping, and caching subscriptions.

How are things with React Native? Which DDP implementation are you using? Are you able to do hot code pushes to the app from meteor?

On Jul 23, 2015, at 21:17, Adam Brodzinski notifications@github.com wrote:

I just released an example on how to use any flux architecture with React and Meteor. This replaces the need for the ReactData mixin and instead relies on Tracker to trigger collection update actions to refresh the UI. I don't think it's inherently better than the mixin for re-rendering but it certainly adds a solid architecture that is well used at this point.

I've been using flux in a React Native app and it has been really nice to work with. Once you know the flow it's very easy to track down state related bugs. It also scales well with many components/actions.

https://github.com/AdamBrodzinski/meteor-flux-leaderboard https://forums.meteor.com/t/flux-example-app-with-react-meteor/7416 (This example is using Alt, one of the many flux libraries)

@arunoda great to hear! I've been doing this as well for several weeks and it's been working out well.

— Reply to this email directly or view it on GitHub.

arunoda commented 9 years ago

This is great.

@AdamBrodzinski how this works with routing? So, I'm thinking a way to put FlowRouter into this loop. So, we can use the same tooling for SSR this kind of app as well.

So, here are the things we need to have and I'm thinking of:

AdamBrodzinski commented 9 years ago

@ccorcos

How are things with React Native? Which DDP implementation are you using? Are you able to do hot code pushes to the app from meteor?

It's being going great, better than expected actually! I love how easy it is to hop between that and the web React. No Meteor loving on that app... The closest thing was Pete Hunt's Minimongo cache. I used a REST API that was used for the legacy iOS app. However it's a Meteor server so i'm looking forward to getting the Meteor bundle JS to run on it.

I haven't worked with Flux very much but it looks like there's a lot of unnecessary fluff/boilerplate

There def. is with vanilla flux! Alt abstracts away the dispatcher and leaves you with setting up actions and stores. Without Meteor it's very hard to do updates across the app, this is where it shines. Even with Meteor it's hard to make an entire tree re-render from some event happening (a click for example). Flux makes that easy.

You can also get things for free like event playback (replay bugs), isomorphic support for SSR, time-travel (undo/redo), etc...

It's also one of those things that's hard to grok, kind of like React itself. I started without it on the React Native and used Pete's Minimongo cache (has a similar update as the MDG mixin). This worked but it was really hard to keep track of things. I didn't realize how much it helped until after I put it in :laughing:

It seems like a bit overkill on the leaderboard app but flux works even better on an app like Spotify. Here's what you have to add (with Alt) if you need a new action and new store (normally you can just add in a method to an existing domain) There's also mixins to reduce this further.

class PlayerActions {
  incrementScore(playerId) {
    // do some logic with
    return playerId;
  }
}

this.PlayerActions = alt.createActions(PlayerActions);
class PlayerStore {
  constructor() {
    this.bindListeners({
      onIncrementScore: PlayerActions.incrementScore
    });

    this.state = {};
  }

  onIncrementScore(docId) {
    Players.update({_id: docId}, {$inc: {score: 5}});
  }

this.PlayerStore = alt.createStore(PlayerStore, 'PlayerStore');
AdamBrodzinski commented 9 years ago

@AdamBrodzinski how this works with routing? So, I'm thinking a way to put FlowRouter into this loop. So, we can use the same tooling for SSR this kind of app as well.

Thanks! Routing has puzzled me as well. Ideally the route should change from an event being emitted. This lets you use it when you're replaying bugs or doing time-travel. In my React Native app i'm using the NavigatorIOS to handle routing so I ended up setting up a RouterActions that has a method 'goToRoute' and the action calls the global NavigatorIOS to pop/push.

With the React-ive Meteor example app i'm planning on making the router fire an action in the router callback that emits the params info for any stores/views that are interested. I imagine that I would create a method similar to above that triggers FlowRouter.go as an action.

I haven't llooked too far into this but the fluxible router seems interesting: https://github.com/yahoo/fluxible-router

So, here are the things we need to have and I'm thinking of:

Update the URL for some set of actions Fire actions saved in the URL when directly loaded from that URL

Sounds good to me!

arunoda commented 9 years ago

Thanks. I'll have a look into that.

On Fri, Jul 24, 2015 at 10:50 AM Adam Brodzinski notifications@github.com wrote:

@AdamBrodzinski https://github.com/AdamBrodzinski how this works with routing? So, I'm thinking a way to put FlowRouter into this loop. So, we can use the same tooling for SSR this kind of app as well.

Thanks! Routing has puzzled me as well. Ideally the route should change from an event being emitted. This lets you use it when you're replaying bugs or doing time-travel. In my React Native app i'm using the NavigatorIOS to handle routing so I ended up setting up a RouterActions that has a method 'goToRoute' and the action calls the global NavigatorIOS to pop/push.

With the React-ive Meteor https://github.com/AdamBrodzinski/react-ive-meteor example app i'm planning on making the router fire an action in the router callback that emits the params info for any stores/views that are interested. I imagine that I would create a method similar to above that triggers FlowRouter.go as an action.

I haven't llooked too far into this but the fluxible router seems interesting: https://github.com/yahoo/fluxible-router

So, here are the things we need to have and I'm thinking of:

Update the URL for some set of actions Fire actions saved in the URL when directly loaded from that URL

Sounds good to me!

— Reply to this email directly or view it on GitHub https://github.com/meteor/react-packages/issues/19#issuecomment-124334897 .

AdamBrodzinski commented 9 years ago

@arunoda Is it possible to use Fast Render with subs manager to send down data to the client? I'm looking into hydrating the flux store without doing a full SSR. Ideally for users we just want the data and can then prime the store when it gets initialized.

However i'm not sure about the subscription situation... I could use a sub in Flow Router but ideally the subscription would be in a 'container' component that just handles the store lifecycle and subscriptions. (i'm going to move subs out of the store and into the components for the ex. app soon)

arunoda commented 9 years ago

I'm working on that part and that's what I building next for FlowRouter. There we only run components and send the detect subscriptions and send them to the client. I'm quite not sure about the final stuff. But I'll let you know.

AdamBrodzinski commented 9 years ago

Woo hoo! :+1: :smile: :beers:

ccorcos commented 9 years ago

@AdamBrodzinski thanks for the example. I've been checking out Flux, and I like the concepts. I'm not the biggest fan of how they've implemented it, but the most important thing I learning from reading more about Flux is that its really nice to keep your data separate from the view.

The MeteorDataMixin works great for Meteor data, but its not generic enough to work well with 3rd party APIs, REST apis, Meteor.methods, etc. The flux pattern has forced me to think about all of these types of asynchronous data fetching and caching alike. meteorhacks:subs-manager for ccorcos:subs-cache are a really round-about way of caching. Whereas if you use a flux-like pattern, caching is far more straightforward.

Anyways, I'm working on my own pattern of this and I'd be happy to share if you're interested.

giantelk commented 9 years ago

My understanding is getMeteorData() along with the real meat this.data is local to a React Component. So if you want to share data between components you can pass as this.props, like <MyComponent newStuff={this.data.someThing} />.

Or assign this.data to a Meteor Session variable, like Session.set("anotherThing", this.data.someThing ).

Or is the ultimate goal to never use Session var's (or Flux) when using React w/ Meteor? Or when writing an app from scratch w/ React & Meteor?

On Mon, Jul 27, 2015 at 2:15 PM, Chet Corcos notifications@github.com wrote:

@AdamBrodzinski https://github.com/AdamBrodzinski thanks for the example. I've been checking out Flux, and I like the concepts. I'm not the biggest fan of how they've implemented it, but the most important thing I learning from reading more about Flux is that its really nice to keep your data separate from the view.

The MeteorDataMixin works great for Meteor data, but its not generic enough to work well with 3rd party APIs, REST apis, Meteor.methods, etc. The flux pattern has forced me to think about all of these types of asynchronous data fetching and caching alike. meteorhacks:subs-manager for ccorcos:subs-cache are a really round-about way of caching. Whereas if you use a flux-like pattern, caching is far more straightforward.

Anyways, I'm working on my own pattern of this and I'd be happy to share if you're interested.

— Reply to this email directly or view it on GitHub https://github.com/meteor/react-packages/issues/19#issuecomment-125293355 .

Cheers, Flying Horse Dancing

AdamBrodzinski commented 9 years ago

Anyways, I'm working on my own pattern of this and I'd be happy to share if you're interested.

@ccorcos Let me know what you come up with! :+1: I also pushed a new PR with a Reflux example to the leaderboard repo. It eliminates the dispatcher altogether. If you're still into functional programming you should checkout Redux!

The MeteorDataMixin is great but it only handles re-rendering so you're on your own for app architecture. That's one of the perks I like about flux. Also since the dispatcher batches up actions there's less of a chance of cascading re-renders.

One note about APIs/Meteor Methods... you should only be dispatching actions on the response or you can lose the 1 way data flow (ex: TODOS_ERROR and TODOS_RECEIVED). This wasn't immediately obvious to me.

You might like this article on flux (one of the better ones!).


My understanding is getMeteorData() along with the real meat this.data is local to a React Component. So if you want to share data between components you can pass as this.props, like <MyComponent newStuff={this.data.someThing} />.

Or assign this.data to a Meteor Session variable, like Session.set("anotherThing", this.data.someThing ).

Yep, you got it! However if you want to share data with a sibling or a parent you can't really do that with just props. Like you mentioned using something like Session or a reactive dict would help you share it globally.

Or is the ultimate goal to never use Session var's (or Flux) when using React w/ Meteor? Or when writing an app from scratch w/ React & Meteor?

I don't think only using the mixin would get you very far. That's one of the issues flux solves... sharing state between components. You can only get so far passing them downward, eventually that's more confusing (I tried with React Native). IMHO the goal of the mixin is to give you easy support for Reactive Meteor data. It's way easier to get started with that than with flux.

giantelk commented 9 years ago

Regarding your API caching, certainly doesn't sound like a good fit for getMeteorData(), unless maybe you just assign the results of your API get call to this.data. But you still run into issues as you mentioned passing props up, and deep down the React Component chain.

I read somewhere that Meteor's Session.set/get eliminate the need for Flux. Although I've never used Flux so I'm not fully aware of Flux's power VS Meteor's Session object. Sounds like a good topic for a blog post -) I personally never liked Meteor's Session since it's Global and reminds me of CSS classes where you never know who's using/calling it without searching the entire code base (i.e. jQuery soup calls).

Would be nice to have a Meteor MDG sanctioned way of doing Flux type stuff, as part of the React integration, or as a separate package, since it's not required for every React app.

On Mon, Jul 27, 2015 at 10:56 PM, Adam Brodzinski notifications@github.com wrote:

Anyways, I'm working on my own pattern of this and I'd be happy to share if you're interested.

@ccorcos https://github.com/ccorcos Let me know what you come up with! [image: :+1:] I also pushed a new PR with a Reflux example to the leaderboard repo. It eliminates the dispatcher altogether. If you're still into functional programming you should checkout Redux https://github.com/gaearon/redux!

The MeteorDataMixin is great but it only handles re-rendering so you're on your own for app architecture. That's one of the perks I like about flux. Also since the dispatcher batches up actions there's less of a chance of cascading re-renders.

One note about APIs/Meteor Methods... you should only be dispatching actions on the response or you can lose the 1 way data flow (ex: TODOS_ERROR and TODOS_RECEIVED). This wasn't immediately obvious to me.

My understanding is getMeteorData() along with the real meat this.data is local to a React Component. So if you want to share data between components you can pass as this.props, like <MyComponent newStuff={this.data.someThing} />.

Or assign this.data to a Meteor Session variable, like Session.set("anotherThing", this.data.someThing ).

Yep, you got it! However if you want to share data with a sibling or a parent you can't really do that with just props. Like you mentioned using something like Session or a reactive dict would help you share it globally.

Or is the ultimate goal to never use Session var's (or Flux) when using React w/ Meteor? Or when writing an app from scratch w/ React & Meteor?

I don't think only using the mixin would get you very far. That's one of the issues flux solves... sharing state between components. You can only get so far passing them downward, eventually that's more confusing (I tried with React Native). IMHO the goal of the mixin is to give you easy support for Reactive Meteor data. It's way easier to get started with that than with flux.

— Reply to this email directly or view it on GitHub https://github.com/meteor/react-packages/issues/19#issuecomment-125418025 .

Cheers, Flying Horse Dancing

AdamBrodzinski commented 9 years ago

@giantelk

I read somewhere that Meteor's Session.set/get eliminate the need for Flux. Although I've never used Flux so I'm not fully aware of Flux's power VS Meteor's Session object. Sounds like a good topic for a blog post -)

Yea I thought about writing one :smile: a lot of work though. This intro to flux article is really good.

Basically flux is an architecture that allows you to have an app data flow like this:

If you follow the paradigm of the app only changing state with actions, you could log dispatcher events and see exactly what happens across the entire app when you move about. Your views are also decoupled from data mutation.

So flux is more of an architecture than anything. Session is just a reactive dictionary/variable (that is global). Better flux implementations (there are many) will only let you access state through a getState method and you can't mutate from the store (only actions)

However you can get by with just a global Session variable that gets passed to getMeteorData and it will re-render. _This works great and is simple_. But where should you set the session? In the click handler? then you get into the problem you mentioned... where is this session being set??? Get out ack/grep and hope for the best.

It's also worth mentioning that the flux designed for React doesn't work well with Blaze because Blaze doesn't re-render the entire tree when something changed (which is good!). Default flux isn't reactive so your templates wouldn't re-render on change. The best I could come up with (without being overly complex) for Blaze is this design pattern which is loosely modeled after flux minus the dispatcher and events.

Anyhow does this explain how flux works/why it's helpful?

ccorcos commented 9 years ago

If you're still into functional programming you should checkout Redux!

I've become very fond of functional programming. I just ran across cycle.js from within the Redux documentation and it looks really interesting. Took me 2-3 hours to read everything -- I think thats the future.

@ccorcos Let me know what you come up with!

Anyways, here's what I've come up with from the perspective of an API:

A "store" here is inspired by flux but clearly different and specific for Meteor. It simply fetches and caches data, separating this functionality from the view. There a few variations. On the client, createStore allows you to fetch some data asynchronously using HTTP.call or a REST API. createListStore does the same thing, but has some paging features. createDDPStore is isomorphic, which abstracts away Meteor.publish and Meteor.subscribe. createDDPListStore is the same thing but with paging features.

Here's how I'm using it right now (facebook.api just abstracts HTTP.call for me). The "query" is serialized and used for caching data, and the "options" include things like paging, offset, and that won't be serialized for caching.

UserSearchStore = createListStore 'user-search', (query, {limit, offset}, callback) ->
  facebook.api 'get', "/search", {
    fields: 'name,picture{url}'
    type: 'user'
    q: query
    limit: limit
    offset: offset
  }, (err, result) ->
    is err then throw err
    callback result.data.map (result) ->
      name: result.name
      id: result.id
      img: result.picture.data.url

Then I have a list component that uses the store to render a list of items. state.data is initially null unless it has been cached. fetch will be a function to fetch initial data or fetch more if there is more data, otherwise its null. clear will set a timer to clear the cached data. store.fetch will callback with the same thing as store.get.

  getInitialState: ->
    return {data, fetch, clear} = @props.store.get(@props.query)
  loadMore: ->
    @setState({loading: true})
    @state.fetch (nextState) =>
      nextState.loading = false
      @setState(nextState)
  componentWillMount: ->
    if not @state.data
      @loadMore()
    else
      @setState({loading: false})
  componentWillUnmount: ->
    @state.clear()

Now, for reactive stuff, its very similar, but deals with the client and the server rather transparently. It still could use some work...

UserFollowersStore = createDDPListStore 'followers', (userId, {limit, offset}) ->
  Neo4j.query """
    MATCH (:USER {id:'#{userId}'})-[f:FOLLOWS]->(u:USER)
    MATCH u
      ORDER BY f.createdAt
      LIMIT #{limit} SKIP #{offset}
  """

This using something very similar to ccorcos:any-db to publish from a Mongo.Cursor or any other data reactively. Now, all we have to do is listen for changes in the component which is an additional returned argument {data, clear, fetch, onChange} = store.get(query). clear will clean up the listener for us as well.

  componentWillMount: ->
    @state.onChange (data) -> @setState({data})
    #...

Now for reactivity and latency compensation, we have actions that call "update" on some store. On the client, the function will latency compensate, but on the server, it will simply trigger another poll and diff for the appropriate subscriptions.

Meteor.methods
  'followUser': (followId) ->
    if Meteor.isServer
      Neo4j """
        MERGE (:USER {id:#{str(@userId)}})-[:FOLLOWS]->(:USER {id:#{str(followId)}})
      """
    UserFollowersStore followId, (data) => data.concat({id:@userId, unverified:true})
    UserFollowingStore @userId, (data) =>  data.concat({id: followId, unverified:true})

This still feels awfully procedural though. But it makes sense and its working smoothly. Let me know what you think.

AdamBrodzinski commented 9 years ago

@ccorcos i'm going to have to digest this but it looks really cool :)

ccorcos commented 9 years ago

@AdamBrodzinski, check out this little example chatroom app

https://github.com/ccorcos/meteor-any-db/blob/v2/packages/ccorcos:any-db-stores/examples/ddp/main.coffee

dbackeus commented 8 years ago

Any progress on any of the ideas outlined above?

Anyone using meteor collections in an immutable way to improve shouldComponentUpdate performance somehow?

filipenevola commented 3 years ago

I'm closing this just because it's too old. We can open new issues for items that are still valid.