The reason is that while you may be able to break up your HTML into a composable hierarchy of purely-functional components, the actual data and state you need to distribute to them has a structure that is completely unrelated to how its laid out in the DOM. In a complex web app, a component nested twenty levels deep may need to display pieces of information, like the user's date of birth and the current temperature on Mars, that none of its parent controls cares about.
At that point, you can either bite the bullet and pass state through layer upon layer of props, completely eliminating any semblance of separation of concerns in your component layering, or inject state into your components via some other mechanism, like Redux/MobX/Context. At which point, JSX is basically just another fancy HTML templating library.
"Idiomatic" React is a fun game to play when doing basic examples, using something like a todo manager, but as soon as you're in the real world, you hit its limitations very quickly.
Having a shared store really does change the way you look at web apps - it's almost a completely different paradigm. If you can avoid re-loading the data for a view if you already have it then then opening a new view to edit a subset of the data instead of dealing with popup makes life so much easier - you get a lot more freedom with the UX and going back to the previous view with the parent data is almost zero-cost.
Sadly it seems that's mainly been cargo-culted. It's not the case that React+Redux is the equivalent to say Angular, but by some unfortunate accident due to Facebook's announcement of Flux, and then the hype around Redux as a 'better Flux', people started assuming React+Redux is what is needed to build a React app.
Meanwhile Dan Abramov, Redux creator says [1]:
> Flux/Redux is meant for big apps with complex nested UIs that have many independent parts but share common caches of data.
That's not "every semi-complex React app". In most "semi-complex React apps", most state can be derived from the route (e.g. react-router) or is relatively local. Redux is not required.
[1] https://twitter.com/dan_abramov/status/732719257579065345
Redux also offered a simple approach to the "action bus" idea from Flux, but omitted first class async support, etc. React itself also somewhat overlooked async patterns, which created the idea that their absence was by design and so an additional (opinionated) library was appropriate.
In sum I'd argue that React actually fights against js quite a bit and that it will truly come into its own once reason gets more mindshare.
For those of us who invested heavily in react as all this was becoming clear (and being ironed out) I hope we learned enough to allow better decision making in the future.
Also, React has a few "on by default" performance enhancements that are a bit conceptually confusing and create something of a mismatch with the functional approach.
But still it's difficult (and unpleasant) to imagine the world without React.
As developers we can't all be building tools. We choose tools that others build for us, so that we can get our job done. Soemtimes I'm interested in going "one layer down" and build tools, but it's almost always a distraction; we want off-the-shelf products like React and Redux. We need to pick stable tools that promise some sort of longevity. We also need to pick tools where best practices and idioms have been established.
To do this we have to trust someone else's opinion and try to gauge which solutions have the best prospects. This means not just evaluating software based on its own technological merits, but also listening to what "the community"/"the industry" seems to be rallying around; you don't want to be stuck using a library that nobody is maintaining, just as you don't want to be stuck using something that's just badly designed.
When we started using React, there was no state management framework available, and everything was handled somewhat ad-hoc. Then Facebook described Flux, and we realized they were onto something, so we started using their patterns, with some simplifications where theirs seemed overly complicated. Then Redux came along and seemed like a better way to do things.
At each point there was uncertainty about what best practices and "idiomatic" solutions would turn out to be, because the technology was immature. I don't think anyone was cargo-culting. It was just that a lot of people were busy trying to create things, and when there isn't clear guidance, and the alternative is going back to Backbone or jQuery or something, then you just forge ahead, trying to do fit the pieces together.
Can you really look at where the community is, the perception that still exists that React+Redux is necessary, projects like redux-form (that now admit Redux was a poor fit for forms) and honestly say no cargo-culting happened?
Sympathy for 'attempting to follow the mainstream' though, I understand that. Personally I stuck with Backbone for years longer than most, avoiding the Angular/Ember wars, and the early days of React+Flux til the React ecosystem had matured a little, before jumping over to React at which point it was clearer that Flux/Redux were being misused.
I never got the impression, even from the start, that setState() was a good solution, by the way. But it's not like the React authors were completely sure of where the right design was, either. Flux, for example, as it was originally described, appears completely overdesigned at this point. The good design is to be found in Relay and GraphQL.
It's not really about blaming bloggers.
It's really on software team leads and senior engineers to not just follow blog posts blindly. They should be pushing back on bad advice like redux-all-the-things, because to a senior software engineer the tradeoffs of something like Redux should be fairly obvious.
> The good design is to be found in Relay and GraphQL.
Nah again, good example to question what you read/hear.
GraphQL is great from a front-end point of view but complex to implement in the backend, so it really depends on your use-case whether that backend complexity is worthwhile.
The claim isn't that there's no cargo cult around the React ecosystem — but not everyone writing React code is approaching it with experience, which means they try to pick up best practices from other people. That's not "cargo culting"; it's a reasonable thing to do when you have to solve a problem you don't know how to solve!
Ultimately, people aren't being "tricked" into using these tools. They proliferate because they solve real problems people have, and a lot of people would rather work with a popular and well-documented solution (with known advantages and drawbacks) than trying to forge their own path and seeing how it goes.
> Also, everyone high up in the React community, including both inventors of Redux [1], say that Redux is not the best place to keep form data. Oops.
[1] https://github.com/reactjs/redux/issues/1287#issuecomment-17...
An isolated login form? Yeah, no point keeping that in Redux - it's not being shared, it's not being persisted, there's no real need to track the history of changes. But, there are certainly situations where you might want to keep some form data in Redux.
As a specific example from my own app: we show some polylines on a 3D globe, and allow the user to edit them. We need to be able to live-update the point locations and labels on the globe as the user edits them, allow resetting edits, and still be able to show the original values elsewhere in the UI. Keeping all that data in Redux makes it easy to do so.
The Redux FAQ has a list of suggested rules of thumb for deciding what state should go in Redux: https://redux.js.org/faq/organizing-state#do-i-have-to-put-a... .
Now, the other app my team works on is built with Backbone/Marionette. We've introduced React and Redux to it, and are progressively refactoring over time. It works, although we've got some rather ugly code in several places (some data is in Backbone collections, other data is in Redux, and occasionally some bits of data are mirrored in both places).
I wrote about some of the techniques we're using to integrate them here: https://blog.isquaredsoftware.com/2017/07/react-redux-backbo...
I also spent just 1 hour completing multiple RFCs in another app built with create-react-app using redux/connect as a purely syntactic-sugar state propagation layer.
I love the complexity and feeling of fulfillment for triumphing in the former scenario, but it is definitely much more of a distraction than anything else.
IIRC there were a lot of comments about having to pass props to children constantly (without using a state management library). Seems like it's a lot easier to not use a state management library now that an official, non-experimental Context API exists. But remember, this was only added a few years later.
> At which point, JSX is basically just another fancy HTML templating library.
JSX is optional syntax sugar; my team uses React and Redux heavily, but we've opted not to use it.
HTML templating is great actually, all the rendering is data -> UI.
If you go back to the early Rethinking Best Practices presentation[0], it boils down to asking why client side development isn't like like HTML templating. The answer is that updating the entire page DOM on every change is unworkable, there needs to be support for partial updates. That's what React offers, a return to the data -> UI paradigm, without having to write a secondary code path for partial updates.
dom.div({ props }, ...)
For our own components, we export the output of React.createFactory https://reactjs.org/docs/react-api.html#createfactory rather that the component itself, so rendering code looks like: MyComponent({ props }, ...)
I realise that both of those are marked legacy, with a nudge towards using JSX instead, but they are pretty thin wrappers around createElement so it seems like a small dependency.Isn't a good chunk of AirBnB, Facebook, Instagram, Netflix, The New York Times and Dropbox written in React?
The built-in state management has limitations, of course. I see it best for keeping state relevant to display-- React is supposed to be a "view" library, not a state management one.
The vast majority of our codebase is comprised of simple idiomatic React components. Our reducers and glue code (e.g. @connected containers) are a very small portion of the codebase. If I woke up tomorrow and decided Redux was the source of all our problems I could completely remove it while only needing to refactor a small portion (<5%) of our codebase that's mostly boilerplate anyways.
One thing to realize is that you always get to decide on the granularity of your compositions. It is not necessary to build a deep hierarchy if you design the granularity of your interfaces correctly. Another great tool is higher order functional programming, such as passing components to components (via children). In this way, it is easy to describe generic interfaces and supply specific pieces of data without having the intermediary interfaces know or care about that data. Another approach, akin to using something like the state monad, is to use Reacts context feature, but this is rarely needed, in my experience.
Personally I make heavy use of higher order components, and I find I rarely exceed a component tree height of three.
Examples of HOC are the connect() call from Redux, or the withNavigation() from React Navigation, or injectIntl() from React-Intl. These respectively add Redux functionality, navigation functionality, and internationalization functionality to your component.
I think the name "Higher Order Component", like many things in the React world, is poorly chosen and actually obscures what it is, how it works, and makes it look more intimidating than it really is. It's technically correct (like 'reducer' is), but communicates nothing.
It's effectively a replacement for the old React mixin system.
It happens to use functional programming methods, but that's an implementation detail.
Right now with a few devs it is going very well but I do worry about on-boarding people who are not proficient in functional programming.
React code without a global state management library is very pleasant and easy to read/understand for many, but for some it is not always obvious to write. You may have to spend more time code reviewing and training newcomers who will sometimes feel like their task is impossible without Redux. I have yet to see if those requirements can still hold for a big team but I think it is possible.
My current opinion on Redux is that it is best suited for existing project migrating to React because you may inherit very odd relationships between the view and the data and Redux will avoid you a lot of pain on this regard. A new project should avoid Redux & co as long as it can.
That's my experience as well. If you use a deep component hierarchy you have bigger problems than those solved by Redux, VueX or the Context API.
Higher order components or even simple functional components are really underrated when it comes to making your application simpler to reason about and to maintain.
I understand that such an open-ended product is frustrating to use because redux is insanely overengineered, mobx reintroduces data binding, and context is buggy and terrible, but I don't think what you're calling "idiomatic React" is actually considered idiomatic by anyone, including the core development team.
Interested in hearing what you think, though. My Redux experience has been more limited by trying to avoid it on small projects.
First, your points about `dispatch` and `context` are specifically about the React-Redux bindings, not the Redux core itself. React-Redux is specifically intended to act as an abstraction layer so that your own components are "unaware" of Redux, which keeps them more reusable and more testable. It also saves you from needing to write store subscription handling every time you want to make use of data from the store. My "Redux Fundamentals" workshop slides [0] show examples of what it would look like to hand-write store subscription code all throughout your UI, and it would be a pain.
If you _really_ want to, there's nothing stopping you from importing the store globally across your application and using it, but that misses out on the benefits of React-Redux, and also ties you to that one specific store instance.
Second, you can absolutely do async logic without any middleware. However, one of the key design points of Redux was to allow users to choose which approach they want to use to handle async logic, without limiting users to whatever was built in to the core. I discussed this in my "Redux Ecosystem" talk at ReactBoston last year [1].
You can certainly do async logic without any special middleware - just `connect()(MyComponent)`, throw in a `setTimeout`, and call `this.props.dispatch()`. The point of `redux-thunk`, the most common async middleware, is that it provides a generic way to move async logic outside of your UI and make it reusable [2].
Finally, the "dispatching an action === calling a function" comparison can be true, but only if you're using Redux with a limited mental approach and treating actions like "setters". If you start thinking about actions as more of an "event that occurred", then that leads to having multiple parts of your reducer logic independently respond to the same action and update their own pieces of state (which is an encouraged usage pattern). Justin Falcone had some good thoughts on this a while back [3], and I talked about this some in a fewmore of my blog posts [4] [5] [6].
[0] https://blog.isquaredsoftware.com/2018/06/redux-fundamentals...
[1] https://blog.isquaredsoftware.com/2017/09/presentation-might...
[2] https://blog.isquaredsoftware.com/presentations/workshops/re...
[3] https://medium.freecodecamp.org/whats-so-great-about-redux-a...
[4] https://blog.isquaredsoftware.com/2017/01/idiomatic-redux-th...
[5] https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-ta...
[6] https://blog.isquaredsoftware.com/2017/01/practical-redux-pa...
However, some of the things that have been traded are default configuration that represents typical use, standardisation across the ecosystem, and brevity. I think that for a lot of use cases, these tradeoffs make React-Redux harder to use, and that as a whole they make it easier to cause significant architectural problems for a newer or less technical user.
> If you _really_ want to, there's nothing stopping you from importing the store globally
What I've taken to doing is exporting a dispatch function that calls the store's dispatch, not the store. I think this better captures the intention behind Flux than a higher-order component, without much cost, since dynamically swapping between stores doesn't seem common.
> ...one of the key design points of Redux was to allow users to choose which approach they want to use to handle async logic...
This power and choice, while allowing a lot of freedom, comes at the cost of a fragmented ecosystem. The Redux documentation page for async actions lists six different packages that can help.
> the "dispatching an action === calling a function" comparison can be true, but only if you're using Redux with a limited mental approach and treating actions like "setters"
I may not have explained that very well. Actions themselves make perfect sense from the perspective of a reducer, and clearly they must be dispatched, but they're a state management detail that the UI doesn't need to know about. Something similar to redux-action could be the default.
I suppose my core problem with Redux/React-Redux is that I don't understand why a typical component in a typical app doesn't look like
import { connect } from 'redux'
import { deleteFoo } from '../actions'
const FooList = ({ foos }) => {
return foos.map(foo => <button onClick={() => deleteFoo(foo.id)}>Delete {foo.name}</button>)
}
export default connect(state => ({ foos: state.foos }))(FooList)> What I've taken to doing is exporting a dispatch function that calls the store's dispatch, not the store.
Not sure I follow that train of thought - could you point to an example?
> Actions themselves make perfect sense from the perspective of a reducer, and clearly they must be dispatched, but they're a state management detail that the UI doesn't need to know about. Something similar to redux-action could be the default.
This is why we recommend use of action creators, so that a component is just calling `this.props.doSomething()` without it "knowing" that a Redux action is actually being dispatched.
Per your snippet there at the end:
- `redux` and `react-redux` are deliberately separate packages, because the Redux core is 100% vanilla JS, and independent from React (in the same way that `react` and `react-dom` are separate packages - the core React logic is independent of the platform-specific reconcilers). So, `import {connect} from "redux"` wouldn't be appropriate.
- The use of `deleteFoo()` that way assumes that it's pre-bound to a specific store instance, which limits the reusability of your logic, and also makes it a lot harder to test. `connect` abstracts away the question of "which store instance am I interacting with?", so that your components and logic are more testable and reusable.
> Something similar to redux-action could be the default.
For what it's worth, earlier this year I threw together a small `redux-starter-kit` package: https://github.com/markerikson/redux-starter-kit . The goal behind it is to simplify some of the most common pain points around Redux: store setup (including thunks and devtools), writing immutable updates in reducers, and having to install multiple packages out of the box. The biggest thing I know it's still missing at this point is indeed something akin to `redux-actions` - see my notes at https://github.com/markerikson/redux-starter-kit/issues/17 .
I haven't had time to push it forward further, but I would seriously like to get some more eyes on it, see if there's any other use cases we should be covering, and then make it an official Redux-branded package and update our docs to recommend that people use it.
Something like
const store = createStore(...)
export const dispatch = (action) => store.dispatch(action)
It removes the temptation to read directly from the store.> `redux` and `react-redux` are deliberately separate packages
Yeah, my bad, got the import wrong.
> ...limits the reusability of your logic...
When would this become a problem, in practice? Reusable actions across different react/react-native UI, or multiple apps? Switching between user accounts? How common a use case is it?
Even if this is a constraint, I'm of two minds about whether a hack in isolation is worse than more complicated action boilerplate in general.
> ...and also makes it a lot harder to test...
This can be mitigated by Jest's mocks.
> https://github.com/markerikson/redux-starter-kit
That's really good. Unfortunately, it's not the same thing as bundling it with Redux proper.
Anyway, thanks for engaging with me. I never intended to do a deep dive into Redux's design, only to sympathise with stupidcar's comments about third-party state management, but it's interesting to hear the perspective of a maintainer.
Your `dispatch` snippet could be simplified to just `const {dispatch} = store`, because the store isn't a class instance - it's a closure. However, I'm not sure why you'd feel a need to "remove the temptation to read directly from the store". In what scenario are you trying to dispatch actions (presumably from the UI), but not read any data?
A basic example of reusability is the unit testing scenario itself. Ideally, you want to minimize the amount of other app code that's getting pulled in, so you can test this one piece in isolation. A React component shouldn't "know" about a store at all - it should just be getting data and functions as props. Similarly, even async logic _should_ be testable in reasonable isolation. If you are directly importing a singleton store instance throughout the entire app, you're coupling all of your logic to that one instance.
Typical testing of store-related logic involves the `redux-mock-store` package, which is just similar enough to a real store to let code run, but records which actions have been dispatched. Similarly, if you do want to test a Redux-connected component and its pieces all together, you would normally provide a unique store or mock store instance there in the unit test - you don't want to drag in the rest of your codebase to test that one piece. (And sure, Jest has some pretty good mocking abilities, but not everyone's using Jest.) I've got some slides on standard Redux unit testing practices [0], and a large section on testing in my React/Redux links list [1].
Outside of testing, yes, reuse of React+Redux across multiple platforms is viable, as is sharing code in libraries. But, again, that only works if the store is effectively dependency-injected at runtime, and that's the point of how things like `connect()`, thunks, and sagas are set up.
I agree that a separate "starter" package isn't the same as putting that all in the core, but that's kind of the point. The starter kit depends on several other packages (`redux-thunk`, `immer`, `selectorator`, etc), while the core Redux library is tiny and standalone. The intent has always been to keep the core small, and let people build pieces on top of it. So, for this package, the specific goal is to help simplify the most common use cases and concerns I've seen, while still avoiding dictating the rest of their app design.
[0] https://blog.isquaredsoftware.com/presentations/workshops/re...
[1] https://github.com/markerikson/react-redux-links/blob/master...
It’s easy to point out frustrating things or hard problems; it’s not easy to point to a way out.
Personally I love the explicit state flow react forces, and I would hate to move to a framework where I spend time figuring out how data goes from point A to point B.
I don't understand your point that injecting state into your components (e.g. with Redux' connect function) means JSX is just another fancy HTML templating library. All you need to do is have your React component export a "dumb" version as well as a connected version. Your dumb version will still be reusable in other parts of your application, and any other application could also use your dumb component and connect it to their global state through whatever means they please.
There's your problem I think, as other sibling comments pointed out.
Redux is what you get when people try to use React as a "full-app" framework instead of what it's meant to be: a view layer.
You build a DataSource class/prototype with methods such as :
- fetch
- addChangeListener
- removeChangeListener
You extend this class for each data type :
class CommentsData extends DataSource {
// Your custom data source with your data treatment and data getters
}
And you use the Higher-Order Component pattern: https://reactjs.org/docs/higher-order-components.htmlI'm not a fan of Redux either, but I fail to see what problems this approach solves.
- It checks to see if the root Redux state is different than last time, and bails out if it's the same reference under the assumption that nothing changed
- It checks to see if the return values from your `mapState` function are shallow-equality different than last time, and again bails out if they're the same
- The handling for `mapState` and `mapDispatch` is heavily memoized to cut down on unneeded work.
If you have specific performance concerns with a Redux app, please let me know - I'd be happy to offer advice and point you to possible solutions. But, a blanket statement that "Redux is quite slow" is meaningless without context for what's happening.
We now speed things up buy removing redux and just using events with multiple stores anyone can access - we can easily tune the system and don't have to rely on magic that can never be as efficient. Plus the syntax is much cleaner.
I have exactly the same experience and implemented the same solution. An ex-colleague also did the same thing. We both worked on collaborative webapps but in different companies at the time so performance gain was very important.
I still use Redux on some projects but I definitely start the new ones without thinking about it.
Could you clarify what you mean by that?
Per both of your comments: generally speaking, your `mapState` functions _should_ run as quickly as possible. Ideally, a `mapState` function should just grab a couple values from the Redux state object and return those.
If a component needs the store data to be transformed in some way, we recommend using memoized selector functions [0] to cut down on unnecessary work.
So, in a well-written app, your `mapState` functions shouldn't be bottlenecks.
As I said, if you've got some examples of specific perf issues, I'd be happy to offer advice.
[0] https://blog.isquaredsoftware.com/2017/12/idiomatic-redux-us...
A call to dispatch is significantly more expensive than the update of a JS object
> Ideally, a `mapState` function should just grab a couple values from the Redux state object and return those.
Ideally it should, but in practice it is really much more expensive than getting a value in a JS object
> If a component needs the store data to be transformed in some way, we recommend using memoized selector functions [0] to cut down on unnecessary work.
I used Reselect before removing Redux. Beyond the fact that it adds a lot of boilerplate it does not help if you do need a lot of data updates.
> So, in a well-written app, your `mapState` functions shouldn't be bottlenecks.
It shouldn't but a few dozen of updates per second through Redux on a mobile take a toll on its performance serious enough to not be able to keep 60fps in a WebGL context.
I decided to remove Redux after noticing the performance overhead per function call in the Chrome performance profiler, so I know that in practice "dispatch" and "mapState" are not comparable to just editing or grabbing "a couple values from the Redux state"
My ex-coworker had the same issues because he needed to display a lot of items handled collaboratively with smartphones. In both of our cases we have a high rate of data updates, even if we optimize like crazy we are never going to fall under 4-5 update/second per connected user.
So yeah Redux can run well for webapps with a slow/regular pace of update but for more performance sensitive apps you do pay a Redux performance tax.
Again I still use Redux on some projects and I like it but the limits are there. So now I don't hesitate to do without it first and to use it when I need its abstraction.
A good way to test it would be to make a simple websocket server that simulate n users each sending n' fake xyz position and rotation data per second. This data updates a Redux store in a React app made with Aframe (HTML wrapper around ThreeJS). You make n cubes move and rotate along the data in the store. Compare the fps you get against the fps you get with a vanilla solution. Also check what happens on the performance tab of chrome
I am able to recognize that my use case is very specific. I used React/Redux/Reselect and Aframe/ThreeJS with sockets a year ago. Now I kept React but the rest of the stack have changed for nearly a year
If you ever do have some free time to throw together an example app that demonstrates this behavior, please feel free to link it in that issue, or ping me directly (Twitter and Reactiflux are usually best).
I mentioned it in the pull request, here it is : https://github.com/Kalkut/redux-data-frequency-benchmark
> I fail to see what problems this approach solves.
You are absolutely right I gave an ad-hoc Redux as an example of Higher-Order Component.
Being OOP or not is only a matter of implementation of your data source. Here you have absolute control on it and depending on the type of your data, the frequency at which it is updated or its (a)synchronicity, you may prefer/need something very different from a dispatcher/action/reducer system.
The real pattern here is the HCO and yes Redux also use this pattern to build "smart components" with "dumb" ones. But it does it in a very opinionated way.
As an example : for performance reasons you really don't want Redux to handle real-time data with very frequent updates, it is better to manage this kind of data your own way.
A more common case is having only a few amount of data that have to be shared across the tree (things like an user session or the chosen language). If your component tree has too many level of depth an ad-hoc solution can do fine here.
IMHO there is two type of arguments for why Redux might not be needed :
1) A reasonable UI respects separation of concerns.
There should be limited need for data exchanges or interactions between two unrelated components. Making the data flow from the local state of a "smart component" is fine most of the time.
2) What Redux does can be done with less boilerplate when and where it is needed.
You can make your component "smart" in a lot of ways. It can be done with pure React state management, it can be done with custom data sources and HCO, it can be done with JavaScript CustomEvents and so on.
The worst problem here is that because Redux is a (very good) opinionated abstraction, lots of developers forget it is a library and not a framework so they try to find what they think is the most Redux-way of doing things. I've seen tons of projects that ended up being a mess of Redux/Thunk/Reselect plus whatever middlewares where updating a single property implies at least handling it in the reducer, adding a selector, and adding the result in mapStateToProps. Redux is definitely not a bad solution but in becoming a standard one it hurt a lot of projects.
This situation is very well explained by pcstl above when he says
> People mainly use Redux because they don't understand that React isn't Angular.
> Redux is what you get when people try to use React as a "full-app" framework instead of what it's meant to be: a view layer.
Is Redux not exactly that?
Your application's state should live outside of React, and be passed in as the root's props.
Redux should have your application state, and any UI state should be in pure React (portals, context, etc).
Redux -> app data React -> UI
Ironically, Redux came out around the time Angular popped. While not everyone, a significant portion of early adopters came in because they were rejecting the Angular 1.X way of handling state, and Redux was the polar opposite.
It took me many years to realize this. I think very few front end developers understand this. And more problematically: very few framework devs seem to understand it.
Stated another way....
Your tree of UI widgets, and
Your tree of data which feeds those widgets
... are two orthogonal trees.
If you are a front end dev and you haven’t spend time wrestling with that, I strongly recommend it. It’s extremely likely your front end architecture is suffering because of it.
React take the strongest stance on this matter, by making the interface to every widget be one giant shared global. But globals are bad, so while it’s good they acknowledge the situation, they mostly punt on the question of how to deal with it.
>Your tree of data which feeds those widgets
Not with a well-designed XML format and XSL-T! But hmm, nobody seems to use that anymore either.
You either really got it and then could do wonderful things, or you sorta muddled along with it and it was ok but it was like they were wearing a straitjacket.
I made a pivot table control out of it, with almost all the logic inside the XLST, it was amazingly fast compared to IE6's javascript at the time. I remember that Blizzard used it for the WoW online character viewer which was streets ahead of what anyone else was doing.
Also there's a DailyWTF about someone doing this on a large app, which brought the developer out of the woodwork to defend it who said in fact it wasn't a WTF, it was deliberate, it worked, loads of people loved it. Which prompted a huge debate about how it was actually kinda good, but just too high a learning curve (which is the death knell of many a tech).
Personally I don't miss it. The best way I can sum up XML/XLST up is that it made me feel clever, and now I know that when I start feeling clever about my code that's a sure sign I'm making something that's going to be a nightmare to maintain.
Oh well, at least Web Components seem to finally be around the corner, a decade later.
https://thedailywtf.com/articles/Sketchy-Skecherscom
The featured comment at the bottom is from the original devs.
One of the early comments is that it only worked properly on Firefox, which is wrong. IE6+ had excellent, extremely fast XSLT support.
The company I worked for at the time actually had an enterprise app that didn't work properly on FF as it relied on some IE only functionality, some sort of VBScript controls you could embed in IE that I forget the name. They dropped support for them in IE8 or 9. The app was otherwise excellent, basically a sort of dropbox + basecamp for the construction industry before dropbox + basecamp were even made, users loved it. The founding developer was the best developer I have ever known (his quirk was that he'd often forget to rename his functions after prototyping something so a lot of his methods were called Something or Thing or Something2 and we'd have to rename them later to make it clear what they did).
We had a SQL helper that would allow you to write XPath in the SELECT names something like this (I have totally forgotten xpath by now, but hopefully you get the jist):
SELECT c.CompanyId [@companyId], c.Name [@companyName],
o.OrderId [/orders/order/@orderId], o.Name [/orders/order/@name], o.Quantity [/orders/order/quantity]
FROM Orders o
JOIN Company c ON o.CompanyId = c.CompanyId
ORDER BY c.CompanyId
And it would spit out appropriately nested and structured XML like: <companies>
<company companyId="1" name="thing">
<orders>
<order orderId="142" orderName="Sally's Order">
<quantity>1</quantity>
</order>
<order orderId="156" orderName="Sally's Other Order">
<quantity>1</quantity>
</order>
</orders>
</company>
<company companyId="2" name="different company">
..etc
</company>
</companies>
It was actually pretty nifty, you only got exactly the data you needed for the page, but meant often our business logic was in SQL statements. It did make me realise how infrequently you actually re-use specific data queries and a lot of people over-egg how much data re-use there is or logic sharing that is actually required in a real-world app.Because you had the XSLT on the page, you could make it dynamic, just post back for the new XML on a drop-down change, button-click, etc.
Another story from there is that they made their own version of jquery just before jquery rose to prominence. It was a nightmare to debug, that fully got me onboard the jquery train.
however it's also really too trivial and really straightforward and boring as a piece of code to be mentioned as a WTF, having difficulty understanding that code would just be a case of not understanding the language, which is also a thing that happens.
The context of that article was more that doing XML/XSLT at all was a WTF.
Agreed. Possibly a microsymptom of the "business only does boring things, developers do interesting things" mindset.
In my experience, one needs to constantly guard against thinking anyone else is mindlessly turning a crank. In fact, they're likely doing just as complex and interesting stuff as you, only with different tools.
Same. Long ago I replaced an expensive Crystal Report solution with reports delivered as XML/XSLTs that used IE's feature at the time of applying the XSLT at the browser. It worked very well and provided nice interactive reports. I also wrote a bunch of cookbooks (for lack of a better term) that allowed the business analysts to create additional reports.
Pierce? Is that you?
Because React = state to DOM while Redux = how to get from previous state + action to a next state? :)
They do different things.
So e.g. if you want to have a Leaflet map i.e. `this.map = L.map(...);`, Vue will automatically add reactivity to this property. Sometimes I had big problems with this behavior (or it was simply annoying), so I wrote vue-static based on ideas of the core developers:
https://github.com/samuelantonioli/vue-static
Now I can have big objects in the Vue component which don't get automatically tracked using the reactivity system.
- - -
A problem for many beginners:
Another problem with the reactivity system is that sometimes you have a route like `/employees/:id` and edit an object. But on abort, you want to rollback. One thing you always have to do is to copy the object you want to edit (I use lodash's `_. cloneDeep(obj)` for this). If you use Vuex, it's not such a big deal because it works using immutable state, but when you work with a plain javascript object this will definitely generate some headaches.
- - -
But otherwise, I think Vue is definitely the way to go. `v-model` can be seen as syntactic sugar while you have to add it to react inputs and textareas manually (using onchange and setState).
You can write stateless Vue code just fine and use JSX. On the other hand, React misses some of the cool features like the Vue component files (.vue files) and its templating language. And the best is: The framework doesn't say that you have to use it. It's not so opinionated but still feature-complete with the official plugins.
React is just JavaScript and HTML.
I personally find Vue templating syntax much easier to follow than JSX, but that's because I'm used to it. I honestly believe that's how React users feel about JSX too.
This is patently not so.
Vue has:
- three different html-like attributes
- three different scripting languages in templates (two Javascript-like, one Javascript, but only expressions)
- one scripting language for controller/model which actually breaks JS assumptions (about what `this` is, for example) by magiacally hoisting some (but not all) properties of an object
This is patently not so.
Maybe if you are used to JSX. - three different html-like attributes
: and @ are just shorthand for v-bind and v-on attributes.v-bind and v-on (and v-if) accept regular javascript, and it's not just expressions... you can use `active = true` to change a data value, for instance.
The only exception is v-for, as you mentioned, but it is close enough to ES6 for syntax to make sense, at least IMO.
- three different scripting languages in templates (two Javascript-like, one Javascript, but only expressions)
This is splitting hairs a bit, isn't it? Apart from v-for, you can write pretty much anything you want in the other two. - one scripting language for controller/model which actually breaks JS assumptions (about what `this` is, for example) by magiacally hoisting some (but not all) properties of an object
You mean the script part of components? Do you have any examples of that?> accept regular javascript... The only exception is... but it is close enough to ES6
So you basically validated all I've said. And it's also incomplete. Because the amount of gotchas and things that you have to constantly keep in mind in Vue is mind-boggling compared to JSX. Because JSX is a paper-thin wrapper on top of React.createComponent, and goes out of its way to keep everything in Javascript. Unlike Vue.
Lets see your defence:
- : and @ as shorthands
It means new syntax that you have to learn, and know differences between. For all intents and purposes Vue has three different types html-like attributes. JSX has only one: javascript names.
- v-bind etc. accepting regular Javascript
Let's see how this is false:
<label @click="edit(todo)">
This is not Javascript. If it was, it would assign the result of the function call to @click. It doesn't. It's a Javascript-like scripting language that has magical binding into regular Javascript.- exception is v-for, as you mentioned, but it is close enough to ES6 for syntax to make sense
So, it's not Javascript (unlike some other places where it is Javascript), and it's not ES6 syntax, but it's "close enough to ES6". So it's not. It's a different Javascript-like scripting language.
The only place where Vue allows regular unadulterated Javascript is inside curly braces: {{}}. And there it only allows expressions (Note: JSX also only allows expressions inside curly braces).
However. Even there it's not Javascript. Not really:
{{ record.commit.message | truncate }}
Valid Vue. Invalid Javascript.- You mean the script part of components? Do you have any examples of that?
Of course. See inline comments. Example from here: https://vuejs.org/v2/examples/tree-view.html
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
data: function () {
return {
open: false
}
},
computed: {
isFolder: function () {
// `this.model` magically hoisted into `this`
// from `object.props.model`
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
// `this.isFolder` magically hoisted into `this` from
// `object.computed.isFolder`
if (this.isFolder) {
// `this.open` magically hoisted into `this` from
// `object.data` which is a function that
// returns an object whose keys and values are hoisted
// into `this`
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
// this.open can be set directly. Magic
// this.model.children cannot. Not magic
Vue.set(this.model, 'children', [])
// `this.addChild` magically hoisted into `this` from
// `object.methods.addChild`
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'new stuff'
})
}
}
})JSX is actually just JavaScript with this one special rule:
<foo bar={baz}>child1 {child2}</foo>
becomes transform("foo", { bar: baz, children: ["child1 ", child2] })
That's it. All the rules of JSX can be inferred from that one transformation. <label @click="edit(todo)">
> This is not Javascript. If it was, it would assign the result of the function call to @click. It doesn't. It's a Javascript-like scripting language that has magical binding into regular Javascript.You're wrong. The contents of the event handler is JS, the whole snippet is obviously HTML.
<button onclick="alert('Hi')">Say Hi.</button>
This is also plain regular HTML and has been since HTML4 (1997). The value of the onclick attribute is a string of JS. There's no assignment of the result happening. It registers a click handler. The only difference between the two, on a surface level, is that the Vue example has things in scope (edit and todo) that aren't globals.> This is also plain regular HTML and has been since HTML4 (1997).
What below is plain regular HTML? Taken from here: https://vuejs.org/v2/examples/tree-view.html
<div
:class="{bold: isFolder}" <--- binding to JS-like objects doesn't exist in HTML
@click="toggle" <--- If it was HTML it would've been toggle()
@dblclick="changeType"> <--- If it was HTML it would've been changeType()
{{ model.name }} <--- etc
<span v-if="isFolder">[{{ open ? '-' : '+' }}]</span>
</div>
Oh. Ooops. None of it. It's a custom HTML-like DSL with three types of magic attributes (magically bound to some Javasdcript code) with three different scripting languages in it.For reference: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Ev... etc.
Edit
Let's add more "plain old HTML". Plain old HTML v-ifs that accept JS booleans. Ah, plain old HTML v-fors that accept a ES6-like (but not truly ES6) mini-DSL. And other plain old HTML attributes.
<ul v-show="open" v-if="isFolder">
<item
class="item"
v-for="(model, index) in model.children"
:key="index"
:model="model">
</item>
<li class="add" @click="addChild">+</li>
</ul>> <label @click="edit(todo)"> > This is not Javascript. If it was, it would assign the result of the function call to @click. It doesn't. It's a Javascript-like scripting language that has magical binding into regular Javascript.
Great, so it's not javascript.
To a lot of folks it looks a lot nicer than
<label onClick={() => this.edit(this.state.todo)}>
There's a lot less noise, and it's _much_ friendlier for (most) designers.
I have never felt that I had to keep hundreds of gotchas in my mind while using vue. The syntax is extremely intuitive (unlike angular). Takes maybe one hour to read through the docs, and you have it.
Have I ever mentioned apollo or graphql tools? Let me check: no
> There's a lot less noise, and it's _much_ friendlier for (most) designers.
I love how you could find exactly one thing out of a whole list of things that may be just ever so slightly better than JSX.
And of course, it's not better than JSX for very obvious reason that you chose to ignore: how { } in JSX accepts plain JS with the only exception that it has to be an expression. And (and it's the most important part): curly braces in JSX mean and accept the same thing everywhere in JSX.
In Vue though: What do @ attributes accept? What do : attributes accept? What do curly braces accept? etc.
> I love how you could find exactly one thing out of a whole list of things that may be just ever so slightly better than JSX.
It's not a question of "better" or "worse". It's a question of tradeoffs on spectrum of abstraction.
I'm not here to bash JSX or react. But you apparently are, and I'm just trying to open your eyes a bit.
When vue came out with JSX, I tried to force it on my team, and there are a serious backlash, to the point where I came to realization that the vue's major strength is actually the templates, and the magic.
You may not like the magic, but a lot of folks do.
As for your specific questions
@ and : are just short hands. That's like saying: what's the difference between `map` vs `reduce` in my component to iterate over an array. They _can_ do the same thing, but map is shorter. In plain JS I can do `i = i + i` `i += i` or `i++` to increment an variable.
@ just means "event handler". @click = "onClick" : just means "use a dynamic value for this prop { } you can put any JS expression, with the plus that you don't have to litter your code with `this`.
Yes. But just because it has that potential, doesn't mean that Vue's DSL(s) are not without the many problems anf gotchas I described. Just because Apollo's/GraphQL's DSL is potentially good, doesn't mean that Vue's is. etc.
> It's not a question of "better" or "worse". It's a question of tradeoffs on spectrum of abstraction. > I'm not here to bash JSX or react. But you apparently are, and I'm just trying to open your eyes a bit.
My eyes are wide open. Where in JSX I see a uniform approach that works in all JSX contexts, in Vue I see several DSLs bound together by magic of the library.
> @ and : are just short hands. That's like saying: what's the difference between `map` vs `reduce`
Shorthands mean:
- new syntax. For all intents and purposes it's a new syntax (call it a DSL syntax). And you have to know the difference and gotchas of that syntax. You have to know where you can:
-- provide a thing that looks like a function ref
-- provide a thing that looks like a function call
-- provide a thing that looks like a bound variable
-- provide a thing that looks like a Javascript expression
etc. etc. etc.
And that is further compounded by the fact that `v-` attributes sometimes take single Javascript-like expressions, sometimes not (v-for), some have additional "arguments". some have additional modifiers etc. etc.
And of course, some of it is Javascript expressions inside strings (`v-` attributes), some of it is Javascript-like (but not really Javascript) DSL inside mustache's curly braces (inside tags).
So here's the original statement that started this thread: "The amount of special cases in Vue templating syntax is comparable to the special cases and gotchas of JSX"
This is demonstrably provably patently not so, as this thread has demonstrated.
> with the plus that you don't have to litter your code with `this`.
"Littering code with this" is exactly how plain old vanilla JS works. Besides, "littering your code with this" very much depends on where your callbacks and variables come from... as in any old plain vanilla Javascript code. Because JSX is a paper-thin DSL on top of vanilla JS. Brilliantly and succintly described in this comment: https://news.ycombinator.com/item?id=17474370
In Vue you don't litter your code with this. You litter your code with hoisted variables and functions that are arbitarily hoisted into scope and injected everywhere. See my comment here: https://news.ycombinator.com/item?id=17471199 starting at "Of course. See inline comments."
The amount of gotchas and special syntaxes, and special cases, and the amount of moving part you have to understand to see where things come from and go to in Vue is insurmountably larger than in JSX.
At my last company we used `react-hyperscript` which was a simple wrapper around `react.createElement`.
Ultimately, the React developers made the mistake of making `createElement` so annoying to work with, I assume because they bought into JSX. Granted, JSX might have been necessary when React was first created in order to ease people into transitioning to HTML in JS, but I feel like we can move passed that now.
I highly recommend `react-hyperscript`: https://github.com/mlmorg/react-hyperscript
A translation from the JSX example that was the first (only?) example on the React homepage at the time:
https://github.com/jasom/parenscriptx/blob/master/example/co...
Furthermore, Elixir has Agent, a higher abstraction on top of Erlang/OTP gen_server that's easier to grok and use in many simple cases with no need to separate the public interface and OTP callbacks.
I think this is what the Flux model is all about, making UI code actually be a pure function of state. By that, I mean literally a function, where state is the argument, and where all you render depends solely on said argument.
If you want to change what is rendered, you send a message and a Store deals with the state modification. Once it's done, it calls your pure function again, this time with a different state, so you get a different render.
This IMO, decouples state and and view instead of tightly coupling it. Redux is the best Flux implementation to demonstrate this IMO because it puts all state in a single Store. The messages to this Store can come from anywhere: UI, server, me typing on the console, etc. The state changes independently of the UI and in fact I could have a full application without having a UI whatsoever. Or I could have a UI without knowledge of my state's topography, because Redux lets me morph and cherrypick my state before I pass it to a component as props.
I think that's a good exercise: to try and build an application thinking of it as a series of actions on state first --and then how that may play onto UI-- to get to appreciate functional components. I'm curious to know why you think why you think Redux only "pretends" they aren't though. (:
Also, conceptually, you often have a lot of state that's very directly concerned with and specific to the UI, and isn't just idyllic, agnostic "data". Things like "is this menu open or closed?" and "what's the current string value of this input (which isn't meaningful until the form is submitted)?" In Flux, those things tend to add a ton of boilerplate in terms of change actions, and split tightly-related concerns into separate corners of the codebase (if I remove this menu component I have to remove its open/closed state variable and the corresponding action).
To be sure, there's not really a great way to decouple things in the reactive architecture. I personally lean towards stateful components that are decoupled at their boundaries instead, which does carry its own downsides. The only solution I've ever seen that truly felt right was two-way databinding, which Vue (sort of) still has, but which has fallen out of fashion in general because it requires "magic" and because it tends to come with performance costs.
https://redux.js.org/faq/organizing-state#do-i-have-to-put-a...
The entire UI is specific to the UI. Trying to separate "entities" or "server data" from "UI concerns" is IMO a waste of time and just confusing.
At the end of the day, you have a state tree, and you project it to a UI. Where that data comes from and what it represents doesn't really matter. Events in the UI generate an log that can get folded into new state to then be projected again. These events could be generated by a button being clicked, or an XHR request completing, but that doesn't change the nature of the payload.
One important thing though, in most of these models, the state shouldn't be "is this menu open or closed". It should be "Is the user focused on an arbitrary task or not". The menu being open or closed is a projection of that. This way when your designer comes and says "No, this thing shouldn't be a dropdown menu anymore, it should be a modal", you don't feel the need to rename your arbitraryMenuOpen: true state key to arbitraryModalOpen: true instead. Saves a lot of time.
Elm is a pretty good example of how all of this works. Its fairly different from Redux and co (even though it inspired it a lot), but its interesting to see how all of these things absolutely can work. They get messy when retrofit into React and JavaScript, but it's not an issue with the concept, just the implementation.
In fact, the reason we have the particular toolbox of standard GUI elements that we have these days is precisely because they serve different use-cases. Each serves a particular one, and the reason it's survived is because it isn't made redundant by any of the others.
The only thing not practical about it is that in these days and age, the amount of people with data modeling experience is low. But it totally does work in practice and is, IMO, the only way to really be productive with Redux. Otherwise any change you make or any new feature requires modifying components, actions, AND reducers. At that point you're just doing 3 times the work for everything.
Wait, they don't though. That's the whole point of functional components. They are of the form:
const Component = (props) => <JSX />
Where are said "literally passed" actions here?> So there's bi-directional coupling between the UI code and the Store (the UI code renders from the state, and passes messages back to the store).
This isn't bi-directional binding. state determining UI, UI sending queued actions and actions determining state is one-way data flow. Bi-directional coupling would be tying a reference in the state to a UI element.
> Also, conceptually, you often have a lot of state that's very directly concerned with and specific to the UI, and isn't just idyllic, agnostic "data". Things like "is this menu open or closed?" and "what's the current string value of this input (which isn't meaningful until the form is submitted)?"
This is architectural, and is why components have their own state. Its up to you wether you want to keep this as application state or local volatile state.
> In Flux, those things tend to add a ton of boilerplate in terms of change actions, and split tightly-related concerns into separate corners of the codebase (if I remove this menu component I have to remove its open/closed state variable and the corresponding action).
Which is why I don't put it in the main application state (:
> To be sure, there's not really a great way to decouple things in the reactive architecture.
I mean, not if you're dumping everything on your application state, no.
> I personally lean towards stateful components that are decoupled at their boundaries instead, which does carry its own downsides.
Curious to learn what you mean by "decoupled at their boundaries". You mean components that are de-coupled from each other? You can make re-usable, functional or stateful components that are de-coupled from application state.
I think the main takeaway here is that not all state is the same, there's state that's part of your core application logic and state the component needs for its display only. Treating them as the same thing does limit the re-usability of your code and make dealing with your application state tedious.
> The only solution I've ever seen that truly felt right was two-way databinding, which Vue (sort of) still has...
There's other solutions (: I don't think Netflix, Facebook or AirBnB have all the state of their menus tied up with the state of their user data and and have all components be use-once because of that. Good state management can make things scale, and functional components very useful!
> but which has fallen out of fashion in general because it requires "magic" and because it tends to come with performance costs.
Yup. It's also hard to know where or how certain state is changing. It definitely allows you to build small applications very quickly, but it doesn't scale all that well.
The author is wrong. Vue doesn't have two-way binding. It has one-way binding with events. Components can't force anything upon their parent's state with an event.
> "You can use the v-model directive to create two-way data bindings on form input and textarea elements. It automatically picks the correct way to update the element based on the input type. Although a bit magical, v-model is essentially syntax sugar for updating data on user input events, plus special care for some edge cases."
I'm not knowledgeable enough on the matter to say if this is or isn't, and to compare vs Angular, etc. But don't blame the author for this one ¯\_(ツ)_/¯
<Component v-model="foo">
is basically shorthand for <Component :value="foo" @input="foo=$event">
In particular, the child can't affect the value of the parent's "foo" attribute except by sending an event. This is in contrast to Angular 1 where changing "foo" in the child would also change it in the parent, which would often lead to confusing data flow cycles. All props form a one-way-down binding between
the child property and the parent one: when the
parent property updates, it will flow down to
the child, but not the other way around. This
prevents child components from accidentally
mutating the parent’s state, which can make
your app’s data flow harder to understand.
https://vuejs.org/v2/guide/components-props.html#One-Way-Dat... AngularJS uses two-way binding between scopes,
while Vue enforces a one-way data flow between
components. This makes the flow of data easier
to reason about in non-trivial applications.
https://vuejs.org/v2/guide/comparison.html#Data-bindingAs other people mentioned, Vue indeed doesn't have two-way binding, and v-model doesn't have the pitfalls that Angular had. It is just syntax sugar.
Maybe it's about time Vue people remove the mention of two-way binding from their websites, since it is being used to attack the framework. I've seen that in other discussions here in Hacker News.
https://reactjs.org/docs/components-and-props.html
The best thing to do is just to brush up on functional concepts, thinking about things in terms of composition and application, and threading state through your application and flowing back through to the top via dispatches. Learn more about flow (the concept, not the framework). I like to picture a waterfall, where the water is state that always keeps flowing no matter what it hits. State should start at the top from some source, we don't care what that is, and then flow through the application down into the leaves. Functions can do transforms and reductions on state, but they should never hold on to state the way class components do.
In the last SPA I wrote there's about 100 components, less than 5 of them are stateful.