Comments (8)
Thanks for the quick response time @gaearon. I think the conceptual problem I'm having was clarified by this:
You need to put your side effects somewhere. We suggest to dispatch “async actions” which are handled by “middleware” and turn into proper “actions” at some point. You may do side effects differently—it's up to you. But we find this mechanism is convenient.
If I were the architect, I would move these side effects into some other contraption that is not middleware. Middleware (to me) seems best suited to orthogonal devices such as logging, error handling, and other interceptions, and should not be responsible for any domain logic or feature-related side effects. Given that you have only one opportunity when initializing a redux app to create a custom dispatcher via createDispatcher(composeStores(stores), [middlewareA, middlewareB, ...])
, it seems an awkward place to attach anything but app-wide orthogonal concerns. I'm just imagining a larger app where side-effects are all handled by middleware, and your bootstrap file for creating the flux app is this giant blob of [middlewareForFeatureA, secondMiddlewareForFeatureA, middlewareFeatureB, SecondMiddlewareFeatureB, ThirdMiddlewareFeatureB, ...]
which seems at the very least a huge nuisance.
In order to take advantage of the event-sourced architecture of redux we need simple object actions pumped through our reducers. Generating these actions via middleware seems convoluted for reasons listed above. Perhaps there can be a second mechanism for handling a logic-laden (or async) action before it ultimately dispatches a synchronous, simple action object. For sake of the example let's call this the domainer. Imagine you have a view component trigger addTodo(text)
, but you have the added domain requirements of 1) no more than 3 todos at once, and 2) a spell-checker that connects to an autocorrect service via AJAX before adding your todo to the list. The view component (directly or indirectly) invokes addTodo(text)
via the domainer. This domainer ensures we have fewer than 3 todos, then fires off the ajax spellchecker service. Exactly like the provided middleware examples, it then dispatches a success/error action with the spell-checked todo text. Really the only difference here is a distinct encapsulation of domain logic / async handling into a single unit versus various middleware behaviors.
Now that I have typed this all out and waded through my own thoughts, I think what I'm missing in Redux is a dedicated state machine. Reducers provide simplicity from (state, action) -> newState, but in reality those switch statements are just begging for a dedicated state machine implementation. The domain logic hidden behind switch statements and if/then blocks could be much more explicitly stated in the form of states: { 'waitingForTodo', 'addingTodo', 'maximumTodos', 'noTodos' }
where each state has different acceptable inputs: waitingForTodo: -addTodo(text) -removeTodo(index) -removeAllTodos()
etc.
This is all crazy talk and hypotheticals, but I appreciate your feedback. I think I should stop trying to impose my design thoughts onto a differently laid out architecture. Feel free to close this as not really an issue, more of a discussion leading nowhere.
from redux-thunk.
Disregarding the “component receives action” misconception, what you describe is convenient to implement with redux-thunk
:
function addTodoWithoutCheck(text) {
return {
type: 'ADD_TODO',
text
};
}
export function warnTooManyTodos() {
return {
type: 'WARN_USER',
message: 'Too many todos.'
};
};
}
export function addTodo(text) {
return function (dispatch, getState) {
return (getState().todos.length === 3) ?
dispatch(warnTooManyTodos()) :
dispatch(addTodoWithoutCheck(text));
}
}
}
Yes, this is domain logic, but it's not “mixed” with actions. It is interpreted by middleware (in this case, redux-thunk
). You still get plain actions in the reducers.
You could move it somewhere and call it something else, but it's just too convenient to be able to “dispatch” these things, whether you call them “async actions”, “instructions”, or something else.
from redux-thunk.
Finally, yes, you should strive to put your logic into reducers whenever possible. But sometimes it's just too much pain, or involves side effects. That's when you'd use redux-thunk
.
from redux-thunk.
Maybe this particular example isn't good. We usually use redux-thunk
for side effects, e.g. async API calls. You need to put those side effects somewhere.
By the time action is plain object and reaches a reducer, it's just a description of what happened. We found it useful to also be able to express intention when dispatching, even if such “async action” transforms into side effects and produces one or more actions, potentially asynchronously, later.
You need to put your side effects somewhere. We suggest to dispatch “async actions” which are handled by “middleware” and turn into proper “actions” at some point. You may do side effects differently—it's up to you. But we find this mechanism is convenient.
Does this clarify anything, or do you still disagree? Please propose how to express http://gaearon.github.io/redux/docs/recipes/ReducingBoilerplate.html#async-action-creators in a clean manner without the concept of “async actions” and a helper like redux-thunk
?
from redux-thunk.
Perhaps one component really wants to know each and every time a new todo is added (maybe for displaying a "too many todos" error) while a second component will receive the action, but follows its own logic to disregard that action because it reads that there are too many todos already.
I'm not sure what you mean by “component receiving action”. Components can't receive actions. They only receive the current state. Reducers receive actions.
from redux-thunk.
That's an interesting discussion, and I'd appreciate if you could whip up a proof of concept of your proposed system for side effects and created an issue in the main Redux repo. We're not dogmatic, it's just that this is the best we have at the moment.
from redux-thunk.
Related:
reduxjs/redux#351
reduxjs/redux#343
reduxjs/redux#307
But at this point, indeed, discussion without a proof of concept goes nowhere.
from redux-thunk.
You might be interested in Redux Saga project which moves side effects to generator-driven "sagas": reduxjs/redux#1139
from redux-thunk.
Related Issues (20)
- Why `redux` module extension is not part of index.d.ts file in package? HOT 1
- Passing `dispatch` instead of calling it.
- Empty payload in PENDING state HOT 1
- Why not next? HOT 1
- types wrong by infer in TS HOT 1
- No type hints when using dispatch HOT 4
- import 'redux-thunk/extend-redux' doesn't work HOT 12
- When running server getting expected errors HOT 2
- ThunkAction is not assignable to parameter of type 'AnyAction'. HOT 46
- Current implemention doesn't allow Jest or any other mock tool to spy on dispatch or getState HOT 2
- TS do not show the error if payload is Partial<> HOT 1
- Can I abort redux thunk dispatches? Without Redux toolkit or aborting the fetch request itself. HOT 1
- Allow wrapped thunk actions for extensibility HOT 2
- When type: "module" is used in root package.json, redux-thunk cannot be applied without crashing HOT 8
- Warning in React-thunk while installing HOT 1
- Query About Redux Thunk HOT 1
- `thunk` as a named export? HOT 3
- redux thunk cannot be use as middleware using Typescript 5.0 or above HOT 1
- during integrating thunk into react-redux project, getting middleware is not a function error. HOT 1
- No default export for redux-thunk HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from redux-thunk.