Giter Site home page Giter Site logo

Comments (16)

skmasq avatar skmasq commented on June 7, 2024

+1

from fluxxor.

bitmage avatar bitmage commented on June 7, 2024

I think this question was more pertaining to having some sort of running server API connected to the front end Fluxxor / React code. The examples you have are interesting, but only demonstrate client code in isolation.

The default scenario that most people will want to see is connecting to a REST API. A more interesting scenario is connecting to a websockets API that would stream changes in the data (ala Firebase, Meteor, or some similar approach).

from fluxxor.

BinaryMuse avatar BinaryMuse commented on June 7, 2024

@bitmage You're right; I meant to reference #6. Thanks!

from fluxxor.

bitmage avatar bitmage commented on June 7, 2024

Cool! Would you like help building some examples?

On Jul 4, 2014, at 4:28 PM, Brandon Tilley [email protected] wrote:

@bitmage You're right; I meant to reference #6. Thanks!


Reply to this email directly or view it on GitHub.

from fluxxor.

BinaryMuse avatar BinaryMuse commented on June 7, 2024

I have a couple demos/examples in various stages of completeness, but ideas and code are always welcome and appreciated!

from fluxxor.

jwalton avatar jwalton commented on June 7, 2024

A few random thoughts about this:

First, our objective is to build an isomorphic app, so we want to render an HTML page on the server which contains fully rendered React views and which has Javascript which prepopulates all the stores and then calls into React to hook up our event handlers.

My first thought is that we probably do not want to run the same actions on the client and server; the client side may do Ajax requests which go fetch data from the server, but server side we don't want to send HTTP Ajax requests to ourselves; we want to replace these calls with synchronous or asynchronous calls. Server side there also may not be any need to fire a bunch of actions through flux in the first place, as it may be more desirable to just build an initial view of the stores through plain old sync and async calls.

If we head this way, note that if your components use FluxMixin or StoreWatchMixin, then you must pass a flux instance to your components, even when rendering them on the server, because these two classes try to find flux in componentWillMount() which is called on both the client and the server. It's tempting to say that in the case of StoreWatchMixin, this block should be moved to componentDidMount(), which is only called client side - this way server side we can just pre-populate the stores and we don't need to bother hooking up any listeners. Alternatively, StoreWatchMixin could take an optional parameter listenToStoresOnServer which controls whether this is done in componentWillMount() or componentDidMount().

Rendering the react components on the server is straightforward, but we also need to marshal the contents of the stores into a JSON string that we can pass down to the client. Ideally this should be straightforward without having to write a lot of boilerplate code. Stores can store data fairly arbitrarily, though. Perhaps if we defined a toJSON() and a fromJSON(jsonString) on each Store, then on the server we could call flux.storesToJSON() to get a big JSON object of all the stores, which we could embed in a <script> tag, then client side call flux.storesFromJSON(jsonStores) to prepopulate the stores before our initial render.

How does this sound? Do you have some completely other way of doing this in mind? :)

from fluxxor.

BinaryMuse avatar BinaryMuse commented on June 7, 2024

@jwalton I think this is right on. I think you're right about moving the StoreWatchMixin code to componentDidMount; in fact, the current implementation will probably leak on the server.

from fluxxor.

ptomasroos avatar ptomasroos commented on June 7, 2024

@jwalton look at the components yahoo has open sourced? https://github.com/yahoo/flux-examples

from fluxxor.

ftorre104 avatar ftorre104 commented on June 7, 2024

+1 for a server-side rendered example. Fluxxor is great; would love to use it for our isomorphic app. IMO Yahoo's implementation is too involved, they give you more of a complete framework rather than a library.

from fluxxor.

ftorre104 avatar ftorre104 commented on June 7, 2024

I have been able to get fluxxor to work with server side rendering, but feel it is not 100%.
Functioning implementation:
Create a flux object on the server and pass it into your App before you renderToString:

app.route('/*').get(function(req, res, next) {
    var path = url.parse(req.url).pathname;
    var stores = {
        CommentStore: new CommentStore(),
        TodoStore: new TodoStore()
    };
    var flux = new Fluxxor.Flux(stores, actions);
    var AppElement = React.createElement(App, {
        flux: flux,
        path: path
    });
    var markup = React.renderToString(AppElement);
    res.send(markup);
});

Then in your client side bootstrap code, you have to recreate the same flux object:

if (typeof window !== 'undefined') {
    // trigger render to bind UI to application on front end
    window.onload = function() {
        console.log('RUNNING ON CLIENT: re-render app on load to initialize UI elements and all')
        var path = url.parse(document.URL).pathname;
        var stores = {
            TodoStore: new TodoStore(),
            CommentStore: new CommentStore()
        };
        var flux = new Fluxxor.Flux(stores, actions);
        flux.on("dispatch", function(type, payload) {
            if (console && console.log) {
                console.log("[CLIENT Dispatch]", type, payload);
            }
        });
        // re-render on client side with same information to bind UI actions!
        React.render(React.createElement(App, {
            flux: flux,
            path: path
        }), document);
    };
}

The code above works, however I see it potentially having some drawbacks, particularly because we are recreating the flux object.

  1. Stores could have changed between server side rendering and client side re-rendering (not sure if this would be an invariant error, though - ??)
  2. Duplication of code in the creation of the stores -- you have to keep up with 2 spots. I guess this can be outsourced to a CommonJS module or somthing?

I guess what inherently bothers me is that it allows for inconsistencies between server/client version.

I would love to be able to pass the flux object down to the client:
either

markup += '<script id="flux">window.flux = ' + JSON.stringify(flux) + '</script>';

or

markup += '<script id="flux" type="application/json">' + JSON.stringify(flux) + '</script>';

and then simply retrieve it in the bootstrap client code.

However whenever I try to do this, I get an error. Apparently the flux object is too cool.

TypeError: Converting circular structure to JSON

Any thoughts? Is it safe to create the flux object separately? Or is there another way to pass it down?

from fluxxor.

ftorre104 avatar ftorre104 commented on June 7, 2024

For anyone interested, @BinaryMuse has a good solution. Since the flux object is circular, you can't really serialize it (as shown above).

Here's the example I followed:
https://github.com/BinaryMuse/isomorphic-fluxxor-experiment/

Brief overview:

  1. Modify your stores such that:
    -- all state data is held in a this.state
    -- add methods serialize() and hydrate() that call JSON.stringify() and JSON.parse(), respectively, on the this.state

Example:

var ExampleStore = Fluxxor.createStore({
    initialize: function() {
        // fetch initial data here!
        this.state = {
            page: {
                title: '[email protected]',
                layout: 'Test Account',
                content: '<h1> this is a page </h1><div> content goes here! </div>'
            }
        };
        this.state = {
            todos: [],
            otherData: {}
        };
        // BIND actions that this store can take to dispatcher actions
        this.bindActions(
            constants.ADD_TODO, this.onAddTodo,
            constants.TOGGLE_TODO, this.onToggleTodo,
            constants.CLEAR_TODOS, this.onClearTodos
        );
    },
    serialize: function() {
        return JSON.stringify(this.state);
    },
    hydrate: function(json) {
        this.state = JSON.parse(json);
    },
    // when the ADD_TODO action is called, the dispatcher notifies this store
    // and this store runs this function
    onAddTodo: function(payload) {
        // save data to server here
        this.todos.push({
            text: payload.text,
            complete: false
        });
        this.emit('change');
 ....
  1. Extend your flux object with 2 methods: serialize() and hydrate()
var flux = new Fluxxor.Flux(stores, actions);

// Add our own custom serialize and hydrate methods.
flux.serialize = function() {
    var data = {};
    for (var key in stores) {
        data[key] = stores[key].serialize();
    }
    return JSON.stringify(data);
};

flux.hydrate = function(data) {
    for (var key in data) {
        console.log(key)
        stores[key].hydrate(data[key]);
    }
};
  1. On pass the serializedFlux into your render function on the server (client too, but in a second!)
        var serializedFlux = flux.serialize();
        var AppElement = React.createElement(App, {
            flux: flux,
            path: path,
            serializedFlux: serializedFlux
        });
        var markup = React.renderToString(AppElement);
        if (markup.indexOf('NotFoundPage') > -1) {
            console.log('404 page not found!');
            res.status(404);
        }
        res.send(markup);
  1. Include the serializedFlux in your template
var App = React.createClass({
    displayName: 'App',
    mixins: [FluxMixin],
    test: function(){
        console.log('hey');
    },
    render: function() {
        return  <html>
                    <AppHead />
                    <body>
                        <AppRoutes path={this.props.path} />
                        <script type='text/javascript' src={formatTo.cdn('/dist/js/bundle.js')}/>
                        <script id="serializedFlux" type="application/json" dangerouslySetInnerHTML={{__html: this.props.serializedFlux}} />
                    </body>
                </html>;
  1. When you are going to rehydrate, create a new flux object and then hydrate it with the serializedFlux data
var React = require('react');
var App = require('./App');
var flux = require('./flux');

if (typeof window !== 'undefined') {
    window.onload = function() {
        console.log('Rehydrating application.');
        flux.on('dispatch', function(type, payload) {
            if (console && console.log) {
                console.log('[CLIENT Dispatch]', type, payload);
            }
        });
        var path = window.location.pathname;
        var serializedFlux = document.getElementById('serializedFlux').innerHTML;
        flux.hydrate(JSON.parse(serializedFlux));
        var AppElement = React.createElement(App, {
            flux: flux,
            path: path,
            serializedFlux: serializedFlux
        });
        React.render(AppElement, document);
    }
}

And that should do it!

from fluxxor.

BinaryMuse avatar BinaryMuse commented on June 7, 2024

Thanks for the note, @ftorre104. The example currently on master at https://github.com/BinaryMuse/isomorphic-fluxxor-experiment/ seems to work out pretty well, but it has some caveats (see the bottom of the readme). I'm toying with some other ideas, and I'll get an example into the Fluxxor examples folder before too long. :)

from fluxxor.

ftorre104 avatar ftorre104 commented on June 7, 2024

Ah that's true. I set it up to only do one render on the server. I call an action serverFetchAsync() that makes all the calls. Once the calls are finished, it will render:

Server:

....
var App = require('../app/App');
var flux = require('../app/flux');
....

server.route('/*').get(function(req, res, next) {
    var path = url.parse(req.url).pathname;
    flux.actions.serverFetchAsync(path, doRender);

    function doRender() {
        var serializedFlux = flux.serialize();
        var AppElement = React.createElement(App, {
            flux: flux,
            path: path,
            serializedFlux: serializedFlux
        });
        var markup = React.renderToString(AppElement);
        if (markup.indexOf('NotFoundPage') > -1) {
            console.log('404 page not found!');
            res.status(404);
        }
        res.send(markup);
    }
});

And:

var Actions = {
    serverFetchAsync: function(path, callback) {
        var asyncCallCount = 0;
        if (path.indexOf('user') > -1) {
            // ex. mydomain.com/user/123
            // can be improved to better path matching!
            asyncCallCount++;
            request.get('https://mydomain.com/api/v1/user/123', function(err, response, body) {
                this.dispatch(constants.USER_DETAILS_SUCCESS, body);
                finished();
            }.bind(this));
        }
        if (path.indexOf('posts') > -1) {
            asyncCallCount++;
            var url = 'https://mydomain.com/api/v1/posts'
            request.get(url, function(err, response, body) {
                this.dispatch(constants.USERS_SUCCESS, body);
                finished();
            }.bind(this));
        }
        var finished = _.after(asyncCallCount, callback);
    },

Actions will be dispatched w/ payloads to the stores listening to them. You can then load the data into the relevant store. After that, your app will be rendered on the server.

from fluxxor.

jwalton avatar jwalton commented on June 7, 2024

@BinaryMuse A slight suggested variation on what you've got going on in https://github.com/BinaryMuse/isomorphic-fluxxor-experiment/. Instead of throwing away the initial result from the first server side render, just return it.

The idea I'm proposing here is, on the server side, stores should basically not do any async work - if they have the data cached, return it, and if not, return a loading token (and then maybe go kick off the async call so the result will be cached for later, if you want to be fancy.) If we render a page with "Loading...", so be it; we return that to the client. Client side, when we do our client-side re-render, components will ask the stores for this data again, and if they returned a loading token on the server, they still won't have them, so they'll kick off an async call to the server to go fetch this data.

There are a couple of advantages here; first and foremost we return something to the client as quickly as possible, and our user is not left staring at a loading bar (especially good if our async call is taking a while for some reason.) Second, this neatly solves the caveat you mention where one async call completes, which adds new widgets, which make more async calls. This cascade all happens on the client now.

from fluxxor.

BinaryMuse avatar BinaryMuse commented on June 7, 2024

@jwalton I could see this technique being useful for a subset of applications, but one of the draws of server-rendering with React is SEO; in these cases, we would want to have content before we render.

from fluxxor.

ptomasroos avatar ptomasroos commented on June 7, 2024

We're doing it wrong. Lets just make something that no one else does, just reuse the routes and pipe out the JSON results needed for the stores ?

Proof: http://www.onebigfluke.com/2015/01/experimentally-verified-why-client-side.html

from fluxxor.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.