Redesigning Redux
medium.comThe strength of redux is that all your interactions with it are through functions. Redux today is far better than the Redux of yesteryear thanks to things like ReSelect, Thunks, Sagas, Higher Order Components etc. All these things augment Redux, and they can do so because of Redux's open embrace of the humble pure function.
Once you commit to a config based approach, you're basically closing the door to a future you can't imagine yet. I'm so tired of this short-sighted obsession with configuration some developers seem to have.
I think the author has a myopic vision for where Redux should go.
> The strength of redux is that all your interactions with it are through functions
I think it's the other way around: The greatest weakness of Redux is that (unnecessarily) message passing instead of function calls are used.
Why do a `store.dispatch({type: 'INCREMENT', value: 10})` when it could be a `store.count.increment(10)`? In basically all cases every action is handled by exactly one reducer so just let the reducer be an object with methods instead of a big switch function operating on string constants.
If the verbosity bothers you, why not write a helper function for your codebase? Action creator functions originally served this role, and you can successfully create more complex "helper" functions for generating unique action type names, sets of action creators, or even reducers if you have a consistent use-case.
IMHO you want the tools you use (i.e. redux) to be flexible and powerful, and write helpers, etc, to increase your ease-of-use. Otherwise, as the OP said, you're unnecessarily restricting yourself and creating a more difficult future.
One of the original reasons for message passing is that multiple reducers can respond to the same action. There have been many libraries since the beginning that try to shrink that relationship (ex reducks), but I never understood it, my code is replete with multiple responders.
> my code is replete with multiple responders
Thanks for your input! Do you mind giving 2-3 examples of good uses of multiple reducers for the same action?
One example is fetching data, often it is more effecient to request data in a certain way, but the app needs it represented differently, multiple reducers can respond to the same payload and take what they need.
Another example is url updates, I use state-driven-routing and when a url update happens many reducers need to respond to get the pieces of state they are concerned about.
Another example is a login status change, many reducers may need to update themselves if a user logs in our logs out.
It's the difference between having tons of highly specific 1:1 actions getting called from the action creator with their relationship to the root action obfuscated vs. having 1 action that many respond to with their relationship to that action clearly denoted.
To take it a bit further, it reminds me of refactoring vs adding another "if" statement - the former requires comprehensive understanding of the holistic purpose of the system with the dependent relationships properly defined (anti-fragile), whereas the latter is a temporary bandaid that can be implemented with less thought (and the sideeffects that come with it).
> when a url update happens many reducers need to respond
Wouldn't the components handle this change and dispatch any actions needed?
> a login status change, many reducers may need to update themselves if a user logs in our logs out.
Again, couldn't this be handled at the component level? It seems risky to have multiple sources of truth for data.
> multiple sources of truth
There wouldn't be more than a single source of a given truth, the multiple responders pull different truths relevant to them, the combination of which making the complete app state.
Middleware. A UI events logger for example. Or finishing a network request may need to close some part of tge Ui and also trigger a reload somewhere else.
I missed the part where you explained why changing the reducer to an object with methods would bring any benefits, or what's bad about message passing (it seems to me off the top of my head that changing this would at the very least, break redux observable as it currently works)
> I missed the part where you explained why changing the reducer to an object with methods would bring any benefits
Because this is how the rest of your programming works, you call functions. In Node you don't have `fs.processMessage('readFile', {path, next})` because `fs.readFile(path, next)` is simpler. Message passing is nice if you pass process boundaries or can't know about the function definition but in Redux you pull in the string constants and everything anyway. Message passing also lets you hook several handlers (reducers) to the same message but I have yet to see it significantly used, this is a niche case.
> to an object with methods would bring any benefits
It lets static typing do a lot more work for you when using Typescript/Flow. Less to remember and less to break.
Message passing is a really useful architectural choice because it lets you do powerful global operations. It's a lot crummier on the development ergonomics front.
You can already write interfaces for your Redux actions in TypeScript to get the benefits of static typing, so I'm not even sure that there would be an appreciable improvement if Redux used methods instead.
Direct function calls would at least make it easier to step through the code in the debugger, I imagine.
I feel like this benefit is more theoretical than practical owing to redux devtools, which offers a very similar experience of seeing what's been executed, perhaps even superior in some ways since you can see "everything" that's happening (with respect to the redux store), though you could probably create a similar thing without message passing
"In basically all cases every action is handled by exactly one reducer" this is patently false
While Redux is great because it enforces a single source of truth, the whole thing with "actions" and "reducers" in Redux is a huge source of boilerplate. In almost every Redux project I have worked on, developers bring in some weird scheme to reduce the boilerplate load of having to come up with a bunch of events when all that anyone wants to do is call some functions.
The fact that Redux forces you to use events to modify the store is a useless complication which has spawned reams and reams of crazy libraries like redux-thunk, redux-saga, standard action formats, etc. The craziest thing yet is Ducks: https://medium.freecodecamp.org/scaling-your-redux-app-with-.... This is a pattern which tries as hard as possible to make using events not feel like using events, by tying each event intimately to its listener.
This library is a step in the right direction, but wrapping Redux seems unnecessary. Here's a similar approach in 100 LOC: https://github.com/didierfranc/react-stateful
I share this sentiment. I always felt like Redux had the right idea (single store modified via actions), but the API was just making things unnecessarily complicated causing people to either write lots of boilerplate or come up with complicated workarounds.
Here's yet another similar approach, also in 100 LOC https://qubitproducts.github.io/tiny-atom/. Been using it for a year in a few projects.
The issues with Redux Sagas and etc. was unavoidable, because Redux didn't take an early stance on how to handle actions which weren't ready right away, something the article author calls differed actions. It's not because of the overabundance of boilerplate, but the LACK of guidance in the existing boilerplate.
> You don’t need to understand what a “comonad” is to use jQuery, and you shouldn’t necessarily need to comprehend functional composition to handle state management.
Is there a point to spreading this piece of sage FUD?
It should not be surprising that pure functions + immutable state results in fewer errors.
The "pattern" Redux asks you to use prevents you from shooting your leg off. If you've written a sizeable application that uses global, mutable state the benefits of Redux shouldn't be surprising.
My only gripe with this eco-system is that it constantly comes up with new names and concepts for things that already exist in the FP world. I agree that the math jargon is off-putting but so is having a dozen words for the same concept. I think the latter is worse.
The library described in the article feels less like an abstraction and more like a shuffling around of syntax. Useful but misleading with a title like, Redesigning Redux.
Update: s/grip/gripe
> My only gripe with this eco-system is that it constantly comes up with new names and concepts for things that already exist in the FP world.
I agree, but I think it's easy to see how it happens. It's an impediment to adoption of the project if people don't understand what you're talking about, so simplistic shorthand is used initially to get people up to speed, and then you find that your temporary names have become engraved in stone for the vast majority of your users. People being how they are, you're just as likely to get them to change editors as change terminology at that point.
Coming from no math background I LOVE functional programming and would love to learn what words I'm using have existing math terms.
A functional programming rosetta stone? Sounds challenging and interesting!
Hah. I just meant maybe a blog post that maps the terms I use in JavaScript land with math land. Ie. "it's not a reducer it's a functormonadicfoobar"
https://eloquentjavascript.net/3rd_edition/00_intro.html
Not exactly a blogpost but a nice reading, explains a lot of the functional programming terms and their mathematical analogs.
edit: s/slang/terms
Thanks for the idea. Should make a fun series of posts for my blog assuming there isn't prior art on the subject.
> Consider time_saved to represent the time you may have spent developing your own solution
Developing your own solution is one thing, and developing your own solution that's as battle tested as a popular library is another.
Baking your own solution helps you understand the core problem that a library you could have used tried to solve. But generally speaking developers that tend to do that have difficulty learning top to bottom and favor bottom up approach which leads them to developing things from scratch. That's just a personal anecdote though.
There are cases were a developer has been using a library for a while and is experienced enough to come up with better solution and cleaner APIs but I'd argue that's not the common case.
Understanding other people's code/libraries is a skill worth mastering.
As far as libraries generally go, Redux is tiny.
https://github.com/reactjs/redux/tree/master/src
The largest and most important part is CreateStore, but at 250 lines long (mainly comments), it's shockingly simple when you take a look at it.
> But generally speaking developers that tend to do that have difficulty learning top to bottom and favor bottom up approach which leads them to developing things from scratch.
Damn, that was a hard pill for me to swallow just now.
I'm afraid I'm guilty of this type of re-invention myself. Having built a very large project using Redux, I decided to develop my own alternative, not just to Redux, but to Flux itself:
https://hackernoon.com/transmission-tx-a-flux-alternative-fe...
I'm convinced it's better and simpler, but then again, I'm arguably biased.
Is Redux too complex? That's the central argument this article makes but I find it to be patently false. What drew me to redux was its simplicity: Abramov said from the beginning that it's not anything you can't code yourself, it just provides a thin framework around making changes to some base state object. Its main strength (to me) is that it forces developers to think about state changes as mutations rather than recreating a spaghetti state machine oneself.
It's too much boilerplate. In my current project, I have to describe one action 7 times:
compare that to The Elm Architecture, from which redux is inspired AFAIK. An action only needs to be referenced in 3 places:1. The constant var `const ACTION_NAME = ...` 2. The constant's value `... = Symbol('ACTION_NAME')` 3. The "action creator" function `const doAction = arg => ...` (extra creativity to add a verb here?) 4. The action creator's return struct: `... => ({ type: ACTION_NAME, payload: arg })` 5. The reducer switch clause `case ACTION NAME:` 6. The container action to callback mapping `onClickThing: dispatch(doAction(42))` 7: The jsx/component calling the callback `onClick={onClickThing}`1. The tag on the Msg type: `type Msg = ActionName arg | ...` 2. The `case` pattern matcher in the update function 3. The view sending said Msg/actionIt's just javascript... when I'm writing a redux-backed app I will often abstract out commonly repeated patterns in action creators and reducers. Every project is different so I just wait until things start looking predictable and refactor. Not trying to be snarky, I just see this a lot at work; people seem to be locked in to the patterns used in the redux documentation and never attempt to rewrite it as they would any other part of their app. It's quite simple (especially if you are using ES6 with its destructuring assignment) to write a function that takes an action type and a function body that eliminates most of the boilerplate.
Actually, I agree with you and appreciate that redux doesn't force something like a framework on you. You have choice of (non-)immutability, async handling, organizing your code, or how to "wrap it up" like you suggest.
Nothing on this project solves the real problems and introduces different ones, like how to have a reducer respond to different actions. My quibble with React is that when you get into moderately complicated forms, you start sprinkling stuff all over the place to get the desired output. Take for instance the most popular form library, redux-form. It's very heavy on it's own way of doing things, and there's a lot of stuff you have to know about it to be able to read into what the hell is going on, and it's hard to keep shit in the same place. Reselect is also very heavy handed for what it does. I NEVER needed anything like reselect before redux, and it kinda went alright! In my experience, for any reasonable development with redux you have to introduce: reselect, redux-form, thunk, some sort of immutability (immutable or redux-orm), a sprinkle of compromises and plenty of boilerplate, or roll your own magic util functions (i actually rolled my own very similar stuff to rematch, for quick and dirty stuff). When you summarise all that, it's really not that simple anymore! By the time you get through all of that, you might aswell pick up angular with the added benefit of clearer best practices, nicer dependency injection (oh, but react calls it context so it's cooler!), familiarity and type safety.
It's kinda funny how redux succeeded with such heavy concepts, which is normally not the case. This is still a mistery to me.
Oh and don't get me started on HOC. Want a tooltip over your component? Sure, wrap it in tooltip HOC! Sooo much nicer than directives!
This looks nice, although in Redux's favour is a large ecosystem of libraries that will work with it. This highlights a problem I still have with the JS ecosystem, which is relatively tight coupling between libraries. Of course many of these libraries could be glued together manually without too much work, but with the rapidly changing ecosystem and APIs for interoperability between libraries, and the obscurity of some of the libraries (Redux being a good example), this is often just not practical.
Compare this to Elm and the Elm architecture, and it seems worlds apart. Interoperability between libraries that all works in the same straightforward way, checked by the compiler.
It feels like the JS community made a lot of good decisions for the right reasons, but still ended up in a place that isn't great.
Babel is both the driving force of innovation and the cause of much of the fragmentation in JS. Babel allows you to use the latest language features and target the oldest environments. The trouble is that there's not a "right way" to package software to cover all of the deployment concerns. Code needs to work on Node going back several versions as well as browsers going back several versions. And for mobile/desktop websites, your code bundle size is extremely important. Rollup and Webpack 4 go a long way towards making this easier but it's nothing close to a "standard best way" to do it.
That's a good summary of the fragmentation and coupling in the build tools, but there's also the libraries themselves - React, Redux, React-Router, React-Router-Redux, etc etc.
I have to agree. The webpack-blocks project seemed like a step in the right direction, as it lets you 'compose' a webpack config (in theory) without side effects. I recently discovered lerna and am curious about using it to put together "safe" bundles of libraries which are commonly used together. No idea if that's actually a practical approach or not - its all in the dreaming stage.
Seems kind of like replacing common functional patterns with esoteric configuration objects?
I've never really understood the point of redux-thunk. But, then, I also don't use asynchronous actions in my datastore.
Asynchronous actions feel, to me, that they belong at the component layer where niceties such as spinners are being rendered, and then the backing store is updated with the results of the triggered action.
What drives people to put all of that into their store?
Thunk is not about storing async state in the datastore, it's more akin to multithreading in my opinion.
In backend, you might spin up a thread to do expensive operation like API access, and that thread has it's own database connection, to read and persist data.
With thunk, you dispatch a thunk-ed action to do expensive operation like fetching data from backend, and that action has it's own access to redux in form of `dispatch` / `getState` arguments.
In the end, my component calls `fetchData` function upon mounting. And the logic dealing with making multiple requests due to paging, or re-trying on errors then lives its own life inside a thunk action, independent from the component that spawned it.
How do you test the code being run inside the Redux store? How do you mock your API client?
A thunk action `fetchData` would return a `(dispatch, getState) => {}` function. You call that with `getState` that would return your fixtures, and verify that `dispatch` gets called as expected. For API, I use jest and mock out imported functions.
Redux is for state management - that could be your data store but many people use it manage their app's UI state as well. Dan Abramov himself has said many times that redux isn't strictly necessary and using local state is fine, but there are some benefits to having a full, serializable application state. You can capture and include it with a bug report/runtime exception for example. Having redux actions dispatched when users interact with your app can also be useful since you get an implicit global event bus. For things like analytics instead of littering calls to your analytics provider in all of your components' click handlers you can write middleware and keep all of that code outside of your presentation/behavior layer.
> full, serializable application state
Perhaps I'm dense, but you can serialize the current running state of a Promise in redux-thunk?
One approach is to write a reducer which has `isWaiting`, `error`, `finished` bools (in addition to whatever data said promise eventually modifies), or something similar, and programmatically update them in your actions/thunks. This effectively tracks the state of the promise (resolved/not resolved, error).I think the Flux "standard action" puts something similar in the action object - not sure what they do with it then.
I always put a `pending` property on the `meta` object, and any component that needs to show network status can just check that property on every render cycle. Aside from requiring some extra utility functions in the reducers to prevent having to updating `pending` for every state of the request it's rock solid and I haven't ever encountered a situation where it wouldn't work. You can get a _lot_ of mileage out of conforming to the FSA standard[1]; network status goes in meta, and errors are always handled the same way.
Agreed. And that sounds like a great approach. We didn’t conform exactly to FSA (which I want to try out on future projects). Our isWaiting was equivalent to your pending, I think.
(Edit) FYI we had actions to set isWaiting, etc.
Also, this conversation got me thinking about the difference between application state, state of an asynchronous request, and Redux actions as events (I'm thinking of the request/response events in node.js), and using them as such - to pass data throughout the application (in addition to the payload). Which I guess is what FSA is all about? So, thank you!
Yeah I get that, but then the application state isn't serialized in a usable form. If you re-load that state, you have half-open, "pending" operations.
If an idea of Redux is that serializable state is a good thing, then redux-thunk seems inimical to that.
Why would you serialize a pending network request? I use a property whitelist in my serializer that drops any application state not related to the business logic. Also, I don't use redux-thunk, I move all async operations to middleware and action creators are synchronous and declarative. For example, an API request will be defined in an action creator as an endpoint, http method, and request body, and the middleware will intercept this, make the API calls, and dispatch "pending" and "success/failure" actions. It scales extremely well and I don't know why people don't use custom middleware more often. The currying is a little confusing if you've never been exposed to it before, but the concept is no different than middleware in pretty much any http framework. I have yet to run into a problem that can't be solved by a custom middleware chain more cleanly than crazy async action frameworks like saga (I know saga works great for some folks but I think the cognitive overhead makes it of questionable utility when a promise chain in middleware can solve the same problem without requiring everyone on your team learn yet another library).
> Why would you serialize a pending network request?
I wouldn't. That's why I wouldn't put anything related to it in my Redux state!
But, like, I'm willing to be convinced. This is just where I'm at right now. Maybe your method would make more sense than redux-thunk does to me; have you written anything in more detail about this? (I'm not scared of middlewares, I've used Redux and middlewares even in non-web contexts, so intuitively this at least sounds more promising.)
I agree, I'm pretty strict about keeping UI state out of the store, but when you need to coordinate a bunch of components based on the status of network API requests and you already have a global message bus available...
I've created a gist[1] with some snippets from a current project; it's still alpha, but you can get a general feeling for how the data flow works. I would clean it up a bit if I were officially releasing it to the public, but I'm definitely interested in feedback if anyone thinks I'm doing it ALL WRONG. :)
[1]: https://gist.github.com/parkerault/9dc7e825cc9a62b5efa8a4c1c...
Edit: regarding serializing state; I don't know how others handle it but I subscribe a localStorage writer to the store that only gets called when the store is changed, not when an action dispatches, so if an action creator dispatches a function to redux-thunk, the action doesn't get serialized, just the resulting store after the reducers do their thing. Do you have any examples of actions themselves getting serialized? I believe when Abramov talks about keeping the state serializable in the redux docs he's referring to the store itself; as far as I know anything goes in the actions themselves.
This is very cool! Thank you for sharing this. We have had a somewhat similar approach, but stored request/response success/error states in request-specific reducers (it may have been better to put that data in the actions).
I think you will find of interest how we've used Immutable.js Maps for reducer state and Immutable.js Records as an easy way to create and pass around (guaranteed immutable) action types.
We were using Immutable.js and functional-style JS a lot in general, so it was a good fit.
https://gist.github.com/adamcee/3191762f2af43ec62a4b335b6695...
I'll look at this tomorrow. Thank you!
When I refer to serializing an action, I mean that, by necessity, the current state of any in-flight asynchronous actions must be recorded and saved--or else I don't think serialization works, its promises fail and you end up with a state whose completeness you can't guarantee. That opinion may change when I look at this tomorrow, of course--you very well may be cleverer than I am!
I use redux-storage for what you describe for persistence, btw. I like it a lot, I can easily select what I'm going to persist, and I don't have to maintain it. I dynamically pick a backend: IndexedDB where supported, LocalStorage where not.
FYI we used redux thunk
The problem with putting all async code directly into components is that often, async code is business logic or some other non-view-layer thing that doesn't really belong in a component.
The benefit of redux-thunk (and there are obviously other alternatives that provide the same value) is that it lets you write plain functions that have nothing to do with the store, and nothing to do with components. If you put all your business logic into these plain functions, then they're really easy to test, reason about, move, refactor, etc.
> What drives people to put all of that into their store?
It's worth pointing out that redux-thunk doesn't move async code into your store. The store (i.e. reducers) is still completely synchronous.
> async code is business logic or some other non-view-layer thing that doesn't really belong in a component.
In many React applications, components are separated into independent "controller"-layers and "view"-layers (containers and components are nomenclature I see occasionally, but the important takeaway is that both inherit from the React component class). Business logic has no place in your view components, of course, but is quite appropriate in your controller components. I don't think component implies view layer here. Even redux actions, when used, are appropriately called from the controller layer and not the view layer. Redux doesn't change anything about logical separation of concerns.
When the Flux pattern was originally introduced, it was driven home that it should not be used for everything. redux-thunk seems to encourage that you use it for everything. What do you use when global state is not necessary (or wanted)?
> Business logic has no place in your view components, of course, but is quite appropriate in your controller components.
Sure. Where you actually put stuff depends entirely on the complexity of what it is that you're building. If you have an async function that you need to use in several places then it makes sense to keep it separate. If you have several of these then maintaining a separate container component for each view component that needs access to these quickly becomes unmaintainable.
On the other hand, keeping them as separate functions and adding them in via redux `connect` syntax and `mapDispatchToProps` is simple and makes it clear what's going on. In this scenario, the `connect` higher-order-component acts like the container class, but it built up from separate parts depending on what you need.
> redux-thunk seems to encourage that you use it for everything
I don't think this is true. redux-thunk is a piece of middleware and that's pretty much it. It should be used however is most appropriate for your application and your particular use case. Keeping state inside components isn't always appropriate, but isn't always a bad thing either. If one is growing too complicated, then perhaps the other is more appropriate.
I agree that no two applications have the same needs, but async functions (and non-async, for that matter) are naturally separate and reusable, so I'm not sure I am entirely clear on what you are getting at here. What is the benefit of using connect to connect your function and simply calling that function from your controller? Code needed in multiple places should not require redux.
> Asynchronous actions feel, to me, that they belong at the component layer where niceties such as spinners are being rendered, and then the backing store is updated with the results of the triggered action.
This is exactly how thunks work, you just fire them from your component as a Redux action. I use thunks primarily for when an action needs to get data from an API and insert it into the store (e.g. on login).
Inverting the question, why would I want my presentational components to do a bunch of extra work beyond presenting? Calling an API is not presentational.
I think calling components "presentational" is an abuse of the term. It is all imperative facade at any meaningful level. The relationship of components to the user interactions triggered through them is not materially, to me, different from issuing a command from a CLI. As such, I tend to constrain imperative, this-can-fail activity to that layer rather than chucking it into my datastore.
The beauty of Redux and react is that you can do that and nobody's going to tell you you're bad for doing it.
Even Dan Abramov did it in his recent Redux presentation.
It's important that we understand why "best practices" exist but to never see them as commandments. Do what makes sense for your case, but it's your own funeral if you're deviating from best practices without having fully thought it through first.
I'm not saying anyone is "bad", I'm saying that I don't see why it makes sense to include the responsibility of acquiring data into the thing that stores data.
Part of the idea behind Redux is to keep components as pure as possible and limit side effects to one place, usually actions. You keep all async actions in components?
I write separate business logic into which are passed their dependencies and/or the data to operate on. If you have a defined API client, you can pass that to your business logic and let it work with the API client, but to do so means that in testing you have to mock your API client. So my business logic objects tend to work on groups of domain objects (in a functional manner, usually using ImmutableJS these days).
Functional-core, imperative-shell design suggests to then use this functional core within a shell of code that handles IO, handles failure cases--effectively, anything that can fail belongs here. To me, that's the React component; I don't view React as "functional", as it's got multiple types of state even separate from Redux (and this is not a demerit, IMO). The onClick handler of a React button is no different from the command handler for a CLI application. The code in that handler makes requests of its API client (which I typically inject through context and my own HOC--like I said, React's statefulness doesn't bug me at all), it performs business logic functions on the data yielded from its API client, and it stores the result (typically with a onComplete method or the like, so my redux-connected component can dispatch an action).
I'm amenable to other ways to do that data routing (because that's really all any of this is), I just don't see why the state store is where this routing should be done and see a lot of reasons around testing and failure-rescue and general code cleanliness why I wouldn't. If you were to make HTTP requests in your SQL datastore I'd be remarkably concerned for your sanity; I don't think it makes any more sense to do so in a Redux store.
_Can_ you put actions in a proper redux store? I thought the whole point was to keep them distinctly separate.
That's effectively what redux-thunk seems to want you to be doing: running asynchronous operations inside of its control.
That makes me super nervous.
> Is Redux more performant? No. In fact, it gets slightly slower with each new action that must be handled.
This point is not revisited. What is the performance of Rematch in comparison to Redux? I mean I guess it's more or less the same, since it's essentially Redux with less boilerplate, but it would be nice to have an explicit answer to this.
Can someone explain to me why someone would choose Redux over something like MobX ?
I feel like implements an action for each modification of state (or group of modifications) requires a lot of boilerplate code. In MobX, you treat your data tree as a separate entity, and simply modify it. The UI is automatically updated whenever it needs to.
Mobx isn't bad performance-wise, I think, since it only re-renders the components that are listening to the modified variables when a modification occurs.
The only advantage I see is being able to rewind the state store, which can be quite cool when debugging.
I have done some medium-sized application (only games), and I don't think I've run into any performance or readability problem with MobX. I've never done web apps with a team, though, but I don't think Redux would specially make it easier to work with other programmers.
> The only advantage I see is being able to rewind the state store
Another big one is being able to restore the state. In Redux, it's just one giant object that can be easily serialized and use in the future. With Mobx, I'm guessing you have to write deserialize logic for your various classes.
(To preface, I use React/Redux in all of my web applications. I think Redux is great for managing state.)
One thing I’ve never quite understood about Redux is why we are dispatching pure data instead of a function of state. This would remove the need for reducers, since your action _is_ your reducer. Something like this...
https://gist.github.com/anonymous/a5d741c5dec81be61e0aa70820...
Would this come with any drawbacks? I could see a large application having performance issues because of a large number of function allocations, but for small applications I don’t see a downside, just a simpler API.
Now what you have is a global mutable state store instead of an "immutable" state store, as everywhere can change anything and tracking down what code does what is back to being nightmarish.
This article resonated with me fairly well. As someone who has had an extensive background in Rails and has more recently switched to 100% javascript, this kind of pattern is what the community needs. There's something to be said about getting up and running as quickly as possible. Sure you can make the argument that not everyone's application is the same, but there's a ton of similar patterns that would have been better suited to having less mental gymnastics with a simple config while still leaving the door open with FP for those that want to fine tune.
Isn’t one of the points of redux to decouple actions from reducers? How would you implement multiple reducers responding to a single action if your actions are tied to a reducer with a namespace?
This can be done using Subscriptions [1].
I have another problem with this library, which is relying on singleton pattern for communication. This way, you cannot have more than one rematch instance in one application, which can be limiting. It also makes reasoning more difficult.
[1] https://github.com/rematch/rematch/blob/master/plugins/subsc...
Ahh thanks, I was lazy and didn’t go looking for an answer. Though this line seems a bit odd..
> In this case, subscriptions avoid the need of coupling the auth model to the profile model. Profile simply listens to an action.
I guess it’s no different to using “vanilla” redux actions but because actions contain the reducer namespace then it’s inherently coupled to another model isn’t it?
Agreed. The singleton pattern for state is a non starter for me. Got super wary as soon as I saw `dispatch` get imported and used directly.
It's easier to get started, but you'll likely have really nasty problems later (testing, server side rendering, etc).
Rematch reminds me of Marty.js. I like some of the ideas about providing a createStore wrapper that is more zero-config and using something like redux-actions to make reducers simpler. I have been using my own wrapper around sagas to allow for dispatching effects.
They lose me with their idea of "models" where I have a global dispatch that can be extended, like dispatch.modelName.actionName. That's just old school object-oriented thinking layered on top of redux's functional programming ideals.
whats the problem of mixing the good parts of OOP with the good parts of FP?
The two are just so fundamentally different that you will usually end up with a net negative taking two good things from both worlds. With the parent comment, you lose a degree of composability of the functional world while not gaining any degree of inheritability from OOP.
This approach looks really similar to the VueX approach: synchronous reducers are mutations, and async effects are actions. Can't wait to try this out in my next React project.
Nice. I've thought of some of these myself and implemented them as helper functions, but there are some new ideas here I hadn't thought of. Not sure I'm ready to port my project to a new library, but I may try some of these out piecemeal.
I like it. Configuration should be just that. Interested to read other opinions.
should have known it was a ploy for yet another library.