Skip to content

Commit 5cc997f

Browse files
committed
feature: create async thunk and create action
1 parent 3faca6f commit 5cc997f

File tree

8 files changed

+154
-16
lines changed

8 files changed

+154
-16
lines changed

lib/redux_toolkit.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
library redux_toolkit;
22

33
export 'package:reselect/reselect.dart';
4+
export './src/action.dart';
5+
export './src/async_thunk.dart';
46
export './src/create_reducer.dart';

lib/src/action.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
abstract class PayloadAction<Payload, Meta, Error> {
2+
final Payload payload;
3+
final Meta meta;
4+
final Error error;
5+
6+
const PayloadAction({
7+
this.payload,
8+
this.meta,
9+
this.error,
10+
});
11+
}

lib/src/async_thunk.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import 'dart:async';
2+
import 'package:redux/redux.dart';
3+
import 'package:redux_thunk/redux_thunk.dart';
4+
5+
import './action.dart';
6+
7+
class Pending<T, M> extends PayloadAction<dynamic, M, dynamic> {
8+
const Pending(M meta) : super(meta: meta);
9+
}
10+
11+
class Fulfilled<T, P, M> extends PayloadAction<P, M, dynamic> {
12+
const Fulfilled(P payload, M meta) : super(payload: payload, meta: meta);
13+
}
14+
15+
class Rejected<T, M, E> extends PayloadAction<dynamic, M, E> {
16+
const Rejected(M meta, E error) : super(meta: meta, error: error);
17+
}
18+
19+
abstract class AsyncThunk<Self, State, Payload, Result> implements CallableThunkAction<State> {
20+
final Payload payload;
21+
22+
const AsyncThunk([this.payload]);
23+
24+
Future<Result> run();
25+
26+
@override
27+
Future<void> call(Store<State> store) async {
28+
store.dispatch(Pending<Self, Payload>(payload));
29+
try {
30+
final result = await run();
31+
store.dispatch(Fulfilled<Self, Result, Payload>(result, payload));
32+
} catch (err) {
33+
store.dispatch(Rejected<Self, Payload, dynamic>(payload, err));
34+
}
35+
}
36+
}

lib/src/create_reducer.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import 'package:redux/redux.dart';
22

3-
typedef CaseReducer<State, Action> = State Function(State, Action);
4-
typedef DefaultCaseReducer<State> = State Function(State);
5-
typedef BuilderCallback<State> = Function(ActionReducerMapBuilder<State>);
3+
typedef CaseReducer<State, Action> = State Function(State state, Action action);
4+
typedef DefaultCaseReducer<State> = State Function(State state);
5+
typedef BuilderCallback<State> = Function(ActionReducerMapBuilder<State> builder);
66

77
abstract class ActionReducerMapBuilder<State> {
88
ActionReducerMapBuilder<State> addCase<Action>(CaseReducer<State, Action> reducer);
@@ -47,10 +47,7 @@ Reducer<State> createReducer<State>(State initialState, BuilderCallback<State> b
4747
final actionReducerMap = builder.build();
4848

4949
return (state, action) {
50-
if (state == null) {
51-
state = initialState;
52-
}
53-
50+
state = state ?? initialState;
5451
return actionReducerMap.getReducerForAction(action.runtimeType)(state, action);
5552
};
5653
}

pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ environment:
88
dependencies:
99
redux: ^4.0.0+2
1010
reselect: ^0.4.0
11+
redux_thunk: ^0.3.0
1112

1213
dev_dependencies:
1314
test: ^1.3.0
15+
mockito: ^4.1.1

test/async_thunk_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'package:test/test.dart';
2+
import 'package:mockito/mockito.dart';
3+
import 'package:redux/redux.dart';
4+
import 'package:redux_toolkit/redux_toolkit.dart';
5+
6+
import 'test_thunks.dart';
7+
8+
class MockStore extends Mock implements Store<int> {}
9+
10+
void main() {
11+
group('async thunk', () {
12+
Store<int> store;
13+
14+
setUp(() {
15+
store = MockStore();
16+
});
17+
18+
test('should dispatch Pending<StringLength, String>', () async {
19+
final thunk = StringLength('hello');
20+
await thunk.call(store);
21+
verify(store.dispatch(argThat(isA<Pending<StringLength, String>>())));
22+
});
23+
24+
test('should dispatch Fulfilled<StringLength, int, String>', () async {
25+
final thunk = StringLength('hello');
26+
await thunk.call(store);
27+
verify(store.dispatch(argThat(isA<Fulfilled<StringLength, int, String>>())));
28+
});
29+
30+
test('should dispatch Pending<EpicFail, String>', () async {
31+
final thunk = EpicFail('hello');
32+
await thunk.call(store);
33+
verify(store.dispatch(argThat(isA<Pending<EpicFail, String>>())));
34+
});
35+
36+
test('should dispatch Rejected<EpicFail, String, dynamic>', () async {
37+
final thunk = EpicFail('hello');
38+
await thunk.call(store);
39+
verify(store.dispatch(argThat(isA<Rejected<EpicFail, String, dynamic>>())));
40+
});
41+
});
42+
}

test/create_reducer_test.dart

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@ import 'package:test/test.dart';
55
import 'test_actions.dart';
66
import 'test_state.dart';
77

8+
class SingleTestThunk {}
9+
10+
class DoubleTestThunk {}
11+
812
void main() {
9-
group('Create reducer', () {
10-
Reducer<TestState> reducer;
13+
group('create reducer', () {
1114
final TestState initialState = TestState(items: List.unmodifiable([]));
15+
16+
Reducer<TestState> testStateReducer;
17+
Reducer<int> listReducer;
1218

1319
setUp(() {
14-
reducer = createReducer<TestState>(initialState, (builder) {
20+
testStateReducer = createReducer<TestState>(initialState, (builder) {
1521
builder
1622
.addCase<RequestItemsAction>(
1723
(state, action) => state.copyWith(isLoading: true))
@@ -20,15 +26,23 @@ void main() {
2026
.addCase<FetchItemsErrorAction>((state, action) =>
2127
state.copyWith(isLoading: false, error: action.error));
2228
});
29+
30+
listReducer = createReducer<int>(0, (builder) {
31+
builder
32+
.addCase<Fulfilled<SingleTestThunk, String, void>>(
33+
(state, action) => 1)
34+
.addCase<Fulfilled<DoubleTestThunk, String, void>>(
35+
(state, action) => 2);
36+
});
2337
});
2438

2539
test('should yield the initialState', () {
26-
final result = reducer(initialState, SomeUnhandledAction());
40+
final result = testStateReducer(initialState, SomeUnhandledAction());
2741
expect(result, equals(initialState));
2842
});
2943

3044
test('should yield a loading state', () {
31-
final result = reducer(initialState, RequestItemsAction());
45+
final result = testStateReducer(initialState, RequestItemsAction());
3246
expect(result.isLoading, isTrue);
3347
});
3448

@@ -39,7 +53,7 @@ void main() {
3953
]);
4054

4155
final prevState = initialState.copyWith(isLoading: true);
42-
final result = reducer(
56+
final result = testStateReducer(
4357
prevState,
4458
FetchItemsSuccessfulAction(
4559
payload: items,
@@ -55,7 +69,7 @@ void main() {
5569
]);
5670

5771
final prevState = initialState.copyWith(isLoading: true);
58-
final result = reducer(
72+
final result = testStateReducer(
5973
prevState,
6074
FetchItemsSuccessfulAction(
6175
payload: items,
@@ -68,7 +82,7 @@ void main() {
6882
final error = Exception("Hehe you failed");
6983

7084
final prevState = initialState.copyWith(isLoading: true);
71-
final result = reducer(
85+
final result = testStateReducer(
7286
prevState,
7387
FetchItemsErrorAction(
7488
error: error,
@@ -81,13 +95,31 @@ void main() {
8195
final error = Exception("Hehe you failed");
8296

8397
final prevState = initialState.copyWith(isLoading: true);
84-
final result = reducer(
98+
final result = testStateReducer(
8599
prevState,
86100
FetchItemsErrorAction(
87101
error: error,
88102
));
89103

90104
expect(result.error, equals(error));
91105
});
106+
107+
test('should yield list with only SingleTestThunk', () {
108+
final result = listReducer(
109+
0,
110+
Fulfilled<SingleTestThunk, String, void>('SingleTestThunk', null),
111+
);
112+
113+
expect(result, equals(1));
114+
});
115+
116+
test('should yield list with only DoubleTestThunk', () {
117+
final result = listReducer(
118+
0,
119+
Fulfilled<DoubleTestThunk, String, void>('DoubleTestThunk', null),
120+
);
121+
122+
expect(result, equals(2));
123+
});
92124
});
93125
}

test/test_thunks.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import 'dart:async';
2+
import 'package:redux_toolkit/redux_toolkit.dart';
3+
4+
class StringLength extends AsyncThunk<StringLength, int, String, int> {
5+
const StringLength(String payload) : super(payload);
6+
7+
@override
8+
Future<int> run() => Future.sync(() => payload.length);
9+
}
10+
11+
class EpicFail extends AsyncThunk<EpicFail, int, String, int> {
12+
const EpicFail(String payload) : super(payload);
13+
14+
@override
15+
Future<int> run() => Future.error(Exception('hehe'));
16+
}

0 commit comments

Comments
 (0)