Merge pull request #3050 from bcameron1231/pnp-js-hooks

New Sample - PnPJs V3 with Hooks
This commit is contained in:
Hugo Bernier 2022-10-17 14:59:09 -04:00 committed by GitHub
commit df0d103402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 25884 additions and 0 deletions

View File

@ -0,0 +1,5 @@
require("@rushstack/eslint-config/patch/modern-module-resolution");
module.exports = {
extends: ["@microsoft/eslint-config-spfx/lib/profiles/react"],
parserOptions: { tsconfigRootDir: __dirname },
};

34
samples/react-pnp-js-hooks/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
release
solution
temp
*.sppkg
.heft
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,16 @@
!dist
config
gulpfile.js
release
src
temp
tsconfig.json
tslint.json
*.log
.yo-rc.json
.vscode

View File

@ -0,0 +1,14 @@
{
"@microsoft/generator-sharepoint": {
"plusBeta": true,
"isCreatingSolution": true,
"environment": "spo",
"version": "1.15.2",
"libraryName": "spfx-pnp-js-example",
"libraryId": "d20ceaf6-094b-4086-b7a0-85761bc8be23",
"packageManager": "npm",
"solutionShortDescription": "spfx-pnp-js-example description",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,101 @@
---
page_type: sample
products:
- office-sp
languages:
- javascript
- typescript
extensions:
contentType: samples
technologies:
- SharePoint Framework
platforms:
- react
createdDate: 10/10/2022 12:00:00 AM
---
# @pnp/js and ReactJS Functional Components
## Summary
This solution builds off of the solution [react-async-await-sp-pnp-js](./react-async-await-sp-pnp-js) submitted by Jose Quinto ([@jquintozamora](https://twitter.com/jquintozamora) , [blog.josequinto.com](https://blog.josequinto.com)) and re-work of the existing class based [react-pnp-js-sample](./react-pnp-js-sample) by [Julie Turner](https://twitter.com/jfj1997)
This implementation refactors to take aspects out and utilize and showcase PnPjs Version 3 using React Functional Components and Hooks.
![React-pnp-js-sample](./assets/react-pnp-js-sample.png)
## Compatibility
This sample is optimally compatible with the following environment configuration:
![SPFx 1.15.2](https://img.shields.io/badge/SPFx-1.15.2-green.svg)
![Node.js v16 | v14 | v12](https://img.shields.io/badge/Node.js-v16%20%7C%20v14%20%7C%20v12-green.svg)
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
![Local Workbench Unsupported](https://img.shields.io/badge/Local%20Workbench-Unsupported-red.svg "Local workbench is no longer available as of SPFx 1.13 and above")
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)
For more information about SPFx compatibility, please refer to <https://aka.ms/spfx-matrix>
## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Microsoft 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/m365devprogram)
## Solution
Solution|Author(s)
--------|---------
react-pnp-js-hooks | [Beau Cameron](https://github.com/bcameron1231) ([@beau__cameron](https://twitter.com/Beau__Cameron))
## Version history
Version|Date|Comments
-------|----|--------
1.0|Oct 09, 2022|Initial release
## Minimal Path to Awesome
* Clone this repository (or [download this solution as a .ZIP file](https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnp-js-hooks) then unzip it)
* From your command line, change your current directory to the directory containing this sample (`react-pnp-js-hooks`, located under `samples`)
* in the command line run:
* `npm install`
* Update online workbench URL in the `initialPage` property of the `config/serve.json` file.
* `gulp serve`
## Features
* Establishing context for the SharePoint Factory Interface
* Creating a project config file to centralize defining the PnPjs imports and SharePoint Queryable object for reuse.
* Demo using PnPjs with React Functional Components & Hooks
* Demo extending the SharePoint Queryables instance with the PnPLogging behavior.
* Demo extending the SharePoint Queryable instance with the Caching behavior
* Demo loading list items from a SharePoint library
* Demo creating a batched instance of the SharePoint Queryable object.
* Demo updating list items by modifying the Title property.
* Demo executing a batch and working with the results.
## Help
We do not support samples, but this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
If you're having issues building the solution, please run [spfx doctor](https://pnp.github.io/cli-microsoft365/cmd/spfx/spfx-doctor/) from within the solution folder to diagnose incompatibility issues with your environment.
You can try looking at [issues related to this sample](https://github.com/pnp/sp-dev-fx-webparts/issues?q=label%3A%22sample%3A%20react-pnp-js-hooks%22) to see if anybody else is having the same issues.
You can also try looking at [discussions related to this sample](https://github.com/pnp/sp-dev-fx-webparts/discussions?discussions_q=react-pnp-js-hooks) and see what the community is saying.
If you encounter any issues using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected%2Csample%3A%20react-pnp-js-hooks&template=bug-report.yml&sample=react-pnp-js-hooks&authors=@bcameron1231&title=react-pnp-js-hooks%20-%20).
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Aquestion%2Csample%3A%20react-pnp-js-hooks&template=question.yml&sample=react-pnp-js-hooks&authors=@bcameron1231&title=react-pnp-js-hooks%20-%20).
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Aenhancement%2Csample%3A%20react-pnp-js-hooks&template=suggestion.yml&sample=react-pnp-js-hooks&authors=@bcameron1231&title=react-pnp-js-hooks%20-%20).
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
<img src="https://pnptelemetry.azurewebsites.net/sp-dev-fx-webparts/samples/react-pnp-js-hooks" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,50 @@
[
{
"name": "pnp-sp-dev-spfx-web-parts-react-pnp-js-hooks",
"source": "pnp",
"title": "pnp/js and ReactJS Functional Components",
"shortDescription": "This solution builds off of the solution react-async-await-sp-pnp-js submitted by Jose Quinto, and re-work of the existing class based react-pnp-js-sample by Julie Turner. This implementation refactors to take aspects out and utilize and showcase PnPjs Version 3 using React Functional Components and Hooks.",
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnp-js-hooks",
"downloadUrl": "https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-pnp-js-hooks",
"longDescription": [
"This solution builds off of the solution react-async-await-sp-pnp-js submitted by Jose Quinto, and re-work of the existing class based react-pnp-js-sample by Julie Turner. This implementation refactors to take aspects out and utilize and showcase PnPjs Version 3 using React Functional Components and Hooks."
],
"creationDateTime": "2022-10-17",
"updateDateTime": "2022-10-17",
"products": [
"SharePoint"
],
"metadata": [
{
"key": "CLIENT-SIDE-DEV",
"value": "React"
},
{
"key": "SPFX-VERSION",
"value": "1.15.2"
}
],
"thumbnails": [
{
"type": "image",
"order": 100,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-pnp-js-hooks/assets/react-pnp-js-sample.png",
"alt": "Web Part Preview"
}
],
"authors": [
{
"gitHubAccount": "bcameron1231",
"pictureUrl": "https://github.com/bcameron1231.png",
"name": "Beau Cameron"
}
],
"references": [
{
"name": "Build your first SharePoint client-side web part",
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
}
]
}
]

View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"pn-pjs-example-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/pnPjsExample/PnPjsExampleWebPart.js",
"manifest": "./src/webparts/pnPjsExample/PnPjsExampleWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"PnPjsExampleWebPartStrings": "lib/webparts/pnPjsExample/loc/{locale}.js"
}
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./release/assets/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spfx-pnp-js-example",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,40 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "spfx-pnp-js-example-client-side-solution",
"id": "d20ceaf6-094b-4086-b7a0-85761bc8be23",
"version": "1.1.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"developer": {
"name": "",
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": "Undefined-1.14.0-beta.4"
},
"metadata": {
"shortDescription": {
"default": "spfx-pnp-js-example description"
},
"longDescription": {
"default": "spfx-pnp-js-example description"
},
"screenshotPaths": [],
"videoUrl": "",
"categories": []
},
"features": [
{
"title": "spfx-pnp-js-example Feature",
"description": "The feature that activates elements of the spfx-pnp-js-example solution.",
"id": "97ead3d0-6478-4aa5-abba-151b0e9b76c9",
"version": "1.0.0.0"
}
]
},
"paths": {
"zippedPackage": "solution/spfx-pnp-js-example.sppkg"
}
}

View File

@ -0,0 +1,6 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://enter-your-SharePoint-site/_layouts/workbench.aspx"
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

16
samples/react-pnp-js-hooks/gulpfile.js vendored Normal file
View File

@ -0,0 +1,16 @@
'use strict';
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
var result = getTasks.call(build.rig);
result.set('serve', result.get('serve-deprecated'));
return result;
};
build.initialize(require('gulp'));

25059
samples/react-pnp-js-hooks/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
{
"name": "spfx-pnp-js-example",
"version": "1.1.0",
"private": true,
"main": "lib/index.js",
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.15.2",
"@microsoft/sp-lodash-subset": "1.15.2",
"@microsoft/sp-office-ui-fabric-core": "1.15.2",
"@microsoft/sp-property-pane": "1.15.2",
"@microsoft/sp-webpart-base": "1.15.2",
"@pnp/logging": "^3.6.0",
"@pnp/sp": "^3.6.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"tslib": "2.3.1"
},
"devDependencies": {
"@microsoft/rush-stack-compiler-4.5": "^0.2.2",
"@microsoft/sp-build-web": "1.15.2",
"@microsoft/sp-module-interfaces": "1.15.2",
"@types/react": "16.9.51",
"@types/react-dom": "16.9.8",
"@types/webpack-env": "1.15.2",
"ajv": "6.12.5",
"gulp": "~4.0.2",
"@rushstack/eslint-config": "2.5.1",
"@microsoft/eslint-plugin-spfx": "1.15.2",
"@microsoft/eslint-config-spfx": "1.15.2",
"eslint": "8.7.0",
"eslint-plugin-react-hooks": "4.3.0"
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,28 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "72f602dd-5b1b-4a6e-ae8f-83c9544cb711",
"alias": "PnPjsExampleWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
"supportsThemeVariants": true,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "PnPJSExample" },
"description": { "default": "PnPJSExample description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "PnPJSExample"
}
}]
}

View File

@ -0,0 +1,107 @@
import * as React from "react";
import * as ReactDom from "react-dom";
import { Version } from "@microsoft/sp-core-library";
import {
IPropertyPaneConfiguration,
PropertyPaneTextField,
} from "@microsoft/sp-property-pane";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { IReadonlyTheme } from "@microsoft/sp-component-base";
import * as strings from "PnPjsExampleWebPartStrings";
import { PnPjsExample } from "./components/PnPjsExample";
import { IPnPjsExampleProps } from "./components/IPnPjsExampleProps";
import { getSP } from "./pnpjsConfig";
export interface IPnPjsExampleWebPartProps {
description: string;
}
export default class PnPjsExampleWebPart extends BaseClientSideWebPart<IPnPjsExampleWebPartProps> {
private _isDarkTheme = false;
private _environmentMessage = "";
protected async onInit(): Promise<void> {
this._environmentMessage = this._getEnvironmentMessage();
super.onInit();
//Initialize our _sp object that we can then use in other packages without having to pass around the context.
// Check out pnpjsConfig.ts for an example of a project setup file.
getSP(this.context);
}
public render(): void {
const element: React.ReactElement<IPnPjsExampleProps> = React.createElement(
PnPjsExample,
{
description: this.properties.description,
isDarkTheme: this._isDarkTheme,
environmentMessage: this._environmentMessage,
hasTeamsContext: !!this.context.sdks.microsoftTeams,
userDisplayName: this.context.pageContext.user.displayName,
}
);
ReactDom.render(element, this.domElement);
}
private _getEnvironmentMessage(): string {
if (!this.context.sdks.microsoftTeams) {
// running in Teams
return this.context.isServedFromLocalhost
? strings.AppLocalEnvironmentTeams
: strings.AppTeamsTabEnvironment;
}
return this.context.isServedFromLocalhost
? strings.AppLocalEnvironmentSharePoint
: strings.AppSharePointEnvironment;
}
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
if (!currentTheme) {
return;
}
this._isDarkTheme = !!currentTheme.isInverted;
const { semanticColors } = currentTheme;
this.domElement.style.setProperty("--bodyText", semanticColors.bodyText);
this.domElement.style.setProperty("--link", semanticColors.link);
this.domElement.style.setProperty(
"--linkHovered",
semanticColors.linkHovered
);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse("1.0");
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription,
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField("description", {
label: strings.DescriptionFieldLabel,
}),
],
},
],
},
],
};
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,7 @@
export interface IPnPjsExampleProps {
description: string;
isDarkTheme: boolean;
environmentMessage: string;
hasTeamsContext: boolean;
userDisplayName: string;
}

View File

@ -0,0 +1,34 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.pnPjsExample {
overflow: hidden;
padding: 1em;
color: "[theme:bodyText, default: #323130]";
color: var(--bodyText);
&.teams {
font-family: $ms-font-family-fallbacks;
}
}
.welcome {
text-align: center;
}
.welcomeImage {
width: 100%;
max-width: 420px;
}
.links {
a {
text-decoration: none;
color: "[theme:link, default:#03787c]";
color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only
&:hover {
text-decoration: underline;
color: "[theme:linkHovered, default: #014446]";
color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only
}
}
}

View File

@ -0,0 +1,63 @@
import * as React from "react";
import styles from "./PnPjsExample.module.scss";
import { IPnPjsExampleProps } from "./IPnPjsExampleProps";
// import interfaces & hooks
import { Label, PrimaryButton } from "@microsoft/office-ui-fabric-react-bundle";
import useDocuments from "../hooks/useDocuments";
export interface IAsyncAwaitPnPJsProps {
description: string;
}
export const PnPjsExample: React.FC<IPnPjsExampleProps> = (props) => {
//use custom hook for CRUD operations with documents
const [documents, updateDocuments, totalSize, isError] = useDocuments();
if (!isError) {
return (
<div className={styles.pnPjsExample}>
<Label>
Welcome to PnP JS Version 3 Demo with Functional Components + Hooks!
</Label>
<PrimaryButton onClick={updateDocuments}>
Update Item Titles
</PrimaryButton>
<Label>List of documents:</Label>
<table width="100%">
<tr>
<td>
<strong>Title</strong>
</td>
<td>
<strong>Name</strong>
</td>
<td>
<strong>Size (KB)</strong>
</td>
</tr>
{documents.map((document, idx) => {
return (
<tr key={idx}>
<td>{document.Title}</td>
<td>{document.Name}</td>
<td>{(document.Size / 1024).toFixed(2)}</td>
</tr>
);
})}
<tr>
<td>&nbps;</td>
<td>
<strong>Total:</strong>
</td>
<td>
<strong>{(totalSize / 1024).toFixed(2)}</strong>
</td>
</tr>
</table>
</div>
);
} else {
return <p>Error</p>;
}
};

View File

@ -0,0 +1,20 @@
// create File item to work with it internally
export interface IFile {
Id: number;
Title: string;
Name: string;
Size: number;
}
// create PnP JS response interface for File
export interface IResponseFile {
Length: number;
}
// create PnP JS response interface for Item
export interface IResponseItem {
Id: number;
File: IResponseFile;
FileLeafRef: string;
Title: string;
}

View File

@ -0,0 +1,135 @@
import { Logger, LogLevel } from "@pnp/logging";
import { spfi, SPFI } from "@pnp/sp";
import { useEffect, useState } from "react";
import { IFile, IResponseItem } from "../components/interfaces";
import { getSP } from "../pnpjsConfig";
import { Caching } from "@pnp/queryable";
import { IItemUpdateResult } from "@pnp/sp/items";
const useDocuments = () => {
const LOG_SOURCE = "🅿PnPjsExample";
const LIBRARY_NAME = "Documents";
const [documents, setDocuments] = useState<IFile[]>([]);
const [totalSize, setTotalSize] = useState<number>(0);
const [isError, setError] = useState<Boolean>(false);
const _sp: SPFI = getSP();
//side effect with empty depdency array, means to run once
useEffect(() => {
//mount logic to prevent updates to state when unmounted. Typically we'd use AbortController with Fetch, just can't with PnPjs
let mounted = true;
(async () => {
try {
// do PnP JS query, some notes:
// - .expand() method will retrive Item.File item but only Length property
// - .get() always returns a promise
// - await resolves proimises making your code act syncronous, ergo Promise<IResponseItem[]> becomes IResponse[]
//Extending our sp object to include caching behavior, this modification will add caching to the sp object itself
//this._sp.using(Caching("session"));
//Creating a new sp object to include caching behavior. This way our original object is unchanged.
const spCache = spfi(_sp).using(Caching({ store: "session" }));
const response: IResponseItem[] = await spCache.web.lists
.getByTitle(LIBRARY_NAME)
.items.select("Id", "Title", "FileLeafRef", "File/Length")
.expand("File/Length")();
// use map to convert IResponseItem[] into our internal object IFile[]
const documents: IFile[] = response.map((item: IResponseItem) => {
return {
Id: item.Id,
Title: item.Title || "Unknown",
Size: item.File?.Length || 0,
Name: item.FileLeafRef,
};
});
//only update state if we are still mounted
if (mounted) {
// Add the items and totalsize to the state of the hook
setDocuments(documents);
setTotalSize(
documents.length > 0
? documents.reduce<number>((acc: number, item: IFile) => {
return acc + Number(item.Size);
}, 0)
: 0
);
}
} catch (err) {
if (mounted) {
setError(true);
}
Logger.write(
`${LOG_SOURCE} (getting files useEffect) - ${JSON.stringify(err)} - `,
LogLevel.Error
);
}
//best practice to return a cleanup method in scenarios where component unmounts before completion
return () => (mounted = false);
})();
}, []);
const updateDocuments = async () => {
//mount logic to prevent updates to state when unmounted. Typically we'd use AbortController with Fetch, just can't with PnPjs
let mounted = true;
try {
const [batchedSP, execute] = _sp.batched();
//clone documents
const items = JSON.parse(JSON.stringify(documents));
const res: IItemUpdateResult[] = [];
for (let i = 0; i < items.length; i++) {
// you need to use .then syntax here as otherwise the application will stop and await the result
batchedSP.web.lists
.getByTitle(LIBRARY_NAME)
.items.getById(items[i].Id)
.update({ Title: `${items[i].Name}-Updated` })
.then((r) => res.push(r));
}
// Executes the batched calls
await execute();
// Results for all batched calls are available
for (let i = 0; i < res.length; i++) {
//If the result is successful update the item
//NOTE: This code is over simplified, you need to make sure the Id's match
const item = await res[i].item.select("Id, Title")<{
Id: number;
Title: string;
}>();
items[i].Name = item.Title;
}
//only update state if we are still mounted
if (mounted) {
//Update the state
setDocuments(items);
setError(false);
}
} catch (err) {
if (mounted) {
setError(true);
}
Logger.write(
`${LOG_SOURCE} (updating titles) - ${JSON.stringify(err)} - `,
LogLevel.Error
);
}
//best practice to return a cleanup method in scenarios where component unmounts before completion
return () => (mounted = false);
};
return [documents, updateDocuments, totalSize, isError] as const;
};
export default useDocuments;

View File

@ -0,0 +1,11 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field",
"AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",
"AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",
"AppSharePointEnvironment": "The app is running on SharePoint page",
"AppTeamsTabEnvironment": "The app is running in Microsoft Teams"
}
});

View File

@ -0,0 +1,14 @@
declare interface IPnPjsExampleWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
AppLocalEnvironmentSharePoint: string;
AppLocalEnvironmentTeams: string;
AppSharePointEnvironment: string;
AppTeamsTabEnvironment: string;
}
declare module 'PnPjsExampleWebPartStrings' {
const strings: IPnPjsExampleWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,21 @@
import { WebPartContext } from "@microsoft/sp-webpart-base";
// import pnp and pnp logging system
import { spfi, SPFI, SPFx } from "@pnp/sp";
import { LogLevel, PnPLogging } from "@pnp/logging";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/batching";
// eslint-disable-next-line no-var
var _sp: SPFI = null;
export const getSP = (context?: WebPartContext): SPFI => {
if (context != null) {
//You must add the @pnp/logging package to include the PnPLogging behavior it is no longer a peer dependency
// The LogLevel set's at what level a message will be written to the console
_sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
}
return _sp;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

View File

@ -0,0 +1,35 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-4.5/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection",
"es2015.promise"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
]
}