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.

Tuesday, August 21, 2018

Enable SP Editor Chrome extension in private (incognito) Chrome mode

Some time ago I wrote about very convenient Chrome extension SP Editor (see this post: SP Editor Chrome extension: free open source alternative to Sharepoint Designer). It allows to perform many operations with your Sharepoint Online or on-prem site right in the browser without running any scripts or installing additional tools. However like usual Chrome extension it is disabled by default in private (incognito) Chrome mode. This is not very convenient because when you work with Sharepoint Online you often need to login to different sites with different accounts and incognito mode is often used for that.

The good thing is that it is quite easy to enable SP Editor for incognito mode: go to Chrome menu > More tools > Extensions > SP Editor > Details. In opened window click “Allow in incognito”:

image

After that extension will become available in private mode (you will need to re-open F12 developer tools to see SharePoint tab from SP Editor there).