reframe_middleware 1.0.1
reframe_middleware: ^1.0.1 copied to clipboard
Clojurescript re-frame middleware for redux-dart
reframe-middleware: the ‘action first’ approach to redux #
Reframe-middleware makes actions first class in redux.dart. #
Inspired by Clojurescript's re-frame.
How to use #
1. Add reframe_middleware
to your pubspec.yaml
:
dependencies:
reframe_middleware: ^1.0.0
2. Add reframeReducer
and reframeMiddleware
to your redux.dart Store
:
import 'package:reframe_middleware';
final store = Store<AppState>(
reframeReducer, // produces new state
initialState: AppState(),
middleware: [reframeMiddleware(), // handles actions
thirdPartyMiddleware, ...]);
3. Define an action:
Synchronous, pure action:
import 'package:reframe_middleware';
@immutable
class IncrementAction extends ReframeAction {
@override
ReframeResponse<AppState> handle(AppState state) =>
ReframeResponse.stateUpdate(
state.copy(count: state.count + 1));
}
Asynchronous, impure action (side-effect):
import 'package:reframe_middleware';
@immutable
class AsyncIncrementAction extends ReframeAction {
@override
ReframeResponse<AppState> handle(AppState state) =>
ReframeResponse.sideEffect(() =>
Future.delayed(Duration(milliseconds: 1000))
.then((_) => [IncrementEvent()]));
}
An action that does both:
@immutable
class DoubleIncrementAction extends ReframeAction {
@override
ReframeResponse<AppState> handle(AppState state, Effects effects) {
return ReframeResponse(
nextState: Optional.of(state.copy(count: state.count + 1)),
effect: () => Future.delayed(Duration(milliseconds: 1000))
.then((_) => [IncrementAction()]));
}
4. Dispatch... and done.
store.dispatch(IncrementAction());
How it works #
Actions are handled by their own handle
method:
action.handle(store.state) -> ReframeResponse
A ReframeResponse
contains a new state and side effect.
@immutable
class ReframeResponse<S> {
final Optional<S> nextState;
final SideEffect effect;
const ReframeResponse({
this.nextState = const Optional.absent(),
this.effect = noEffect,
});
// A side-effect is a closure that becomes a list of actions
typedef SideEffect = Future<List<Action>> Function();
Future<List<Action>> noEffect() async => [];
FAQ #
Do I need thunkMiddleware? #
No. Reframe-middleware already does async logic -- that’s what ReframeResponse
's effect
is for.
Does this replace redux.dart or flutter-redux? #
No. Reframe-middleware is supposed to be used with redux.dart (in the same way e.g. Flutter redux_persist is).
Reframe-middleware, like redux.dart, can be used with or without the excellent flutter-redux.
Doesn't this couple reducers and actions, which is discouraged? #
Short answer: Yes.
Long answer:
There have been objections to 1:1 mappings between actions and reducers. (“The whole point of Flux/Redux is to decouple actions and reducers”).
But the decoupling of actions and reducers is an implementation detail of redux.js.
In contrast, Clojurescript re-frame intentionally couples an event (action) with its handler (reducer + middleware). Why?
Every redux system* -- Elm, re-frame, redux.js, redux-dart etc. -- is characterized by two fundamental principles:
- UI is explained by state ("state causes UI")
- state is explained by actions ("actions cause state")
When we dispatch an action we ask, "What does this action mean, what updates or side-effects will it cause?"
If you need to reuse state modification logic, reuse a function -- don't reuse a reducer.
*(In contrast, SwiftUI has 1 but not 2, and so is not a redux sytem.)