ReactHooks with Spfx

Hooks Overview

 
React Hooks are a way to add React.Component features to functional components. Features include:
  • State
  • Lifecycle
Hooks let you use React's features without classes.
 
 

What is this useState() syntax?

 
 
You may be unfamiliar with the useState() syntax. This uses destructuring assignment for arrays. It is similar to how we would pull props out an object with object destructuring.
 

What does useState() give us?

 
 
useState gives us two variables. We can name our two variables whatever we want, just know that:
  • The first variable is the value. Similar to this.state
  • The second variable is a function to update that value. Similar to this.setState
  • The final part of useState is the argument that we pass to it. The useState argument is the initial state value. In the case of our counter, we started at 0.

What's wrong with classes?

 
The React Hooks intro gives a good section on this: Classes confuse both people and machines. Classes can sometimes be confusing and be written in any number of ways. Dive into somebody else's project and you could be in for a world of different syntax and style choices. Though there are no plans to remove class support. we just have another way to code.
 
By allowing classes to be converted into smaller functional components, we can further break out parts of our application into more focused components.
 

React's Effect Hook

 
 
The State Hook allows us to use state in React functional components. This gets us a step closer to using functional components over class components. The next part of moving to functional components is lifecycle methods.
 
Effects are similar to componentDidMount, componentDidUpdate, and componentWillUnmount
 
This is what the Effect Hook is for. Side-effects are things you want your application to do such as:
  • Fetch data
  • Manually change the DOM (document title)
  • Set up a subscription
  • Run effects after every render

Running an Effect Hook only when something changes

 
 
Since useEffect() runs every time a component renders, how do we get it to only run once on mount? The Effect Hook can take a second argument, an array. It will look through the array and only run the effect if one of the values has changed.
  1. componentDidMount: Runs once  
  2. // only run on mount. pass an empty array  
  3. useEffect(() => {  
  4.   // only runs once  
  5. }, []);  
  6. componentDidUpdate: Runs on changes  
  7. // only run if count changes  
  8. useEffect(  
  9.   () => {  
  10.     // run here if count changes  
  11.   },  
  12.   [count]  
  13. );  
What about componentWillUnmount()
 
To run something before a component unmounts, we just have to return a function from useEffect()
 

Using State and Effects Together

 
 
There's no problem using them together! Together they can create functional components that work the same as your class components!
 
Open a command prompt. Create a directory for SPFx solution.
 
md spfx-React-Hooks
 
Navigate to the newly created directory above.
 
cd spfx-React-Hooks
 
Run the Yeoman SharePoint Generator to create the solution.
 
yo @microsoft/sharepoint
 
Solution Name
 
Hit Enter to have default name (spfx-pnp-DropDown in this case) or type in any other name for your solution.
Selected choice - Hit Enter
 
Target for the component
 
Here, we can select the target environment where we are planning to deploy the client web part, i.e., SharePoint Online or SharePoint OnPremise (SharePoint 2016 onwards).
Selected choice - SharePoint Online only (latest)
 
Place of files
 
We may choose to use the same folder or create a subfolder for our solution.
Selected choice - Same folder
 
Deployment option
 
Selecting Y will allow the app to deploy instantly to all sites
Selected choice -  N (install on each site explicitly)
 
Permissions to access web APIs
 
Choose if the components in the solution require permission to access web APIs that are unique and unshared with other components in the tenant.
Selected choice - N (solution contains unique permissions)
 
Type of client-side component to create
 
We can choose to create a client-side web part or an extension. Choose the web part option.
Selected choice - WebPart
 
Web part name
 
Hit Enter to select the default name or type in any other name.
Selected choice - SpfxReactHooksCrud
 
Web part description
 
Hit Enter to select the default description or type in any other value.
 
Framework to use
 
Select any JavaScript framework to develop the component. Available choices are - No JavaScript Framework, React, and Knockout.
Selected choice - React
 
The yeoman generator will perform a scaffolding process to generate the solution. The scaffolding process will take a significant amount of time.
 
Once the scaffolding process is completed, lock down the version of the project dependencies by running below command.
 
npm shrinkwrap
 
In the command prompt, type the below command to open the solution in the code editor of your choice.
 
code .
 
NPM Packages Used,
 
On the command prompt, run below command.
 
npm i @pnp/logging @pnp/common @pnp/odata @pnp/sp --save
 
for Polyfills
 
npm install --save @pnp/polyfill-ie11
 
in SpfxReactHooksCrudWebPart.ts,
  1. import * as React from 'react';  
  2. import * as ReactDom from 'react-dom';  
  3. import { Version } from '@microsoft/sp-core-library';  
  4. import {  
  5.   BaseClientSideWebPart,  
  6.   IPropertyPaneConfiguration,  
  7.   PropertyPaneTextField  
  8. } from '@microsoft/sp-webpart-base';  
  9.   
  10. import * as strings from 'SpfxReactHooksCrudWebPartStrings';  
  11. import SpfxReactHooksCrud from './components/SpfxReactHooksCrud';  
  12. import { ISpfxReactHooksCrudProps } from './components/ISpfxReactHooksCrudProps';  
  13. import "@pnp/polyfill-ie11";  
  14. import { sp, Web } from '@pnp/sp';   
  15.   
  16. export interface ISpfxReactHooksCrudWebPartProps {  
  17.   description: string;  
  18. }  
  19.   
  20. export default class SpfxReactHooksCrudWebPart extends BaseClientSideWebPart<ISpfxReactHooksCrudWebPartProps> {  
  21.   protected onInit(): Promise<void> {  
  22.     return new Promise<void>((resolve: () => void, reject: (error?: any) => void): void => {  
  23.       sp.setup({  
  24.         sp: {  
  25.           headers: {  
  26.             "Accept""application/json; odata=nometadata"  
  27.           }  
  28.         }  
  29.       });  
  30.       resolve();  
  31.     });  
  32.   }  
  33.   public render(): void {  
  34.     const element: React.ReactElement<ISpfxReactHooksCrudProps > = React.createElement(  
  35.       SpfxReactHooksCrud,  
  36.       {  
  37.         description: this.properties.description,  
  38.         context: this.context    
  39.       }  
  40.     );  
  41.   
  42.     ReactDom.render(element, this.domElement);  
  43.   }  
  44.   
  45.   protected onDispose(): void {  
  46.     ReactDom.unmountComponentAtNode(this.domElement);  
  47.   }  
  48.   
  49.   protected get dataVersion(): Version {  
  50.     return Version.parse('1.0');  
  51.   }  
  52.   
  53.   protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {  
  54.     return {  
  55.       pages: [  
  56.         {  
  57.           header: {  
  58.             description: strings.PropertyPaneDescription  
  59.           },  
  60.           groups: [  
  61.             {  
  62.               groupName: strings.BasicGroupName,  
  63.               groupFields: [  
  64.                 PropertyPaneTextField('description', {  
  65.                   label: strings.DescriptionFieldLabel  
  66.                 })  
  67.               ]  
  68.             }  
  69.           ]  
  70.         }  
  71.       ]  
  72.     };  
  73.   }  
  74. }  
in HookUsestate.ts
  1. export interface IHooksuseState {    
  2.   MileStone?:any;  
  3.     Id?:any;  
  4.     Title?:any;  
  5. }   
  6. /*interface ServiceInit { 
  7.     mysttt: 'init'; 
  8.   } 
  9.   interface ServiceLoading { 
  10.     status: 'loading'; 
  11.   } 
  12.   interface ServiceLoaded<T> { 
  13.     status: 'loaded'; 
  14.     payload: T; 
  15.   } 
  16.   interface ServiceError { 
  17.     status: 'error'; 
  18.     error: Error; 
  19.   } 
  20.   export type Service<T> = 
  21.     | ServiceInit 
  22.     | ServiceLoading 
  23.     | ServiceLoaded<T> 
  24.     |IHooksuseState 
  25.     | ServiceError;*/  
 in ISpfxReactHooksCrudProps.ts
  1. import {WebPartContext} from '@microsoft/sp-webpart-base';  
  2. export interface ISpfxReactHooksCrudProps {  
  3.   description: string;  
  4.   context: WebPartContext;  
  5. }  
in SpfxReactHooksCrud.tsx
  1. import * as React from 'react';  
  2. /*import styles from './SpfxReactHooksCrud.module.scss';*/  
  3. import { ISpfxReactHooksCrudProps } from './ISpfxReactHooksCrudProps';  
  4. import { escape } from '@microsoft/sp-lodash-subset';  
  5. import { MyListComponent } from './MyList';  
  6. const UserContext = React.createContext([]);  
  7. export default class SpfxReactHooksCrud extends React.Component<ISpfxReactHooksCrudProps, {}> {  
  8.   public render(): React.ReactElement<ISpfxReactHooksCrudProps> {  
  9.     return (  
  10.       <div >  
  11.         <MyListComponent />  
  12.       </div>  
  13.     );  
  14.   }  
  15. }  
in MyList.tsx
  1. import * as React from 'react';  
  2. import { SpListCollection } from './mySpListCollection';  
  3.   
  4. export const MyListComponent = () => {  
  5.   const { datacol, loaddata } = SpListCollection();  
  6.   React.useEffect(() => {  
  7.     loaddata();  
  8.   }, []);  
  9.   return (  
  10.     <div>  
  11.       <DataBinding DataNewCol={datacol} />  
  12.   
  13.     </div>  
  14.   );  
  15. };  
  16. const  DataBinding = (props) => {  
  17.   //function DataBinding(props) {  
  18.   const DataNewCol = props.DataNewCol;  
  19.   const listItems = DataNewCol.map((data, index) =>  
  20.     <div key={index}>  
  21.       <h3>{data.Title}</h3>  
  22.       <p>{data.MileStone}</p>  
  23.       <p>{data.Id}</p>  
  24.     </div>  
  25.   );  
  26.   return (  
  27.     <div>{listItems}</div>  
  28.   );  
  29. };  

 in mySpListCOllection.tsx
  1. import * as React from 'react';  
  2. import { sp } from "@pnp/sp";  
  3. import { IHooksuseState } from './HooksuseState';  
  4. export const SpListCollection = () => {  
  5.   const [datacol, setdatacol] = React.useState([]);  
  6.   const loaddata = () => {  
  7.     sp.web.lists.getByTitle("ProjectStatus").items.select('Title''Id''MileStone').get().then((items) => {  
  8.       let result: IHooksuseState[] = [];  
  9.       items.forEach(element => {  
  10.         result.push({  
  11.           Id: element.Id, Title: element.Title, MileStone: element.MileStone  
  12.         });  
  13.       });  
  14.       return result;  
  15.     }) .then(resultdata => setdatacol(resultdata));  
  16.   };  
  17.   return { datacol, loaddata, setdatacol };  
  18. };  
Here, I am using "ProjectStatus" as a list name with "MileStone,Id,Title" as Field Names. For details, visit React-Hooks
 
Happy Coding :)