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.

Flutter demo.

Pub.dev.

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:

  1. UI is explained by state ("state causes UI")
  2. 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.)

0
likes
60
pub points
0%
popularity

Publisher

chrisclampitt.life

Clojurescript re-frame middleware for redux-dart

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD (LICENSE)

Dependencies

meta, quiver, redux

More

Packages that depend on reframe_middleware