Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: <Routes dataSource> #405

Closed
mjackson opened this issue Oct 14, 2014 · 22 comments
Closed

Proposal: <Routes dataSource> #405

mjackson opened this issue Oct 14, 2014 · 22 comments
Labels

Comments

@mjackson
Copy link
Member

I haven't fully crystalized my thinking about this yet, but I had a great conversation with @petehunt last week where he told me about a few ideas he had about data fetching. I've had a few thoughts since, but hopefully I haven't strayed too far from the meat of it. Hopefully Pete can correct me where I messed up.

The Problem

We need a way to get data into the views. The router is fairly well-suited to solve this problem because it decides what components render on the page given the current URL.

A Possible Solution

There are a few parts to this solution:

First, "route handler" components would have a way to declare the types of data they are interested in using some unique key. For example, a route handler could indicate it needs the currentUser or post variable. Then, it would have another method that returns a hash of these keys to strings (think URLs) that uniquely identify those pieces of data. This could look like:

var BlogPost = React.createClass({
  statics: {
    dataTypes: {
      currentUser: React.PropTypes.instanceOf(User),
      author: React.PropTypes.instanceOf(User),
      post: React.PropTypes.instanceOf(Post)
    },
    getDataKeys: function (params, query) {
      return {
        currentUser: 'users/current',
        author: 'posts/' + params.postID + '/author',
        post: 'posts/' + params.postID
      };
    }
  }
});

Secondly, The <Routes> object would have a dataSource. The dataSource is responsible for fetching data based on the (de-duped) set of keys generated by the currently active routes. It could be as simple as a function that takes an array of the keys and returns an array of values/promises.

function fetchData(dataKeys) {
  // Do whatever you want here...
}

<Routes dataSource={fetchData}>
  ...
</Routes>

Lastly, any component that needs some data simply mixes in Router.Data and declares the dataTypes it needs:

var AuthorWidget = React.createClass({
  mixins: [ Router.Data ],
  dataTypes: {
    author: React.PropTypes.instanceOf(User)
  },
  render: function () {
    var author = this.getData('author');
    // ...
  }
});

Benefits

The only real advantage I can think of for this approach over getHandlerProps is that branching for data fetching happens in your dataSource instead of your route handlers.

There is also the fact that you don't have to pass props around, but you still get the benefits of declaring the types of data you need (similar to propTypes) so it shouldn't be too hard to refactor stuff.

Thoughts?

Edit: Every time we talk about data loading the thread gets a hundred comments. If you're going to comment here, please keep your comments specific to this proposal. If you have another idea, spin up a separate issue. Thanks!

@ryanflorence
Copy link
Member

I am kind of refactoring to this in my getRouteProps work, so its appealing to me.

My only concern is that its a lot of convention and indirection, which I hate documenting, and is generally rejected in react. Would be cool if we could build this on top of something like getRouteProps, so people can drop down a level to do whatever they want.

Its the instanceOf stuff that's really bothering me. I don't want to conventionalize model stuff when we just need data.

@ryanflorence
Copy link
Member

What's the point of dataTypes? Can't those just be propTypes?

 var BlogPost = React.createClass({
  statics: {
    getDataKeys: function (params, query) {
      return {
        currentUser: 'users/current',
        author: 'posts/' + params.postID + '/author',
        post: 'posts/' + params.postID
      };
    }
  },

  propTypes: {
    currentUser: React.PropTypes.instanceOf(User),
    author: React.PropTypes.instanceOf(User),
    post: React.PropTypes.instanceOf(Post)
  }
});

@mjackson
Copy link
Member Author

its a lot of convention and indirection

I don't understand the word "indirection". People use it all the time, but I have no idea what they're talking about. Can you explain?

Its the instanceOf stuff that's really bothering me

instanceOf is just one example. You can use any of the stuff in React.PropTypes.

@ryanflorence
Copy link
Member

In computer programming, indirection is the ability to reference something using a name, reference, or container instead of the value itself.

http://en.wikipedia.org/wiki/Indirection

@mjackson
Copy link
Member Author

What's the point of dataTypes? Can't those just be propTypes?

No, because they're not props.

@mjackson
Copy link
Member Author

Thank you for quoting Wikipedia. Trust me, I've read that article and I still don't get it.

@ryanflorence
Copy link
Member

author: 'posts/' + params.postID + '/author',

I also don't like that I may have to now parse this key to perform the operation in my dataSource, the router already parsed it for me.

No, because they're not props.

They are eventually.

@mjackson
Copy link
Member Author

They are eventually.

Not necessarily. They only become props if you use them as props. In my AuthorWidget above there are no props.

@ryanflorence
Copy link
Member

Maybe we could send more than just the key to data source,

function dataSourceHandler(payload) {
  payload.routeName
  payload.params
  payload.query
  payload.keys
}

@ryanflorence
Copy link
Member

sometimes the route name will matter, other times it won't at all, or even send the route handler so people can then put static methods on them to keep the code in the same place as getRouteProps

@ryanflorence
Copy link
Member

yeah send payload.route instead of routeName, then you have a bunch of information

@appsforartists
Copy link

Thank you for quoting Wikipedia. Trust me, I've read that article and I still don't get it.

You guys make me laugh.

@ryanflorence
Copy link
Member

@mjackson I like this, I'll screw around with some fake code now that I've got my head wrapped around the constraints of building a server and client rendered app. I'm sure I'll have some questions at the end of the day.

Right now I have foggy thoughts and concerns about how the lookup/caching lifecycle works. I want people to have their own data layers that this just plugs into minimally and explicitly.

@mjackson
Copy link
Member Author

I have foggy thoughts and concerns about how the lookup/caching lifecycle works

Totally. I don't want to get into managing any of that explicitly. I was thinking we would call the dataSource with only the keys from the routes we're transitioning into, which is analogous to the way willTransitionTo works (and getHandlerProps).

@mjackson
Copy link
Member Author

@rpflorence The whole idea of keys may be too tailored to my use case. I'm using a key/value store as my backend, so it appeals to me. But I do find the idea of being able to refer to each piece of data in your system by some key/URL very appealing.

@mjackson
Copy link
Member Author

@spicyj Is there any way we can hook into the createClass method so that dataTypes can be defined outside of statics but still live on the constructor?

@ryanflorence
Copy link
Member

But I do find the idea of being able to refer to each piece of data in your system by some key/URL very appealing.

Yeah, it works great for the server-render, and then the initial client render. After that you'll need subscriptions, not sure if we should handle this, or if we just say to subscribe in componentDidMount and make sure to slurp up the context data value in getInitialState and componentWillReceiveProps.

Why do dataTypes even matter? Are they optional like propTypes?

@sophiebits
Copy link
Contributor

No. In a few releases there'll just be plain ES6 classes and it'll look something like facebook/react#1380, so you can do whatever's possible there. What's wrong with statics?

@mjackson
Copy link
Member Author

@rpflorence dataTypes are the mask we use to 1) declare what data components need/expect and 2) validate they are present. It really helps when you're refactoring things.

@spicyj Nothing's wrong with statics. Just wondering..

@mjackson
Copy link
Member Author

After that you'll need subscriptions

I personally re-use my keys for subscribing to data. I can do a one-time fetch or subscribe using the same key.

@edygar
Copy link
Contributor

edygar commented Oct 17, 2014

What if we forked the dependencies (like Stores) accordingly to the environment?

/* on client-side */
var routes = require('./routes');
// Inject dependencies throught context
React.renderComponent(React.withContext({
   MessagesStore: new ClientMessagesStore(),
   ThreadStore: new ClientThreadStore()
}, function() {
   return routes;
}),document.body);
/* on server-side */
// Inject dependencies throught context
renderRoutesToString(React.withContext({
   MessagesStore: new ServerMessagesStore(),
   ThreadStore: new ServerThreadStore()
}, function() {
  return routes;
}), req.path, function() { /* ... */ });

If there were anyway to inject the context on static methods like the one bellow, we would solve this gracefully

React.createClass({
  statics: {
    willTransitionTo: function(transition) {
      transition.wait(  this.context.MessagesStore.getAllMessages());
    }
  }
});

@mjackson
Copy link
Member Author

I did a bunch of work on this idea in the data-types branch, but at this point I'm thinking that getAsyncProps would be a simpler API. Let's go with that for the next release. We can always come back to this if we realize we need it.

@lock lock bot locked as resolved and limited conversation to collaborators Jan 25, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants