Yes, yes, yes. At my company we've been switching everything over to React for both client/server rendering using our own ExecJS rendering engine for Ruby on Rails. It certainly adds a bunch of complexity to the stack, but every improvement we make provides new benefits in terms of the ease with which we can keep the API stack we like while cranking out rich UI while worrying much less about the client/server distinction.
Needless to say, I am a React fan.
That said, here are some things you will run into as you build more complex applications with this architecture. Some are related to shared browser/server JavaScript and some are React in general:
* You will find that some JavaScript libraries you want to use should not be loaded into the server context.
* On occasion, you will need to add server-vs-browser conditionals. And creating components to deal with browser-focused JS libraries (think Google Maps) will require some liberal and sometimes awkward use of React component lifecycle methods.
* Until there's a nice, agreed-upon open-source layering library for React out there, or you make your own, things like click-based (not hover-based, which can work with just CSS) dropdowns and non-modal dialogs are weirder to get to dismiss (when clicking outside of them) than they should be. Just because of the way React events happen and are bound.
* Also because of the way events are bound, it's awkward to write nice reusable form components. Without some trickery, you can only bind events from the component you're writing to its descendents. You can't bind events from one descendent to another. You end up writing a new component for each form every time, which isn't necessarily terrible until you want to make a fancy form builder like Rails provides in ERB. I've tried every which way around this, and I've had some success, but the solution always feels wrong and ugly no matter how I do it.
* This is really more of a good thing, but it's so enabling that you'll never really be done adding convenience features. There are just so many "Wouldn't it be nice if ____ just worked?" scenarios that get you thinking about the next reusable tool you want. I spend a LOT of time trying to create the "perfect" browser-side data store abstraction and the "perfect" lazy loader/renderer component for not-yet-fetched models.
The tradeoffs thus far have been worth it, but we haven't yet found or built the holy grail. So many things are so much easier than the old ways, but sometimes they're harder.
Do you have some example use cases for your form descendants vs. event bindings issues? I'd like to see how they could possibly map to React form libraries.
It's kind of an abstract problem to describe without code examples or without diving pretty deep into React, but let me see if I can just make something up without getting too far in the weeds. Imagine you're trying to make a ModelUpdateForm component that you can just drop in, which manages all its own state:
var ProfileUpdateForm = React.createClass({
render: function() {
return <ModelUpdateForm model={@props.user}>
// I don't actually have a reference to `form`.
// I can't have a reference to `form`.
// But I need to link state between the ModelUpdateForm and its inputs.
// Anyway, this is never going to work.
<ModelInput type="text" valueLink={form.modelValueLink("name")} />
</ModelUpdateForm>;
}
});
Weird workaround that I'm not sure is an officially sanctioned way to deal with it and fear might break in future React versions or have unintended consequences:
var ProfileUpdateForm = React.createClass({
render: function() {
return <ModelUpdateForm
model={@props.user}
do={function(form) {
// Instead of taking children, ModelUpdateForm takes a `do` function prop.
// It passes itself to `do` which give you a reference to which you can link its inputs.
return <div>
<ModelInput type="text" valueLink="form.modelValueLink("name")} />
</div>;
}}
/>;
}
});
Another idea I've been kicking around and haven't tried would be to create some sort of linker object. Here's the hypothetical end result:
var ProfileUpdateForm = React.createClass({
render: function() {
var valueLinker = new ModelUpdateForm.ValueLinker()
return <ModelUpdateForm model={@props.user} valueLinker={valueLinker}>
<ModelInput type="text" valueLink={valueLinker.linkState("name")} />
</ModelUpdateForm>;
}
});
tl;dr - The way React typically wants you to do things, ProfileUpdateForm would have to manage the state. But what I wanted to build was a reusable model-bound form component. If I have to write a lot of model-binding code every time I write a new form, then there's no point to trying to make a reusable component at all. So you start having to try weird-ish ways to get it to become feasible.
It looks like this could make use of React's context feature once #2112 [1] lands.
I have similarish form components [2] which need to do this the hacky way in the meantime (cloning [3] their children in order to pass the form prop they need all the way down):
So are you traversing the entire tree of children here? I'm familiar with cloneWithProps, but I've been wary about this technique because of how deep the components I actually want to bind could be and how much traversal it could take to find them.
Needless to say, I am a React fan.
That said, here are some things you will run into as you build more complex applications with this architecture. Some are related to shared browser/server JavaScript and some are React in general:
* You will find that some JavaScript libraries you want to use should not be loaded into the server context.
* On occasion, you will need to add server-vs-browser conditionals. And creating components to deal with browser-focused JS libraries (think Google Maps) will require some liberal and sometimes awkward use of React component lifecycle methods.
* Until there's a nice, agreed-upon open-source layering library for React out there, or you make your own, things like click-based (not hover-based, which can work with just CSS) dropdowns and non-modal dialogs are weirder to get to dismiss (when clicking outside of them) than they should be. Just because of the way React events happen and are bound.
* Also because of the way events are bound, it's awkward to write nice reusable form components. Without some trickery, you can only bind events from the component you're writing to its descendents. You can't bind events from one descendent to another. You end up writing a new component for each form every time, which isn't necessarily terrible until you want to make a fancy form builder like Rails provides in ERB. I've tried every which way around this, and I've had some success, but the solution always feels wrong and ugly no matter how I do it.
* This is really more of a good thing, but it's so enabling that you'll never really be done adding convenience features. There are just so many "Wouldn't it be nice if ____ just worked?" scenarios that get you thinking about the next reusable tool you want. I spend a LOT of time trying to create the "perfect" browser-side data store abstraction and the "perfect" lazy loader/renderer component for not-yet-fetched models.
The tradeoffs thus far have been worth it, but we haven't yet found or built the holy grail. So many things are so much easier than the old ways, but sometimes they're harder.