omniscientjs / omniscient

A library providing an abstraction for React components that allows for fast top-down rendering embracing immutable data for js
http://omniscientjs.github.io/
1.22k stars 51 forks source link

Defer React.createClass call as much as possible - Ask for ideas :) #98

Closed facundocabrera closed 9 years ago

facundocabrera commented 9 years ago

Hey all,

I'm trying to decouple the implementation of some components because I need to do different things between client and server, basically I want to inject different mixins based on the environment, but using always the same APIs between the different implementations.

The following example show data usage, but my situation do not depends on data which could be injected as a cursor or something similar.

var component = require('omniscient');

var base = component(function() {
  return <p>{this.something()}</p>;
});

module.exports = base;
// server.js
var base = require('./base');

var server = base({
  something: function() {
    return 'server';
  }
});

module.exports = server;
// client.js
var base = require('./base');

var client = base({
  something: function() {
    return 'client';
  }
});

module.exports = client;
// package.json
{
  "name": "example",
  "main": "server.js",
  "browser": "client.js"
}

If we could defer the React.createClass till I officially use the component (basically defer till I call to create), I will be able to inject more mixins to customize the behavior as mush as I want.

In the example provided I'm writing an imaginary API, where I'm adding more behavior to the component, like a composition phase. The component could be "finalized" with an specific call if it's necessary, and we could do that using the same approach of factories provided by React with our custom implementation to call an specific method:

var component = require('example'),
  omniscient = require('omniscient');

omniscient.createFactory(component) {
  return component.final(); // here I'm ready to call the `create` function and export the ReactElement with all my behavior
}

NOTE: I understand completely the composition approach promoted nowadays about be wrapping components, but I feel more natural to have full access to the component state and mixins instead of been wrapping all the time, that's why I want to have something like that in the project and seems not to be too complex to achieve.

Suggestions? Ideas? Any feedback will be really appreciated.

mikaelbr commented 9 years ago

I'm not sure I entirely understand your question, so bear with me on this: Couldn't you just await "converting a function to a component"? By that I mean, look at component() as your React.createClass.

Or you can have two different "component factories" depending on you are on client or server:

// componentFactoryClient.js
var component = require('omniscient');

module.exports = function createComponent (fn) {
  return component({
    something: function() {
      return 'client';
    }
  }, fn);
}
// componentFactoryServer.js
var component = require('omniscient');

module.exports = function createComponent (fn) {
  return component({
    something: function() {
      return 'server';
    }
  }, fn);
}

(Just a bit more sophisticated.)

facundocabrera commented 9 years ago

I'll try around your proposal, but I need more power in the composition process because I want to separate as a mention before the "composition phase" from the "instantiation". I showed a "1 level (base + client/server)" composition here, but my expectation is: keep the component composition open till the instantiation arrives, for example:

var component = require('omniscient');

var base = component(function() {
  return <p>{this.something()}</p>;
});

var level1 = base({
  something: function() {
    return this.somethingElse();
  }
});

var level2 = level1({
  somethingElse: function() {
    return 'somethingElse';
  }
});

And I don't know where the mixins will be in the source code, so factories could work in the way you proposed, but I'll need to write 1 factory per scenario, obviously, I can add some code to make it more friendly and generic, but applying this mechanism out of omniscient will make my code incompatible with the default API, that's why I was trying to ask for something like this in the project core.

Maybe now the request makes more sense :smile:

mikaelbr commented 9 years ago

I think the best approach is to either have different object literals of mixins and you choose the ones you like inside a factory function (or dynamically build up different sets of mixins), or use "higher order components" with partial applications etc.

What you are describing sound kind of like currying, and you can fairly easily create a "component builder" abstraction on top of Omniscient that achieve this. But it sounds to me like a very specific/rare use case and I don't think the best idea is to implement this to core. We're trying to keep the core as simple and directed as possible.

facundocabrera commented 9 years ago

Make sense. I'll create my own solution then. Thanks a lot for your time!

facundocabrera commented 9 years ago

@mikaelbr I have implemented a simplified version of what I guess could be an API. Basically sometimes high order components works just fine, but in others I found we need to have a way to have several mixins under the react default behavior.

Any comment will be welcome :smile: