erikras / react-redux-universal-hot-example

A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform
MIT License
12k stars 2.5k forks source link

Practical applications #678

Open thebarndog opened 8 years ago

thebarndog commented 8 years ago

I've been parsing through and reading all the code, getting a sense for the architecture, for about a week. And after deciphering line after line, I keep coming back to one fundamental question: "What's the point of having a javascript app that can be run on both the client and the server?" Or more to the point, what's the practical application of an app like that?

I can understand the need for things like webpack to be universal, it's easy to imagine a scenario where one is building a web application with a both a user interface and a server component. In that case, having tools like webpack is incredibly useful seeing as you can change Sass styles and api routes in the same application. When it comes for the need for the application to be run both on the server and on the client, I'm at a loss for why. To me, it makes no sense to give a web application that ability, and here's why:

Everything on the client can be done by the server

When talking about isomorphic or universal apps, one is talking about an application running independent of its environment. While I do think this is a noble and worthwhile goal, in practice it doesn't make sense. If you're building a website with no server-side needs, you can simply build it on the client. If you do need the server, then you can build it on the server, render to the client, and make use of tools like webpack-isomorphic-tools.

Added complexity

There's an enormous amount of added complexity to the application. The structure blew me away (not in a good way). The staggering amount of code needed to ensure that the application could run smoothly in either environment stuck me as simply unnecessary. Why not just build a server-side rendering app? It took me a very long time to chase down every single piece of functionality and that was solely due to abstractions used to encompass client vs server functionality such as the ApiClient.

Is there something I'm missing? As an engineer, I can't ever see myself needing a javascript application that runs both via a browser and via a server since everything that can be achieved client side can be achieved server side (and more). As I said, I do agree with having certain tools be isomorphic but an entire application? The endeavor seems to be nothing more than a fun exercise.

adailey14 commented 8 years ago

I shared your opinion until recently but was convinced otherwise by some smart people who work for some big companies in the valley. The main reasons that convinced me come down to:

General benefits of a client side app:

Benefits of React/Flux data flow:

Benefits of Server Side Rendering (assuming you want the benefits of the client side app listed above, this really just solves some of the problems that client side apps introduce):

With all that said this is definitely "cutting edge" aka experimental stuff and introduces a risk to your project - so may not be worth the risk depending on what you're doing. Also Redux in particular is all about functional programming, so it will take longer to get over the learning curve if you're not comfortable with that.

thebarndog commented 8 years ago

General benefits of a client side app:

Much easier to make a dynamic UI that feels snappy. Forces a separation of view code and data storage code, which also helps separate roles on a development team. The above mentioned separation also makes it easier to build a native mobile app, since you already have an API developed.

^I totally agree, those are definitely great reasons to develop client side applications. And I do agree with the general philosophy just not the practical approach given the amount of complexity it seems to introduce. Perhaps what's throwing me is where the client comes into play in the development cycle and its subsequent role.

So if I understand correctly, the whole point of this exercise is to ease the development cycle so that one can go from client side development to server side development with relative ease right? I think what threw me before was that regardless of what's being developed here, there's always some sort of server or node instance that's running and hosting the application. Really, it's less of a universal application and more of an intermingled client-side tools/node backed application that always runs off some server instance, is that fair to say?

adailey14 commented 8 years ago

I think yes that is fair to say - though I had nothing to do with putting this all together so someone else might have a different perspective. I would say this is a web app (intended for use with a node server and browser client) with universal rendering (to achieve SEO and speedy initial page load), vs a universal application. Possibly you could package this up in something like phonegap and use it as a native app and that wouldn't involve a node server... but yeah I don't think that's the point.

Really I think the point of this project is to show how the pieces fit together around Redux. I came here after reading about many of the packages involved, but not wanting to spend weeks figuring out how to install and configure each one. And also it serves as something of a meeting point for best practices in the Redux world - which is important because Redux and its author are very cool but also very un-opinionated about how to do a lot of practical things.

thebarndog commented 8 years ago

I would say this is a web app (intended for use with a node server and browser client) with universal rendering (to achieve SEO and speedy initial page load), vs a universal application

I like that a lot, took the words right out of my brain. There's no denying it's a powerful technique/architecture. I'm going to change the issue title to be less snarky...

The guy that showed me this referred to it as the "holy grail" of web applications and I can kind of see why. If I was building a web application using Redux, React, etc without this general technique, I'd first have to develop all the UI on the client then copy-paste all my scss and whatnot to a static assets folder and from there begin server side development, which would be a pain to say the least.

I wonder under what circumstances one would change __CLIENT__ to true, would it be when working on client side development (scss, html, etc)? Because that would make much more sense to me. Based on the documentation and explanations in the README, I assumed those global flags had to do with where it was being deployed, not with what part of the development cycle a developer was in. Clarification in the docs might help.

adailey14 commented 8 years ago

From what I understand __CLIENT__ would be true when the page is rendered by the browser, while __SERVER__ would be true if the page is rendered by the server. Also __CLIENT__ appears to be true when testing. When you work on html/css you expect it to be rendered on either the client or server depending on when it is requested in the user flow.

This may be helpful (or maybe not!): The app has 2 main "entry points": src/server.js for server rendering and src/client.js for client rendering.

A typical user flow might look like this:

  1. A user directs browser to www.exampleapp.com, the server renders this page, so __SERVER__ is true. Any rendering code that refers to __SERVER__ would be true, and __CLIENT__ would be false.
  2. Then the user clicks a react-router link on the page to www.example.com/widgets. The browser handles this click, and renders the widgets page without talking to the server (but maybe requesting some json data from the api section of the server). At this point any rendering code that refers to __SERVER__ would be false, while __CLIENT__ would be true.
  3. The user clicks "refresh". Now the server renders the widgets page, so __SERVER__ would be true again.
thebarndog commented 8 years ago

That would be really neat but unfortunately I don't think that's true. I grepped through the project and __CLIENT__ is only set via prod.config.js and dev.config.js and __SERVER__ only gets set in /bin/server.js which would mean that __CLIENT__ is only set via running webpack, not something that would happen when the user visits the page, rather during development time. So it looks like, unless I'm way off, that the application running off the client and the server is a convenience for the developer. Perhaps @erikras can answer that, I might be wrong on that.

Either way, that user flow you described is a cool idea, I like the idea of it dynamically changing the rendering based on if it's the client or the browser.

adailey14 commented 8 years ago

Well I'm running the app and just did a little test, and it is all working the way I described. You will have to experiment with all of it a bit more to see how it's working. I'm not sure exactly how the config files are setting __CLIENT__ and __SERVER__, I suspect the webpack build sets them with the client side values, and then bin/server.js overrides them for the server side process. But the flow I described is definitely how it is working.

thebarndog commented 8 years ago

Probably something do with the webpack dev server instance? That seems to be doing hot reloading via the middleware.

tyao1 commented 8 years ago

@adailey14 the variables are set in webpack/dev.config.js and webpack/prod.config.js to build the client js, and the variables are set in bin/server.js for the Node environment.

mmahalwy commented 8 years ago

@xiaobuu youre right. CLIENT is set to true for all webpack compiled JS, which happens to be in this case the same files as the server uses, but it's important to tell the browser that it's a client - for example, when doing API calls on the client, you want to make calls to the node instance which proxies to the API server, and on the server you want it to call directly the API server.

Aadriya commented 7 years ago

Error

Invariant Violation: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(App)".