reactLatest<Value> method
Add effect
triggered by react state's partial value change,
it will cancel previous effect when new effect triggered or system
disposal.
API Overview
searchSystem
...
.reactLatest<String>(
value: (state) => state.keyword, // map state to value, required
equals: (value1, value2) { // `equals` is used to determine if old value equals
return value1 == value2; // to new value. If there are not equal, then effect
}, // is triggered. `equals` is nullable, defaults to
// `==` as shown.
skipInitialValue: true, // return true if initial value is skipped,
// which won't trigger effect.
// return false if initial value isn't skipped,
// which will trigger effect.
// `skipInitialValue` defaults to true if omitted.
effect: (value, dispatch) {
// trigger effect here with new value, required
return Disposer(() { // return a `Disposer` to register cancel logic
// with this ticket.
// return null or omit return, if there is nothing
// to cancel.
});
},
)
...
Usage Example
Below code showed how to model search bar, latest search words cancel previous search API call if previous one is not completed.
searchSystem
...
.reactLatest<String>(
value: (state) => state.keyword,
effect: (keyword, dispatch) async {
try {
final data = await api.call(keyword);
dispatch(LoadDataSuccess(data));
} on Exception {
dispatch(LoadDataError());
}
},
)
For this scenario if previous search result came after latest one, the previous result will be ignored.
If search api
provide a cancellation mechanism,
We can return a Disposer
to register cancel logic with this ticket.
For example if above api.call
return Stream
:
searchSystem
...
.reactLatest<String>(
value: (state) => state.keyword,
effect: (keyword, dispatch) async {
final stream = api.call(keyword); // it return stream now
final subscription = stream.listen(
(data) => dispatch(LoadDataSuccess(data)),
onError: (Object _) => dispatch(LoadDataError()),
);
return Disposer(() => subscription.cancel()); // register cancel
},
)
...
This Disposer
will be called when keyword changed or system disposal.
Implementation
System<State, Event> reactLatest<Value>({
required Value Function(State state) value,
Equals<Value>? equals,
bool skipInitialValue = true,
required Disposer? Function(Value value, Dispatch<Event> dispatch) effect,
}) {
final localEquals = equals ?? defaultEquals;
return withContext<_ReactLatestContext<Value, Event>>(
createContext: () => _ReactLatestContext(),
effect: (context, state, oldState, event, dispatch) {
final reactContext = context.reactContext;
final localValue = value(state);
final bool shouldUpdateOldValue;
final bool shouldTriggerEffect;
if (event == null) {
shouldTriggerEffect = !skipInitialValue;
shouldUpdateOldValue = true;
} else {
final oldValue = reactContext.oldValue as Value;
shouldTriggerEffect = !localEquals(oldValue, localValue);
shouldUpdateOldValue = shouldTriggerEffect;
}
if (shouldUpdateOldValue) {
reactContext.oldValue = localValue;
}
if (shouldTriggerEffect) {
final latestContext = context.latestContext;
latestContext.disposePreviousEffect();
latestContext.disposer = effect(localValue, latestContext.versioned(dispatch));
}
},
dispose: (context) {
context.latestContext.disposePreviousEffect();
},
);
}