-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
store.dispatch was already assigned to the variable dispatch
#2146
Conversation
Pretty sure this is as intended, to ensure that the correct version gets passed down. Thanks, but going to leave this as-is. |
Why isn't CI failing if this is wrong? |
Still getting used to this whole "maintainer" and "CI" thing. The PR was only open for a few minutes - did I close it before Travis had a chance to run things? For that matter, do we have tests covering that aspect of behavior? |
I ran the tests locally and they passed. I don't think this change is wrong… it's just a refactor. I figured @markerikson was suggesting that it was a style thing. I figured using |
I know there's been previous PRs that tried to make tweaks inside Glancing at the diff again, I think this would work okay because we're not dealing with a closure or anything, but I can also see value in keeping the reference explicit. |
In my opinion it is way more clear to have |
I've seen you quote your approach several times… I don't have a problem with that but you might want to update the CONTRIBUTING document. If I knew the default attitude was to reject code I wouldn't have taken the time to submit a PR. |
Sorry, I'm not trying to discourage PRs. I'm just hesitant to make changes to code that we know works correctly, especially in areas that have churned in the past, and where I have seen erroneous PRs before. Dan's also made the comment that "Redux is basically done", and although there's definitely a bunch of stuff tagged with a 4.0 milestone that would potentially be useful to have, it's not like there's active development going on. I am admittedly also new to this whole maintainer thing, so I'm not going to claim I exactly know what I'm doing :) I'm curious if reopening this will actually kick off a CI run. Let's find out. |
That's a "yes", apparently. |
Well, those do pass. Are there any other tests we can add to capture the correct behavior in here? |
I don't think this changes the behavior, it just makes it more clear what is going on so people have a easier time understanding it. |
Right, but I'm asking if we can further guarantee that we have the existing behavior captured. Like, I dunno, maybe a test that ensures that whatever is passed in as A lot of my thoughts on modifying this file are due to having seen previous PRs that thought the use of the closure for the |
@markerikson if redux is this fragile, can we get some issues opened for tests that need to be written? Would be happy to write some |
I'm not sure if "fragile" is the right word - it's more that Dan, Andrew, and others have made specific tweaks over time (like the dispatch closure example), and other people have skimmed through the code and filed PRs without going through the history to see why those changes were made. But yeah, more tests would be a good thing. I personally am a lot more familiar with using Redux than I am with the exact history of specific lines of code, so while I know that there have been specific reasons for certain changes in the past, I'm not familiar with what all those changes are. That also means I don't have specific suggestions for new tests off the top of my head. |
Let me start by acknowledging I believe we all have good intentions. This is a tiny library that received a lot of attention. This means all changes need to be reviewed very carefully because we have Take #1592 for example. It looked like an innocent change but actually caused a crash in apps: #1644. We had to revert it in #1647 and add a corresponding test. In this case I haven’t reviewed code in detail and can’t say for sure if it’s safe or not yet. I also know that many maintainers are busy with other projects, and nobody wants to merge a change that could break thousands of apps just because the code looks nicer. I don’t want us to become risk-averse to the point of being silly either but perhaps contributing tests for more edge cases would be a better starting point. I hope you can appreciate why this isn’t so simple. |
In particular this change is tricky because it exists on the point where we interface with third-party code. Just like #1592, it seems safe with reasonable assumptions we can make about how middlewares work, but those assumptions may be wrong. It is not obvious to me that this change is 100% safe. For example what if there is a weird One would think this is a silly scenario but one thing I learned maintaining popular libraries is that if some weird edge case can possibly occur, it already exists in somebody’s code. Is it worth supporting this edge case? Probably not. Should we break this person with a patch version for no other reason than a nicer-looking implementation? I think not either. I’m happy to take this into |
Yeah, that's what I was trying to get at. I'd rather err on the side of not accidentally breaking things because I didn't fully understand the exact implications of what was going on. If a submitted PR fixes an obvious bug or is clearly an improvement I'm absolutely interested, but small changes that are more style-oriented need more justification for going in. And yeah, #1592 looks like the previous PR that I was thinking of. |
I personally like this change and think you should feel free to accept it as a patch, but you also reserve the right to reject it. It's truly a matter of taste. @gaearon stated the particular case that this causes breakage in users' apps, and it's a real, code-able case. But I think it clearly falls outside the intended usage of redux (especially given the very existence of middleware/enhancers), and redux absolutely should be allowed to accept this change. If the redux collaborators are comfortable supporting an even higher level of stability that precludes a tiny change like this, that's totally cool too! That is an incredibly high level of stability. I just hope redux collaborators don't feel like they're always walking on egg-shells, in fear of enraging users. You all are wonderful and we appreciate your work 👍 |
Thanks! I do think bringing this into the next major would be good, especially given that we plan to clean up a bunch of other middleware-related things. |
Totally agree with @devinivy ! Haters gonna hate. Cheers 🍻 ! |
Let’s not denigrate our users by calling them names. 😉 Even if somebody uses the library in a weird way it doesn’t mean there’s a good reason to break them if we’re not solving an issue for somebody else. Anyway, this thread is going on a tangent. I’m happy to revisit this if the PR is resubmitted against the |
I am really interested in this PR, and spend some time on analyzing applyMiddleware function. return (createStore) => (reducer, preloadedState, enhancer) => {
// here we have created store
var store = createStore(reducer, preloadedState, enhancer)
// remember link to store dispatch
var dispatch = /*store.dispatch*/null // disable unwrapped dispatching, according to comment in spec
var chain = []
var middlewareAPI = {
getState: store.getState,
// function late binding(i am not sure that this is correct definition) in JS looks
// really fine, works because we assign to dispatch value result of middlewares
//composition
dispatch: (action) => dispatch(action)
}
// here is first step middleware creation ---- with modification of store.dispatch?
// in theory no, this step is designed to provide middlewareAPI to middlewares closure
// practically i don't know how to achieve this side effect, because store is encapsulated.
chain = middlewares.map(middleware => middleware(middlewareAPI))
// here is where 2 step of middleware creation where "next" is formed.
// result of 2 step is new dispatch function, also in theory without modification
// of existing store.dispatch(also don't know how to achieve)
const dispatchCreator = compose(...chain)
// store.dispatch will be "next" in middleware which is last in chain(expected behavior)
dispatch = dispatchCreator(store.dispatch)
return {
...store,
dispatch
}
} Conclusion: I think that store.dispatch is on correct place according to comment in applyMiddleware.spec. And in further release dispatch will be initialized as a null(as in my code). PS. Functional style code with mutable objects is hell :) @gaearon @markerikson What do you think about my comments? |
Based on the discussion above, I took a shot at refactoring+commenting export default function applyMiddleware(...middlewares) {
function enhanceDispatch(store) {
// `enhancedDispatch` is initialized as store.dispatch in case a middleware dispatches an
// action during its initialization. The true value of `enhancedDispatch` is not set until
// after all middleware have initialized. Any actions dispatched during a middleware's
// initialization will not be intercetpable by the middleware chain.
let enhancedDispatch = store.dispatch
const middlewareAPI = {
getState: store.getState,
dispatch: function dispatchProxy(action) {
// Because `enhancedDispatch` is mutable, it needs to be wrapped in a proxy function so
// that actions dispatched in middleware are interceptable by the entire middleware chain.
// This allows the middleware and enhancedDispatch to be mutually recursive.
return enhancedDispatch(action)
}
}
const initializedMiddlewareChain = middlewares.map(middleware => middleware(middlewareAPI))
enhancedDispatch = compose(...initializedMiddlewareChain)(store.dispatch)
return enhancedDispatch
}
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
return {
...store,
dispatch: enhanceDispatch(store)
}
}
} |
Whoa. Like, comments and stuff. Are we actually allowed to have those in the codebase? :) Definitely liking where that's headed. Terse code has its place, but I'm all for making things easier to read and understand in context. I'll come back to look at this later, but that certainly looks like a change we'd want to make. |
Hm, I am a bit confused cause it's not clear what is expected behavior? |
I was preserving existing behavior (otherwise it's not just refactoring). If the intent is to forbid early dispatches in a later release, then declaration of let enhancedDispatch = () => { throw new Error('Early dispatch is forbidden.') } and we could do an interim implementation as a warning: let enhancedDispatch = action => {
log.warn('Early dispatch is forbidden. This will throw an error in a future version of Redux.')
store.dispatch(action)
} |
Actually, @timdorr has already made the change in the next branch where the initial value of dispatch is to throw an error. Since that's the direction applyMiddleware is going, this PR would be incompatible. |
No description provided.