jmreidy / fluxy

An implementation of Facebook's Flux architecture
232 stars 19 forks source link

Server rendering? #1

Closed stevoland closed 10 years ago

stevoland commented 10 years ago

Hi,

This looks really good. Are you planning on implementing server side rendering at all?

jmreidy commented 10 years ago

Thanks! React theoretically makes server side rendering quite easy... but it gets complicated when you start dealing with all sorts of async data loading.

I think it'd be possible to pass data to the Fluxy.start call, which would allow server-side bootstrapping of the stores when they mount. Then, React components would try to get data from the stores, which would already have the bootstrapped server data. The challenge is in getting something like react-router to work... not sure it supports server-side rendering yet. @rpflorence or @mjackson, is there a way of specifying the URL for server rendering with react-router?

mjackson commented 10 years ago

@jmreidy Not currently. We definitely want to make it happen tho, just need to figure out how. I'll open an issue over at react-router so we can track progress there.

jmreidy commented 10 years ago

@mjackson Seems like the top level route (that takes the location prop) could also take an initialPath prop, that's passed to urlStore.setup(location, initialPath). If initialPath is not null, urlStore just doesn't call handlePathChange (as it does on L39; instead, it calls updateCurrentPath(initialPath). Make sense?

mjackson commented 10 years ago

@jmreidy Yes, it will probably be something like that. In general, I want to have a way to decouple routers from listening to the URL, so you can just pass in whatever URL you want for testing or server-side rendering. We'll get there.

I've opened an issue where we can track the work here: https://github.com/rpflorence/react-router/issues/16

jmreidy commented 10 years ago

Now that react-router has server side rendering, this should be fairly easy to implement. I'll look into it in the next week or so.

jmreidy commented 10 years ago

Now that 0.3 is out, I'd like to get a priority list for features. Most basically I think that server side rendering would just require the ability to pass data into Fluxy.start. Depending on how stable the API is with 0.3, I'll move onto this soon.

tdreyno commented 10 years ago

:+1:

jmreidy commented 10 years ago

Initial version of this should be landing today! I'll update this ticket when it's ready.

stevoland commented 10 years ago

Looking forward to it!

jmreidy commented 10 years ago

I still need to add tests and document this, but server side rendering can be seen in the example now. It's actually incredibly easy to add support for it with Fluxy.

  1. Give your stores a unique name. Fluxy.createStore({name: 'TodoStore', //other config})
  2. Before calling React.renderComponentToString, make sure to call Fluxy.start on the server, passing it a hash of Store initial states (keyed by Store name)
  3. Make sure to bootstrap your HTML load with the state you passed to Fluxy. Fluxy has a convenience method for this: Fluxy.renderStateToString
  4. Finally, in your client side code, instead of calling Fluxy.start, call Fluxy.bootstrap(windowKeyOfBootstrappedData)

...and that's it. Don't need to touch your Fluxy code at all - it just works.

damassi commented 10 years ago

A+

tdreyno commented 10 years ago

:heart: @jmreidy

Started rolling my own to get used to React/Flux, but may switch to this now. Definitely need "server-side". Doing it all pure static in Middleman, so not really a backend, just a pre-render.

jmreidy commented 10 years ago

@tdreyno Awesome! Let me know if you run into any problems.

I was realizing another cool benefit of the renderStateToString approach is that you can easily serialize your entire client side app state and persist it to the backend. I'm planning on adding that to the example todomvc in the near future.

bgoldman commented 10 years ago

With your implementation of server rendering, how would it handle 2+ requests coming to the server at the same time? It Fluxy is a singleton and stores would contain data relevant to the user making the request, wouldn't multiple requests cause the data to become unreliable?

If User A makes a request and then User B makes a request, the stores would then contain the data relevant to User B, but if the request for User A is still processing, and requests any data from a store, it might render with User B's data, from User A's request!

Please correct me if I'm misunderstanding something, or let me know how to solve this issue, or if this is a known issue without a current solution.

fkrauthan commented 10 years ago

Any news on that ticket? Especially @bgoldman question?

jmreidy commented 10 years ago

I'm going to go ahead and close this for now, as I think the current implementation is working (well enough) that future changes can come from new tickets.

@bgoldman concurrency shouldn't be an issue so long as Fluxy configuration and rendering happen within the same function. Fluxy resets all its stores whenever the state is set, so the global nature of the stores should be isolated within each function call. (Functions bodies can't be processed simultaneously in Node.) That said, if there's any async between Fluxy config and renderStateToString, there'd be a problem - so refactoring things AWAY from global / single instance configuration is a good idea.

bgoldman commented 10 years ago

@jmreidy there is definitely async going on between init and renderStateToString, because the data that ends up in the stores is pulled asynchronously from API calls and/or the database. I'm assuming the vast majority of Fluxy apps have async to pull data, because the vast majority of apps need to make API calls and/or get data from the database.

How would you recommend running Fluxy on the server in a way where if we have two requests come in at the same time, we still maintain data integrity on a per-request basis?

jmreidy commented 10 years ago

@bgoldman I think part of the issue here is that Fluxy.start is not the best name for what this method does (in the case of server side rendering). Fluxy.start is instantiating and setting up a new Fluxy instance every time it's called. So if there's no async between Fluxy.start and renderStateToString, then there shouldn't be a concurrency problem. So basically, Fluxy.start should only be called when all the data is ready and you're rendering your response - just as you would pass data to res.render and a Handlebars template in a traditional web app, for example. See the example here: https://github.com/jmreidy/fluxy/blob/master/examples/todomvc-flux/server.js#L58

But I do think that renderStateToString could be moved to the Fluxy instance, which should solve any and all problems. I'll open a ticket to do that.