connectFlux<TStore extends Store, TActions, TProps extends UiProps> function

UiFactory<TProps> Function(UiFactory<TProps> ) connectFlux<TStore extends Store, TActions, TProps extends UiProps>({
  1. Map mapStateToProps(
    1. TStore state
    )?,
  2. Map mapStateToPropsWithOwnProps(
    1. TStore state,
    2. TProps ownProps
    )?,
  3. Map mapActionsToProps(
    1. TActions actions
    )?,
  4. Map mapActionsToPropsWithOwnProps(
    1. TActions actions,
    2. TProps ownProps
    )?,
  5. Map mergeProps(
    1. TProps stateProps,
    2. TProps dispatchProps,
    3. TProps ownProps
    )?,
  6. bool areOwnPropsEqual(
    1. TProps nextProps,
    2. TProps prevProps
    ) = propsOrStateMapsEqual,
  7. bool areStatePropsEqual(
    1. TProps nextProps,
    2. TProps prevProps
    ) = propsOrStateMapsEqual,
  8. bool areMergedPropsEqual(
    1. TProps nextProps,
    2. TProps prevProps
    ) = propsOrStateMapsEqual,
  9. Context? context,
  10. bool pure = true,
  11. 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 no areStatesEqual 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 be ownProps. 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.

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 if true (default), connect performs several equality checks that are used to avoid unnecessary calls to mapStateToProps, mapActionsToProps, mergeProps, and ultimately to render. These include areOwnPropsEqual, areStatePropsEqual, and areMergedPropsEqual. 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 if true, the ref 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,
  );
}