Earlier quoted context omitted.
Redux is not over-engineered. It's a simple library that's <1000 lines of code. It is great for large, complex apps where state gets hairy to manage. It's a bit verbose for smaller apps. But just because something is verbose does not mean it's over-engineered. It is explicit in defining all the possible states your application can get in. That's useful for a certain class of application.
It might have been more accurate for me to have said "the code you will get if you follow the reference implementations and documentation of Redux will be overengineered". I dislike three things: that dispatch isn't usually made globally available, but is passed through context, which makes it effectively global anyway; that asynchronous actions are only available via plugins despite being a core part of JS developme…
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...