connectFlux<TStore extends Store, TActions, TProps extends UiProps> function
- Map mapStateToProps(
- TStore state
- Map mapStateToPropsWithOwnProps(
- TStore state,
- TProps ownProps
- Map mapActionsToProps(
- TActions actions
- Map mapActionsToPropsWithOwnProps(
- TActions actions,
- TProps ownProps
- Map mergeProps(
- TProps stateProps,
- TProps dispatchProps,
- TProps ownProps
- bool areOwnPropsEqual(
- TProps nextProps,
- TProps prevProps
- bool areStatePropsEqual(
- TProps nextProps,
- TProps prevProps
- bool areMergedPropsEqual(
- TProps nextProps,
- TProps prevProps
- Context? context,
- bool pure = true,
- bool forwardRef = false,
A wrapper around the connect
function that provides a similar API into a Flux store.
This is primarily for use while transitioning to connect
and OverReact Redux.
NOTE: This should only be used to wrap components that extend from Component2.
Additionally, unlike
connect
, there is noareStatesEqual
parameter due to the state update process being impure. It is impure because it involves modification of the store itself, as opposed to creating a new state object with each change.
Example:
UiFactory<CounterProps> Counter = connectFlux<FluxStore, FluxActions, CounterProps>(
mapStateToProps: (state) => (
Counter()..count = state.count
),
mapActionsToProps: (actions) => (
Counter()..increment = actions.incrementAction
),
)(_$Counter);
// The `Counter` component implementation would also be in this file.
-
mapStateToProps
is used for selecting the part of the data from the store that the connected component needs. -
It is called every time the store state changes.
-
It receives the entire store state, and should return an object of data this component needs. If you need access to the props provided to the connected component you can use
mapStateToPropsWithOwnProps
, the second argument will beownProps
. See: react-redux.js.org/using-react-redux/connect-mapstate#defining-mapstatetoprops -
mapActionsToProps
is a way to connect local component props to actions that are part of the provided actions class.
Unlike mapDispatchToProps
with connect
, mapActionsToProps
will be called whenever the store state
changes.
In practice, the most common usage is to set the relevant component action props directly by setting
them equal to the corresponding action within the action class. If you need access to the props provided
to the connected component you can use mapActionsToPropsWithOwnProps
, the second argument will be ownProps
.
-
mergeProps
if specified, defines how the final props for the wrapped component are determined. If you do not providemergeProps
, the wrapped component receives {...ownProps, ...stateProps, ...dispatchProps} by default. -
areOwnPropsEqual
does an equality check using propsOrStateMapsEqual by default. -
areStatePropsEqual
does a shallow Map equality check using propsOrStateMapsEqual by default. -
areMergedPropsEqual
does a shallow Map equality check using propsOrStateMapsEqual by default. -
context
can be utilized to provide a custom context object created withcreateContext
.context
is how you can utilize multiple stores. While supported, this is not recommended. See: redux.js.org/api/store#a-note-for-flux-users See: stackoverflow.com/questions/33619775/redux-multiple-stores-why-not
Example:
import 'package:over_react/react_dom.dart' as react_dom;
Store store1 = Store<CounterState>(counterStateReducer, initialState: CounterState(count: 0));
Store store2 = Store<BigCounterState>(bigCounterStateReducer, initialState: BigCounterState(bigCount: 100));
UiFactory<CounterProps> Counter = connectFlux<SmallCounterFluxStore, FluxActions, CounterProps>(
mapStateToProps: (state) => (Counter()..count = state.count)
)(_$Counter);
UiFactory<CounterProps> BigCounter = connect<BigCounterFluxStore, FluxActions, CounterProps>(
mapStateToProps: (state) => (Counter()..count = state.bigCount),
context: bigCounterContext,
)(_$Counter);
react_dom.render(
Dom.div()(
(ReduxProvider()..store = store1)(
(ReduxProvider()
..store = store2
..context = bigCounterContext
)(
Dom.div()(
Dom.h3()('BigCounter Store2'),
BigCounter()(
Dom.h4()('Counter Store1'),
Counter()(),
),
),
),
),
), querySelector('#content')
);
-
pure
iftrue
(default), connect performs several equality checks that are used to avoid unnecessary calls tomapStateToProps
,mapActionsToProps
,mergeProps
, and ultimately torender
. These includeareOwnPropsEqual
,areStatePropsEqual
, andareMergedPropsEqual
. While the defaults are probably appropriate 99% of the time, you may wish to override them with custom implementations for performance or other reasons. -
forwardRef
iftrue
, theref
prop provided to the connected component will be return the wrapped component.
For more info see the github.com/Workiva/over_react/blob/master/doc/flux_to_redux.md.
Implementation
UiFactory<TProps> Function(UiFactory<TProps>)
connectFlux<TStore extends flux.Store, TActions, TProps extends UiProps>({
Map Function(TStore state)? mapStateToProps,
Map Function(TStore state, TProps ownProps)? mapStateToPropsWithOwnProps,
Map Function(TActions actions)? mapActionsToProps,
Map Function(TActions actions, TProps ownProps)? mapActionsToPropsWithOwnProps,
Map Function(TProps stateProps, TProps dispatchProps, TProps ownProps)?
mergeProps,
// Use default parameter values instead of ??= in the function body to allow consumers
// to specify `null` and fall back to the JS default.
bool Function(TProps nextProps, TProps prevProps) areOwnPropsEqual = propsOrStateMapsEqual,
bool Function(TProps nextProps, TProps prevProps) areStatePropsEqual = propsOrStateMapsEqual,
bool Function(TProps nextProps, TProps prevProps) areMergedPropsEqual = propsOrStateMapsEqual,
Context? context,
bool pure = true,
bool forwardRef = false,
}) {
// Because of the complex relationship between actions and state, it should be
// enforced that a consumer cannot set both `...toProps` and the `withOwnProps`
// variants.
//
// Down stream, `...toProps` is defaulted to in this situation anyway, but at this level,
// allowing both can cause unnecessary complexity in decision logic.
if (mapStateToProps != null && mapStateToPropsWithOwnProps != null) {
throw ArgumentError(
'Both mapStateToProps and mapStateToPropsWithOwnProps cannot be set at the same time.');
}
if (mapActionsToProps != null && mapActionsToPropsWithOwnProps != null) {
throw ArgumentError(
'Both mapActionsToProps and mapActionsToPropsWithOwnProps cannot be set at the same time.');
}
/*--Boolean variables used for creating more complex logic statements--*/
final mapActionsToPropsNeedsToBeWrapped = mapActionsToProps != null;
final mapStateToPropsNeedsToBeWrapped = mapStateToProps != null;
final mapActionsWithOwnPropsNeedsToBeWrapped =
mapActionsToPropsWithOwnProps != null;
final mapStateWithOwnPropsNeedsToBeWrapped =
mapStateToPropsWithOwnProps != null;
final noActionsNeedToBeWrapped = !mapActionsToPropsNeedsToBeWrapped &&
!mapActionsWithOwnPropsNeedsToBeWrapped;
final noStateNeedsToBeWrapped =
!mapStateToPropsNeedsToBeWrapped && !mapStateWithOwnPropsNeedsToBeWrapped;
/*--Boolean variables that represent the possible cases for wrapping parameter functions--*/
/// Wrap mapStateToProps only
final case1 = mapStateToPropsNeedsToBeWrapped && noActionsNeedToBeWrapped;
/// Wrap mapStateToPropsWithOwnProps only
final case2 =
mapStateWithOwnPropsNeedsToBeWrapped && noActionsNeedToBeWrapped;
/// Wrap mapActionsToProps with mapStateToProps
final case3 =
mapStateToPropsNeedsToBeWrapped && mapActionsToPropsNeedsToBeWrapped;
/// Wrap mapActionsToProps only
final case4 = mapActionsToPropsNeedsToBeWrapped && noStateNeedsToBeWrapped;
/// Wrap mapStateToPropsWithOwnProps and mapActionsToPropsWithOwnProps
final case5 = mapActionsWithOwnPropsNeedsToBeWrapped &&
mapStateWithOwnPropsNeedsToBeWrapped;
/// Wrap just mapActionsToPropsWithOwnProps
final case6 =
mapActionsWithOwnPropsNeedsToBeWrapped && noStateNeedsToBeWrapped;
/// Wrap mapStateToProps in mapStateToPropsWithOwnProps
final case7 =
mapStateToPropsNeedsToBeWrapped && mapActionsWithOwnPropsNeedsToBeWrapped;
/// Wrap mapActionToProps with mapStateToPropsWithOwnProps
final case8 =
mapStateWithOwnPropsNeedsToBeWrapped && mapActionsToPropsNeedsToBeWrapped;
/*--Logic block to wrap passed in parameters using the cases from above--*/
// If only mapStateToProps or mapStateToPropsWithOwn props is set, nothing needs
// to be done.
if (!case1 && !case2) {
// Basic case: set mapStateToProps and mapActionsToProps
if (case3) {
final originalMapStateToProps = mapStateToProps;
Map wrappedMapStateToProps(TStore state) {
return {
...originalMapStateToProps(state),
...mapActionsToProps(actionsForStore[state] as TActions),
};
}
mapStateToProps = wrappedMapStateToProps;
}
// Only set mapActionsToProps
if (case4) {
mapStateToProps = (state) {
return {
...mapActionsToProps(actionsForStore[state] as TActions),
};
};
}
// Set both ...WithOwnProps functions
if (case5) {
final originalMapStateWithOwnProps = mapStateToPropsWithOwnProps;
Map wrappedMapStateWithOwnProps(TStore state, TProps ownProps) {
return {
...originalMapStateWithOwnProps(state, ownProps),
...mapActionsToPropsWithOwnProps(
actionsForStore[state] as TActions, ownProps),
};
}
mapStateToPropsWithOwnProps = wrappedMapStateWithOwnProps;
}
// Set only mapActionsToPropsWithOwnProps
if (case6) {
mapStateToPropsWithOwnProps = (state, ownProps) {
return {
...mapActionsToPropsWithOwnProps(
actionsForStore[state] as TActions, ownProps),
};
};
}
// Special case: set both mapStateToProps and mapActionsToPropsWithOwnProps,
// but wrap mapStateToProps in mapStateToPropsWithOwnProps to make the props
// accessible to mapActionsToPropsWithOwnProps
if (case7) {
final newMapStateToProps = mapStateToProps;
mapStateToPropsWithOwnProps = (state, ownProps) {
return {
...newMapStateToProps!(state),
...mapActionsToPropsWithOwnProps(
actionsForStore[state] as TActions, ownProps),
};
};
mapStateToProps = null;
}
// Special case: the converse of case7, set mapStateToPropsWithOwnProps and
// include mapActionsToProps because the actions still need to be set and
// cannot be set on mapStateToProps.
if (case8) {
final originalMapStateWithOwnProps = mapStateToPropsWithOwnProps;
Map wrappedMapStateToPropsWithOwnProps(TStore state, TProps ownProps) {
return {
...originalMapStateWithOwnProps!(state, ownProps),
...mapActionsToProps(actionsForStore[state] as TActions),
};
}
mapStateToPropsWithOwnProps = wrappedMapStateToPropsWithOwnProps;
}
}
/*--end usage of cases--*/
// In dev mode, if areStatePropsEqual is not specified, pass in a version
// that warns for common pitfall cases.
assert(() {
if (areStatePropsEqual == propsOrStateMapsEqual) {
bool areStatePropsEqualWrapper(TProps nextProps, TProps prevProps) {
const propHasher = CollectionLengthHasher();
prevProps.forEach((key, value) {
// If the value is the same instance, check if the instance has been mutated,
// causing its hash to be updated
if (identical(value, nextProps[key]) &&
propHasher.hasHashChanged(value)) {
window.console.warn(
'connect: The instance of the value mapped from store "$TStore" to prop "$key" was mutated directly, which will prevent updates from being detected.'
' Instead of mutating datastructure instances within the store, overwrite them with modified copies.\n'
'\n Good: `_items = [..._items, newItem]`'
'\n Bad: `_items.add(newItem)`'
'\n\nIf this is not possible, then either:'
'\n - update `areStatePropsEqual` to reflect this behavior: `areStatePropsEqual: (_, __) => false`'
'\n - set `pure: false`');
}
});
return propsOrStateMapsEqual(nextProps, prevProps);
}
areStatePropsEqual = areStatePropsEqualWrapper;
}
return true;
}());
return connect(
mapStateToProps: mapStateToProps,
mapStateToPropsWithOwnProps: mapStateToPropsWithOwnProps,
mergeProps: mergeProps,
areOwnPropsEqual: areOwnPropsEqual,
areStatePropsEqual: areStatePropsEqual,
areMergedPropsEqual: areMergedPropsEqual,
context: context,
pure: pure,
forwardRef: forwardRef,
// Don't pass along the dispatcher as props by default
mapDispatchToProps: (_) => const {},
// This will always be false, since both arguments will also be the same store
areStatesEqual: (dynamic _, dynamic __) => false,
);
}