The current version of the Yeoman generator for the SPFx still uses the older-style React class components. Because thatās what you get by default, thatās what most developers end up using.
But functional components enabled by React hooks have become widely popular… and I get a lot of questions on how to use React hooks in your SharePoint projects!
So… until Microsoft updates the Yeoman generator, Iām going to show you how you change your new projects to React hooks in just a few minutes. And then, Iāll show the basics of React hooks with a simple example.
Introduction
Letās start by looking at the current state of React in SPFx projects.
Hooks were added to React v16.8 and they let you use the componentās state and other React features without writing classes. I love them because I find I write less code and the code that I do write is more expressive of what I want to happen when other things happen on the page.
The SharePoint Framework has supported React v16.8 since the SPFx v1.9.1 was released in August 2019, but we still canāt easily use them!
Why? Because even today, in 2022, with SPFx v1.15.2, the Yeoman generator for the SharePoint Framework is still creating React class-based controls:
export default class SpFxReactHooksWebPart
extends BaseClientSideWebPart<ISpFxReactHooksWebPartProps> {
public render(): void {
const element: React.ReactElement<ISpFxReactHooksProps> =
React.createElement(SpFxReactHooks, { .. });
ReactDom.render(element, this.domElement);
}
protected onInit(): Promise<void> {
return super.onInit();
}
}
A hook is a function that allows you to āhook intoā React state & lifecycle events from function components, meaning you donāt have to use classes. Theyāre 100% backward compatible & entirely opt-in. React includes a few hooks, but you can create your own if you wanted to. In this video, Iām only going to cover two popular and most commonly used hooks: useState
& useEffect
.
While thereās nothing wrong with these class components, letās look at how to convert this project to a functional component that leverages React hooks.
The following steps are demonstrated in a working demo in a video on my YouTube channel. Watch this video to learn how to convert a default class-based React component to a functional component, and then see how to implement React hooks in the web part!
ā¶ How to use React hooks with the SharePoint Framework (SPFx)
Convert a class component to a functional component
Thereās nothing to change with the web part because it’s just a React component as far as SPFx is concerned. All the changes you need to implement to switch to functional components happen in the React component.
Converting the default class component to a functional component is pretty simple. The following code shows the core things you need to change to the component. To keep things simple, I removed a lot of the boilerplate HTML in the default componentās render()
method.
The steps include:
- change the declaration from a class to an object
- export the object
- remove the
render()
method - change the reference to the componentās
props
to not use thethis
keyword because it’s not a class
import * as React from 'react';
import styles from './SpFxReactHooks.module.scss';
import { ISpFxReactHooksProps } from './ISpFxReactHooksProps';
import { escape } from '@microsoft/sp-lodash-subset';
- export default class SpFxReactHooks extends React.Component<ISpFxReactHooksProps, {}> {
+ const SpFxReactHooks: React.FC<ISpFxReactHooksProps> = (props) => {
- public render(): React.ReactElement<ISpFxReactHooksProps> {
const {
hasTeamsContext,
userDisplayName
- } = this.props;
+ } = props;
return (
<section className={`${styles.SpFxReactHooks} ${hasTeamsContext ? styles.teams : ''}`}>
<div className={styles.welcome}>
<h2>Well done, {escape(userDisplayName)}!</h2>
</div>
</section>
);
}
+ export default SpFxReactHooks;
That’s it! Your class component is now a functional component.
Now letās see how we can use React hooks in this component.
Implement state & refresh the web part
One of the most common things we do in React components is work with the state. Letās see how can do this with the React hook useState
.
Letās do this by adding a new state property counter
and add it to our rendering:
import * as React from 'react';
+ import { useState } from 'react';
import styles from './SpFxReactHooks.module.scss';
import { ISpFxReactHooksProps } from './ISpFxReactHooksProps';
import { escape } from '@microsoft/sp-lodash-subset';
const SpFxReactHooks: React.FC<ISpFxReactHooksProps> = (props) => {
const {
hasTeamsContext,
userDisplayName
} = props;
+ const [counter, setCounter] = useState<number>(1);
return (
<section className={`${styles.SpFxReactHooks} ${hasTeamsContext ? styles.teams : ''}`}>
<div className={styles.welcome}>
<h2>Well done, {escape(userDisplayName)}!</h2>
+ <div>Counter: <strong>{counter}</strong></div>
</div>
</section>
);
}
export default SpFxReactHooks;
Notice when I create the counter
property, Iām also defining the setter method I can use to update the counter:
const [counter, setCounter] = useState<number>(1);
This replaces the this.setState();
method we use with React class components. Iām also setting the initial value of the counter
to 1
by passing it into the constructor.
Add button & event handler
With a state property created and showing up in our component, letās tackle two more common things: event handlers when something happens on the page and update the state property which triggers the repainting of the component on the page.
In this case, letās add a button that increments the counter when the button is clicked:
import * as React from 'react';
import { useState } from 'react';
import styles from './SpFxReactHooks.module.scss';
import { ISpFxReactHooksProps } from './ISpFxReactHooksProps';
import { escape } from '@microsoft/sp-lodash-subset';
const SpFxReactHooks: React.FC<ISpFxReactHooksProps> = (props) => {
const {
hasTeamsContext,
userDisplayName
} = props;
const [counter, setCounter] = useState<number>(1);
+ const onButtonClick = (): void => {
+ setCounter(counter + 1);
+ }
return (
<section className={`${styles.SpFxReactHooks} ${hasTeamsContext ? styles.teams : ''}`}>
<div className={styles.welcome}>
<h2>Well done, {escape(userDisplayName)}!</h2>
<div>Counter: <strong>{counter}</strong></div>
+ <button onClick={ () => onButtonClick() }>+</button>
</div>
</section>
);
}
export default SpFxReactHooks;
Notice how much cleaner that code is compared to setting the state value with a class component?
Before, when using the setState()
method, when I wanted to use the previous state value, like in my case where I need to increment the counter, Iād have to write a lot more code to keep track of the state and then set it as an immutable object, like the following:
this.setState((prevState) => ({
...prevState,
counter: prevState.counter + 1
}));
Add event handler when state property changes
Now, letās see how to add a side effect to hook into other things happening in our component. This is done with the hook useEffect
.
In this example, Iāll add a new state property to indicate if the counter value is even or odd. It needs to change whenever the counter value changes. Sure, I could have implemented this directly in the rendering of the component, but itās a simple example to show how side effects work.
import * as React from 'react';
- import { useState } from 'react';
+ import { useState, useEffect } from 'react';
import styles from './SpFxReactHooks.module.scss';
import { ISpFxReactHooksProps } from './ISpFxReactHooksProps';
import { escape } from '@microsoft/sp-lodash-subset';
const SpFxReactHooks: React.FC<ISpFxReactHooksProps> = (props) => {
const {
hasTeamsContext,
userDisplayName
} = props;
const [counter, setCounter] = useState<number>(1);
+ const [evenOdd, setEvenOdd] = useState<string>('');
+ useEffect(() => {
+ setEvenOdd((counter % 2 === 0) ? 'even' : 'odd');
+ }, [counter]);
const onButtonClick = (): void => {
setCounter(counter + 1);
}
return (
<section className={`${styles.SpFxReactHooks} ${hasTeamsContext ? styles.teams : ''}`}>
<div className={styles.welcome}>
<h2>Well done, {escape(userDisplayName)}!</h2>
- <div>Counter: <strong>{counter}</strong></div>
+ <div>Counter: <strong>{counter}</strong> is <strong>{evenOdd}</strong></div>
<button onClick={ () => onButtonClick() }>+</button>
</div>
</section>
);
}
export default SpFxReactHooks;
The introduction of the useEffect()
method allows me to hook into whenever the counter
state property changes. You do that by passing it in as the second argument. Now, whenever that property changes, my side effect will run and set the value of another state property evenOdd
. By changing the state, that triggers React to re-render the component to display the new values.
Initialize the component with useEffect
Now letās see how we tackle a common requirement: initiating the component. In class components, weāre used the componentDidMount
and componentDidUpdate
lifecycle methods to handle this scenario.
The way you do this same thing with hooks is to add a useEffect
method with an empty array of dependencies. So, letās add some SPFx sauce to this and get some data from the SharePoint REST API when this component loads:
import * as React from 'react';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import styles from './SpFxReactHooks.module.scss';
import { ISpFxReactHooksProps } from './ISpFxReactHooksProps';
import { escape } from '@microsoft/sp-lodash-subset';
+ import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
const SpFxReactHooks: React.FC<ISpFxReactHooksProps> = (props) => {
const {
hasTeamsContext,
userDisplayName,
+ // update prop interface & add as input prop in web part (not shown)
+ spHttpClient
} = props;
const [counter, setCounter] = useState<number>(1);
const [evenOdd, setEvenOdd] = useState<string>('');
+ const [siteLists, setSiteLists] = useState<string[]>([]);
+ useEffect(() => {
+ (async () => {
+ // line wrapping added for readability
+ const endpoint: string = `${currentSiteUrl}/_api/web/lists
+ ?$select=Title
+ &$filter=Hidden eq false
+ &$orderby=Title
+ &$top=10`;
+ const rawResponse: SPHttpClientResponse = await spHttpClient.get(
+ endpoint, SPHttpClient.configurations.v1);
+ setSiteLists(
+ (await rawResponse.json()).value.map((list: { Title: string }) => {
+ return list.Title;
+ })
+ );
+ })();
+ }, []);
useEffect(() => {
setEvenOdd((counter % 2 === 0) ? 'even' : 'odd');
}, [counter]);
const onButtonClick = (): void => {
setCounter(counter + 1);
}
return (
<section className={`${styles.SpFxReactHooks} ${hasTeamsContext ? styles.teams : ''}`}>
<div className={styles.welcome}>
<h2>Well done, {escape(userDisplayName)}!</h2>
<div>Counter: <strong>{counter}</strong> is <strong>{evenOdd}</strong></div>
<button onClick={() => onButtonClick()}>+</button>
+ <ul>
+ {
+ siteLists.map((list: string) => (
+ <li>{list}</li>
+ ))
+ }
+ </ul>
</div>
</section>
);
}
export default SpFxReactHooks;
One thing I do want to mention here about using the useEffect
for initialization of the component. If I don’t include a second parameter, like useEffect(())
, the effect will run after every single render of the component.
Thatās clearly not what we want in this case… I donāt want to query the SharePoint REST API for the lists every time the component is repainted on the screen.
So, to tell React that I only want it to run when the component is mounted & unmounted, I give it an empty array, useEffect((), [])
, to tell React that it doesnāt depend on anything and therefore, only run when the component is initially mounted.
What do you think about React hooks?
Pretty easy right? Itās a simple project, but if you want a copy of it, you can use the form below to request a link to download this working example.
I much prefer using React hooks and functional components over using the traditional class-based approach. And after showing you how little work is required to switch my new components over to functional components to leverage hooks, I think itās worth it!
What do you think about using React hooks in your SPFx projects? Do you prefer them and functional components over class components? Or not? Iād like to know what you think!