Introduction
Hi everyone, this is part 17 of PnP React Controls Series. This post will explain the usage of the PnP Toolbar control. We will see how we can leverage this control on our SPFx projects and make use of the feature provided by this control. Thanks to the community.
About the Control
This control is very important since it saves some time for the devs and also provide a friendly UI with some advanced features to the end users. Mostly this control is used along with DetailsGrid. By design this control comes with an option to configure the buttons, filter and search and there are also relevant properties to map the methods for the filter and search functionality.
Focus on the Code
You can refer Part 1 post on the creation of the SPFx project and installing all the dependencies. In addition to the dependencies mentioned in that post you have to install @pnp/sp – v3.5.1. You should also do some changes to the project files to make the pnpjs version 3 to work on SPFx 1.14.0. Please follow this link to do the changes.
Created a webpart named toolbarDemo. Below is the ToolbarDemo.tsx file.
import * as React from 'react';
import { useState } from 'react';
import styles from './ToolbarDemo.module.scss';
import { IToolbarDemoProps } from './IToolbarDemoProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { Toolbar } from '@pnp/spfx-controls-react/lib/Toolbar';
import { DetailsList, DetailsListLayoutMode, Selection, SelectionMode, IColumn } from '@fluentui/react/lib/DetailsList';
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import { map } from 'lodash';
const ToolbarDemo: React.FC<IToolbarDemoProps> = (props) => {
const {
description,
isDarkTheme,
environmentMessage,
hasTeamsContext,
userDisplayName
} = props;
const [loading, setLoading] = useState<boolean>(true);
const [items, setItems] = useState<any[]>(undefined);
const [filitems, setFilItems] = useState<any[]>(undefined);
const [itemCats, setItemCats] = useState<string[]>(undefined);
const [gridCols, setGridCols] = React.useState<IColumn[]>(undefined);
const [selectionDetails, setSelectionDetails] = useState<string>(undefined);
const columns: IColumn[] = [
{
key: 'colTitle',
name: 'Title',
fieldName: 'Title',
minWidth: 100,
maxWidth: 200,
onRender: (item?: any, index?: number, column?: IColumn) => {
return (
<div>{item?.Title}</div>
);
}
},
{
key: 'colDescription',
name: 'Description',
fieldName: 'Description',
minWidth: 100,
onRender: (item?: any, index?: number, column?: IColumn) => {
return (
<div>{item?.Description}</div>
);
}
},
{
key: 'colCategory',
name: 'Category',
fieldName: 'Category',
minWidth: 80,
onRender: (item?: any, index?: number, column?: IColumn) => {
return (
<div>{item?.Category}</div>
);
}
}
];
const _getKey = (item: any, index?: number): string => {
return item.key;
};
const _getSelectionDetails = (): string => {
let selID: string = undefined;
if (_selection.getSelectedCount() > 0) {
selID = (_selection.getSelection()[0] as any).ID;
}
return selID;
};
const _selection = new Selection({
onSelectionChanged: () => {
setSelectionDetails(_getSelectionDetails());
}
});
const _loadListitems = async () => {
try {
const items: any[] = await props.sp.web.lists.getByTitle('Announcements')
.select('ID', 'Title', 'Description', 'Category')
.items();
const tempCats: string[] = items.map((obj) => obj.Category).filter((val, index, self) => self.indexOf(val) === index);
const itemCats: any[] = [];
tempCats.map((val: any) => {
itemCats.push({ id: val, title: `${val}` });
});
setItemCats(itemCats);
setItems(items);
setFilItems(items);
setGridCols(columns);
setLoading(false);
} catch (err) {
console.log(err);
}
};
const _editSelection = () => {
if (selectionDetails) console.log(`Selected item Category: ${selectionDetails}`);
else console.log('Please select any item from the Grid!');
};
const _findItem = (findQuery: string) => {
if (items && findQuery) {
let filItems: any[] = items.filter((o: any) => o.Title?.toLowerCase().indexOf(findQuery.toLowerCase()) >= 0);
setFilItems(filItems);
} else setFilItems(items);
return '';
};
const _filterItems = (selectedFilters: string[]): string[] => {
console.log(selectedFilters);
if (selectedFilters.length > 0) {
let filItems: any[] = items.filter((item) => selectedFilters.indexOf(item.Category) !== -1);
setFilItems(filItems);
} else setFilItems(items);
return selectedFilters;
};
React.useEffect(() => {
_loadListitems();
}, []);
return (
<section className={`${styles.toolbarDemo} ${hasTeamsContext ? styles.teams : ''}`}>
<div className={styles.welcome}>
<img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
</div>
<div>
<h3>Welcome to SharePoint Framework!</h3>
{loading ? (
<div>Loading...</div>
) : (
<>
<Toolbar actionGroups={{
'group1': {
'action1': {
title: 'Edit',
iconName: 'Edit',
onClick: _editSelection
},
'action2': {
title: 'New',
iconName: 'Add',
onClick: () => { console.log('New action click'); }
}
}
}}
filters={[{
id: "f1",
title:
"Categories",
items: itemCats
}
]}
find={true}
onFindQueryChange={_findItem}
onSelectedFiltersChange={_filterItems} />
<DetailsList
items={filitems || items}
columns={columns}
selectionMode={SelectionMode.single}
getKey={_getKey}
layoutMode={DetailsListLayoutMode.justified}
isHeaderVisible={true}
selection={_selection}
selectionPreservedOnEmptyClick={true}
enterModalSelectionOnTouch={true}
/>
</>
)}
</div>
</section>
);
};
export default ToolbarDemo;
DetailsGrid is added extra to explain the usage of the Toolbar control. Below are the updates done to the file.
- Changed the class component to the react hooks
- Configured the toolbar to have 2 buttons. Edit and Add. When you click you can open a dialog or panel with the add form, for demo purpose I had wrote the message to the console. Edit is fired with the selected item whenever the user selected an item in the DetailList.
actionGroups = {{
'group1': {
'action1': {
title: 'Edit',
iconName: 'Edit',
onClick: _editSelection
},
'action2': {
title: 'New',
iconName: 'Add',
onClick: () => { console.log('New action click'); }
}
}
}}
const _getSelectionDetails = (): string => {
let selID: string = undefined;
if (_selection.getSelectedCount() > 0) {
selID = (_selection.getSelection()[0] as any).ID;
}
return selID;
};
const _selection = new Selection({
onSelectionChanged: () => {
setSelectionDetails(_getSelectionDetails());
}
});
const _editSelection = () => {
if (selectionDetails) console.log(`Selected item Category: ${selectionDetails}`);
else console.log('Please select any item from the Grid!');
};
- I have used a Category field in the list for filter items and below is the code to load the filter items
filters = {
[{
id: "f1",
title:
"Categories",
items: itemCats
}
const _loadListitems = async () => {
try {
const items: any[] = await props.sp.web.lists.getByTitle('Announcements')
.select('ID', 'Title', 'Description', 'Category')
.items();
const tempCats: string[] = items.map((obj) => obj.Category).filter((val, index, self) => self.indexOf(val) === index);
const itemCats: any[] = [];
tempCats.map((val: any) => {
itemCats.push({ id: val, title: `${val}` });
});
setItemCats(itemCats);
setItems(items);
setFilItems(items);
setGridCols(columns);
setLoading(false);
} catch (err) {
console.log(err);
}
};
const _filterItems = (selectedFilters: string[]): string[] => {
console.log(selectedFilters);
if (selectedFilters.length > 0) {
let filItems: any[] = items.filter((item) => selectedFilters.indexOf(item.Category) !== -1);
setFilItems(filItems);
} else setFilItems(items);
return selectedFilters;
};
- Method to find the items based on the test entered in the find textbox
onFindQueryChange={_findItem}
const _findItem = (findQuery: string) => {
if (items && findQuery) {
let filItems: any[] = items.filter((o: any) => o.Title?.toLowerCase().indexOf(findQuery.toLowerCase()) >= 0);
setFilItems(filItems);
} else setFilItems(items);
return '';
};
Reference URL
Toolbar – @pnp/spfx-controls-react
Sample Implementation

Conclussion
I hope you had learned something about one of the pnp control. There are lot to come in future.
Please free to post your comments and let me know if you want me to post article on any particular feature or plugins that relates to SharePoint. Thanks for your support.
sudharsank/pnpcontrols-demo: Demo project on different PnP React Controls. (github.com)
Happy Coding…