Comments (16)
+1
from fluxxor.
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.
@bitmage You're right; I meant to reference #6. Thanks!
from fluxxor.
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.
I have a couple demos/examples in various stages of completeness, but ideas and code are always welcome and appreciated!
from fluxxor.
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.
@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.
@jwalton look at the components yahoo has open sourced? https://github.com/yahoo/flux-examples
from fluxxor.
+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.
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.
- Stores could have changed between server side rendering and client side re-rendering (not sure if this would be an invariant error, though - ??)
- 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.
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:
- Modify your stores such that:
-- all state data is held in athis.state
-- add methodsserialize()
andhydrate()
that callJSON.stringify()
andJSON.parse()
, respectively, on thethis.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');
....
- 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]);
}
};
- 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);
- 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>;
- 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.
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.
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.
@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.
@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.
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)
- resetting stores HOT 2
- Question: asynchronous route transition HOT 1
- Inconsistent description of stores? HOT 4
- Fluxxor and React Router 1.x and ES6 HOT 7
- this.isMounted is not a function HOT 2
- Best practices for nested models HOT 1
- React warning after update to 1.7.2 HOT 1
- fluxxor with react router 1rc HOT 16
- Can one use Fluxxor with React Native? HOT 2
- Use instanceOf or shape in context types and add propTypes
- Wait until async request completes HOT 2
- Routing examples no longer work with latest version of React Router HOT 3
- Lost events with StoreWatchMixin during ComponentWillMount -> ComponentDidMount HOT 1
- Documentation for setting dispatch interceptor is out of date HOT 3
- In the Async Example not clear how to support add action which needed to call out to server HOT 1
- React-Router v4 and Fluxxor
- Current lodash dependency has a CVE
- Compatibility with React 16 HOT 2
- Typo in a link on the "What is Flux" page
- sdasdasdas
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fluxxor.