Monday, August 27, 2018

Combine async redux actions in SPFx react components

If you work with SPFx and e.g. implement web part which uses react/redux for its components then you may face with need to combine multiple async redux actions into single one. Let’s assume that we have components which shows O365 groups and has appropriate properties and actions for it:

import * as React from 'react';
import { connect } from 'react-redux';
import { IGroup } from 'IGroup';
import * as GroupActions from 'groupActions';

export interface GroupListProps {
  groups: IGroup[];
  actions: {
    getGroups: GroupActions.IGetGroups,
  };
}

class GroupList extends React.Component<GroupListProps, {}> {
  public componentWillMount(): void {
    if (!this.props.groups) {
      this.props.actions.getGroups();
    }
  }

  public render() {
    return (
      this.props.groups ? <GroupsList items={this.props.groups} /> : <LoadingSpinner />
    );
  }
}

const mapStateToProps = (state: GroupListState) => ({
  groups: state.groups,
});

const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
  actions: {
    getGroups: () => dispatch(GroupActions.getGroupsImpl()),
  }
});

/**
 * Connecting the store to the component
 */
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(GroupList);

After that we decide to show both regular Sharepoint sites together with groups. We need to action for getting sites themselves (asynchronously using promises) and another action for combining groups and sites into single list. Both actions ae asynchronous. Let’s see how we may combine them to single one.

At first start with adding new action for sites:

import * as React from 'react';
import { connect } from 'react-redux';
import { IGroup } from 'IGroup';
import { ISite } from 'ISite';
import * as GroupActions from 'groupActions';
import * as SiteActions from 'siteActions';

export interface GroupListProps {
  groups: IGroup[];
  sites: ISite[];
  actions: {
    getGroups: GroupActions.IGetGroups,
	getSites: SiteActions.IGetSites,
  };
}

class GroupListContainer extends React.Component<GroupListProps, {}> {
  public componentWillMount(): void {
    if (!this.props.groups) {
      this.props.actions.getGroups();
    }

    if (!this.props.sites) {
      this.props.actions.getSites();
    }
  }

  public render() {
    return (
      this.props.groups ? <GroupsList items={this.props.groups} /> : <LoadingSpinner />
    );
  }
}

const mapStateToProps = (state: GroupListState) => ({
  groups: state.groups,
  sites: state.sites
});

const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
  actions: {
    getGroups: () => dispatch(GroupActions.getGroupsImpl()),
	getSites: () => dispatch(SiteActions.getSitesImpl()),
  }
});

/**
 * Connecting the store to the component
 */
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(GroupList);

Then create new combined action which calls 2 other actions (getGroups and getSites) via dispatch and additional action which will combine 2 groups and sites to single list:

import * as React from 'react';
import { connect } from 'react-redux';
import { IGroup } from 'IGroup';
import { ISite } from 'ISite';
import { IGroupOrSite } from 'IGroupOrSite';
import * as GroupActions from 'groupActions';
import * as SiteActions from 'siteActions';
import * as GroupOrSiteActions from 'groupOrSiteActions';

export interface GroupListProps {
  groups: IGroup[];
  sites: ISite[];
  groupsOrSites: IGroupOrSite[];
  actions: {
	getGroupsAndSites: GroupOrSiteActions.IGetGroupsAndSites
	combineGroupsAndSites: GroupOrSiteActions.ICombineGroupsAndSites
  };
}

class GroupListContainer extends React.Component<GroupListProps, {}> {
  public componentWillMount(): void {
    if (!this.props.groupsOrSites) {
      this.props.actions.getGroupsAndSites();
    }
  }

  public componentDidUpdate(prevProps: GroupListProps) {
    // Combine groups and sites
    if ((!isEqual(this.props.groups, prevProps.groups) || !isEqual(this.props.sites, prevProps.sites)) &&
	  this.props.groups && this.props.sites) {
	  this.props.actions.combineGroupsAndSites(this.props.groups, this.props.sites);
    }
  }
  
  public render() {
    return (
      this.props.groupsOrSites ? <GroupsList items={this.props.groupsOrSites} /> : <LoadingSpinner />
    );
  }
}

const mapStateToProps = (state: GroupListState) => ({
  groups: state.groups,
  sites: state.sites,
  groupsOrSites: state.groupsOrSites
});

const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
  actions: {
	getGroupsAndSites: () => {
		dispatch(GroupActions.getGroupsImpl());
		return dispatch(SiteActions.getSitesImpl());
	},
	getGroupsAndSites: (groups: IGroup[], sites: ISite[]) => dispatch(GroupOrSiteActions.combineSitesImpl(groups, sites))
  }
});

/**
 * Connecting the store to the component
 */
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(GroupList);

As result when you will call this combined action it will perform 2 async sub actions.

No comments:

Post a Comment