5
(2)

In the previous post we developed the functions to get and apply a provisioning template using PnP core library and deployed them to the Azure Portal, then we configured the Application Registration by uploading the certificate needed for authentication process and added API permissions we need to do the operations on SharePoint.

In this post we will implement the web part to call our functions and also we will learn how we can use some of the React Hooks such as useContext, useState and useEffect.

I’m not going to implement the web part from scratch in this post so please download the source code from here and continue reading.

React Hooks

React Hooks are so magical as they make function components as powerful as class components , if you haven’t heard about them please read here before go further.

So let’s start with Context, as you know SPFx components start with a single parent component that splits up to many levels of child components.

Imagine one of the most common issues we have in SPFx components, passing data all the way down to the last child component, and as you know data is passed up down from one component to another through props, so you have to pass data through every component by setting the props until you reach the last component!

This is where Context comes to the rescue. with React Context API you can pass data deeply throughout your app without having to manually pass props down through multiple levels.

We can create a Context in React by using React.createContext, providing the data types (as we use Typescript) and then passing in an initial value (which is null in our example below)

import * as React from 'react';
import { MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import AppService from '../../../../services/appService';

export interface IMessageBarSettings{
    visible:boolean;
    message: string;
    type: MessageBarType;
  }
  
  interface IAppContextInterface {
    appService: AppService;
    isGlobalAdmin: boolean;
    isSiteOwner: boolean;
    webUrl: string;
    messageBarSettings: IMessageBarSettings;
    isLoading:boolean;
    toggleLoading: (visible:boolean)=>void;
    updateMessageBarSettings: (settings:IMessageBarSettings)=>void;
  }
  
const AppContext = React.createContext<IAppContextInterface | null>(null);
export default AppContext; 

As you can see we have IMessageBarSettings interface that helps us to have a global message bar component as you will see later in this post as well as some global properties and methods to update some of them (when we need to display a loading image or update the message bar).

Next we need to wrap our components inside the Context using the Provider which helps to pass the data through them.

import * as React from 'react';
import styles from './App.module.scss';
import { IAppProps } from './IAppProps';
import AppContext,{IMessageBarSettings} from "./AppContext";
import { MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import AppContent from "./AppContent";
import * as Strings from "SiteProvisioningWebPartWebPartStrings";

const App: React.FC<IAppProps> = (props) => {
  const {webUrl,appService} = props;
  const [isGlobalAdmin,setIsGlobalAdmin] = React.useState(false);
  const [isSiteOwner,setIsSiteOwner] = React.useState(false);
  const [isLoading,setIsLoading] = React.useState(false);
  const [messageBarSettings,setMessageBarSettings] =React.useState({
    message:"",
    type:MessageBarType.info,
    visible:false
  } as IMessageBarSettings);
  
  
  const updateMessageBarSettings = (settings:IMessageBarSettings)=>{
    setMessageBarSettings(settings);
  };

  const toggleLoading = (visibleLoading:boolean)=>{
    setIsLoading(visibleLoading);
  };

  React.useEffect(()=>{
    let didCancel = false;

    const fetchIsGloablAdmin = async ()=>{
      const globalAdmin = await appService.checkUserIsGlobalAdmin();      
      if (!didCancel) {
        setIsGlobalAdmin(globalAdmin);
      }
      
    };
  
    const fetchIsSiteOwner = async ()=>{
      const siteOwner = await appService.IsSiteOwner();
      if (!didCancel) {
        setIsSiteOwner(siteOwner);
        if(!siteOwner){
          setMessageBarSettings({
            message: Strings.ErrorMessageUserNotAdmin,
            type: MessageBarType.error,
            visible: false
          });
        }
      }
    };

    fetchIsGloablAdmin();
    fetchIsSiteOwner();

    return ()=>{didCancel=true;};
  },[]);

  return (
    <div className={styles.siteProvisioningWebPart}>
      <AppContext.Provider value={{ 
        appService,
        isGlobalAdmin,
        isSiteOwner,
        webUrl,
        messageBarSettings,
        isLoading,
        toggleLoading,
        updateMessageBarSettings
        } }>
      <AppContent/>
      </AppContext.Provider>
    </div>
  );
};

export default App;

As you will see, there is no class component and we use only function components as they give us many benefits, you can keep your code maintainable and readable, no setting any state and we only focus on displaying the data.

AppContext.Provider has only one property called value and you need to pass the default values for the properties you defined, some of these values coming through calling a service method, this is where useEffect hook helps us to manage the side effects of a component, it servers componentDidMount, componentDidUpdate and componentWillUnmount in only one place!

By default, React runs the effects after every render (including the first render), the returning method inside useEffect specify how to “clean up” after it’s done (it’s optional).

you cannot return a Promise from useEffect. JavaScript async functions always return a promise and useEffect should exclusively return another function, which is used for cleaning up the effect.

Access context from child components

All we need to do to access the Context’s state is import it into a component and use the useContext Hook in React!

import AppContext from "../App/AppContext";

----

const ctx = React.useContext(AppContext);

How greate is that? now you can easily get access to all values and methods of the context.

const onUploadCompleted = (response) => {
    ctx.updateMessageBarSettings({
        message: Strings.SuccessMessage,
        type: MessageBarType.success,
        visible: true
    });
};

<div hidden={ctx.isLoading}>
    <TextField disabled={!ctx.isGlobalAdmin} label="Site URL" value={webUrl} onChanged={(value) => setWebUrl(value)} />
    <br />
    <FilePond disabled={!ctx.isSiteOwner} onremovefile={fileRemoved} server={
        {
            url: ctx.appService.applyProvisioningTemplateUrl,
            process: {
                        method: 'POST',
                        ondata: (formData) => {
                            formData.append('WebUrl', webUrl);
                            return formData;
                    },
                onload: onUploadCompleted,
                onerror: onUploadFailed
            }
        }
    } />
</div>

You can get a value like ctx.isSiteOwner or call a method like ctx.updateMessageBarSettings without passing them through props!

useState hook

Before react hooks we had to define our values in State object and call this.setState to update values, useState hook makes it easier for us to define and update values.

const [webUrl, setWebUrl] = React.useState(ctx.webUrl);
const [template, setTemplate] = React.useState("");

useState returns a tuple where the first parameter is the current state of the value and second parameter is the method that will allow us to update the state. In above sample ctx.webUrl is the initial value of webUrl or template is a string value with an empty string as intial value, with setWebUrl or setTemplate methods we can update the state whenever we need it.

const response: HttpClientResponse = await ctx.appService.GetProvisioningTemplate(webUrl, handlers.join(","));
const responseText = await response.text();
if (response.status === 200)
    setTemplate(responseText);

Also when you are using useState you should remember:

  • We call it inside a function component to add some local state to it.
  • useState returns a pair: the current state value and a function that lets you update it.
  • It’s similar to this.setState in a class, except it doesn’t merge the old and new state together.
  • The only argument to useState is the initial state.
  • The initial state argument is only used during the first render.
  • You can use the State Hook more than once in a single component.

Hope you enjoed reading this post, you can download the source code from here.

How useful was this post?

Click on a star to rate it!

Average rating 5 / 5. Vote count: 2

No votes so far! Be the first to rate this post.