Converting SPFx Web Part from JavaScript to React
This article was written in collaboration with Eric Skaggs
Introduction
In today’s SharePoint Dev Ecosystem (PnP) Bi-Weekly Call, Eric Skaggs asked a question I’ve heard many times before:
Do you have an example that shows how to go from a JavaScript SPFx web part to a SPFx React web part?
(I’m paraphrasing)
I had been looking for an opportunity to write such as article, so I told Eric to DM me on Twitter to see if he had an example of a web part he’d like to convert.
As it turns out, Eric has such a web part: his GitHub Badge WebPart is a great example of a JavaScript-only SPFx web part.

Eric’s SPFx web part works great, and there is really no need to convert it to React. We’ll convert it to React simply to demonstrate the process and to highlight some of the design differences between a JavaScript only web part and a React web part.
It is also important to point out that every developer has their own coding styles and preferred approaches that do not affect the final product. In this article, I’ll try to point out where I applied my own coding style.
This article is written as if you’re following along and converting the application yourself. Feel free to skip to the end to get the code and compare Eric’s JavaScript-only SPFx web part with my React version of the same web part.
Starting from scratch
The SPFx framework is constantly improving. In fact, the framework went from 1.8 to 1.8.1 two days ago!
Because of this, I like to make sure that I create a new solution every time I start converting a web part (I do this a lot, as it turns out).
Start by making sure that your environment is configured to create SPFx solutions. If you haven’t done so yet, follow these steps to get you started.
If you were already set up, make sure to update your version of the Yeoman generator to the latest. To do so, use the following command from your Node.js command prompt:
npm install -g @microsoft/generator-sharepoint
Once this is completed, I followed the instructions from the SharePoint Framework documentation except, you know, with React GitHub Badge as the solution name:
Create a new project directory in your favorite location.
md react-github-badgeYour web part solution does not need to start with
react-. I just named it that way because it is the naming convention in the SP-Dev-Fx-WebParts repositoryGo to the new folder you created:
md react-github-badgeCreate the React GitHub Badge web part by running the Yeoman SharePoint Generator.
yo @microsoft/sharepointWhen prompted:
- Accept the default react-github-badge as your solution name, and then select ENTER.
- Select SharePoint Online only (latest), and select ENTER.
- Select Use the current folder for where to place the files.
- Select N to allow the solution to be deployed to all sites immediately.
- Select N on the question if solution contains unique permissions.
- Select WebPart as the client-side component type to be created.
The next set of prompts ask for specific information about your web part:
- Enter GitHub Badge as your web part name, and then select ENTER.
- Enter Displays information from GitHub for a specified user as your web part description, and then select ENTER.
- For framework you would like to use, select React, and then select ENTER.

You’ll know it has completed when you see the following message:

Once it has completed, run:
gulp serve
to test your web part. (It should work). You should see something like this:
It isn’t pretty, but it’s a start.
Fixing potential vulnerabilities
If you paid attention as the Yeoman generator created the solution, you may have noticed a nasty message like this one:
added 1759 packages from 1071 contributors and audited 565045 packages in 63.66s
found 1957 vulnerabilities (1806 low, 36 moderate, 115 high)
run npm audit fix to fix them, or npm audit for details
Unfortunately, that’s the nature of building solutions with open-source components.
I don’t like it, so I typically run the following command to fix as many issues as possible:
npm audit fix
Once completed, you should see less scary exploits:
Run your web part again and make sure it still works:
gulp serve
Separating Web Part and Component
Eric’s no-framework web part has all the code for retrieving a user’s GitHub profile, managing web part properties, and rendering in the GitHubBadgeWebPart.ts. That’s how it is done with “no-framework” web parts.
In SPFx React solutions, the web part will be broken into smaller components:
- GitHubBadgeWebPart.ts: The Web Part, which is responsible for storing and retrieving web part properties, displaying the property pane, and calling components to render the web part.
- GitHubBadge.tsx: The main component, which renders the content of the web part.
We’re going to take Eric’s code and move the content to GitHubBadge.tsx, and leave the web part code in GitHubBadgeWebPart.ts.
Adding a web part property to store the GitHub user name
Using your favorite code editor, open
GitHubBadgeWebPart.ts(located under src\webparts\gitHubBadge and find the following code that was generated by Yeoman:export interface IGitHubBadgeWebPartProps { description: string; }Since we don’t need a
descriptionproperty for our web part, let’s rename it togitHubUserName. The code should look like this:export interface IGitHubBadgeWebPartProps { gitHubUserName: string; }If you use Visual Studio Code, simply place your cursor over
descriptionand hitF2. Type ingitHubUserNameand hit ENTER
If you do not use Visual Studio Code, you should look for a line that says:
description: this.properties.descriptionand replace it for:
description: this.properties.gitHubUserNameFind the code inside the
getPropertyPaneConfigurationfunction that looks like this:groupFields: [ PropertyPaneTextField('description', { label: strings.DescriptionFieldLabel }) ]and rename the
descriptionproperty togitHubUserName. The code should look as follows:groupFields: [ PropertyPaneTextField('gitHubUserName', { label: strings.DescriptionFieldLabel }) ]Finally, let’s rename the localized label for the
DescriptionFieldLabeltoGitHubUserNameFieldLabelby using theF2method. Doing so will also rename the localized variable insrc\webparts\gitHubBadge\loc\mystrings.d.ts. If you don’t use Visual Studio Code, make sure to rename theDescriptionFieldLabeltoGitHubUserNameFieldLabel.
As a general rule, I always name the localized variable for all my properties as[PropertyName]FieldLabel. So,GitHubUserNamebecomesGitHubUserNameFieldLabel. Feel free to use your own naming convention.We’ll also need to change the localized text! Go to
src\webparts\gitHubBadge\loc\en-us.jsand find the line that looks like this:"DescriptionFieldLabel": "Description Field"And change it to:
"GitHubUserNameFieldLabel": "GitHub user name"
As always, run gulp serve to test your changes and make sure your web part didn’t self-destruct.
If you click on your web part’s edit button, you should see the following:
So far, so good.
Your web part’s manifest contains a section for pre-configured properties in case you want to provide default properties when users add the new web part to their page. Let’s go make Eric’s username the default GitHub username:
Open
src\webparts\gitHubBadge\GitHubBadgeWebPart.manifest.jsonand find thepropertiesin thepreconfiguredEntriessection. Replace the following line:"description": "GitHub Badge"to this:
"description": "skaggej"JSON files don’t support comments, and Visual Studio Code will kindly remind you of that by showing the file in red. For bonus points, find all the comments (starting with
\\) in that file and remove them. Visual Studio Code will reward you with a nice green file name instead.
When you change the manifest, you won’t notice the difference until you stop and restart gulp serve, remove then re-add the web part to your page. I wasted a lot of time trying to debug this issue before I learned this the hard way.
Disabling reactive property changes
By default, SPFx web parts apply property changes as soon as you make them.
In this example, we don’t want the web part to retrieve the GitHub user’s profile until we’re done entering the name.
We can do this by adding an Apply button to the property pane. To do so, open the Web Part’s code at src\webparts\gitHubBadge\GitHubBadgeWebPart.ts and add the following code just above the getPropertyPaneConfiguration() function:
protected get disableReactivePropertyChanges(): boolean {
return true;
}
When you refresh your web part, you’ll get a nice Apply button at the bottom of your property pane.
Adding GitHubUserName property to the GitHubBadge props
Now that we’ve renamed the web part’s Description property to GitHubUserName, we need to do the same to the property that gets passed into the GitHubPage component.
Typically, your React component will define a I[ComponentName]Props to store properties, and a I[ComponentName]State to store the component’s state.
By default, the Yeoman generator will have created the IGitHubBadgeProps interface for you, which should be placed in the src\webparts\gitHubBadge\components\IGitHubBadgeProps.ts file.
Because I learned React from reading the Office UI Fabric code, and they already have awesome Coding Style, React and TypeScript guidelines, I tend to follow their standards.
In Office UI Fabric, they often group all types related to a component in a file called [ComponentName].types.ts.
You don’t have to do this, but I prefer to do the same by storing both my I[ComponentName]Props and I[ComponentName]State interfaces in the same file called [ComponentName].types.ts.
In this case, I’ll just rename the IGitHubBadgeProps.ts to GitHubBadge.types.ts by selecting the file in the Visual Studio Code explorer pane and hitting F2 then typing GitHubBadge.types.ts followed by ENTER.
We’ll add the IGitHubBadgetState interface later.
For now, though, let’s open the newly renamed GitHubBadge.types.ts file and find the following line in the IGitHubBadgeProps interface:
description: string;
and rename the description property to gitHubUserName by using the trusty F2 rename shortcut.
If all goes well, you’ll notice that both src\webparts\gitHubBadge\GitHubBadgeWebPart.ts and src\webparts\gitHubBadge\components\GitHubBadge.tsx will update where they refer to the description property to point to the new gitHubUserName property.
Rendering static HTML
So far, I haven’t used any of Eric’s code.
That’s about to change.
Open:
Find the following code in the
renderfunction and select it:<div className={ styles.container }> <div className={ styles.row }> <div className={ styles.column }> <span className={ styles.title }>Welcome to SharePoint!</span> <p className={ styles.subTitle }>Customize SharePoint experiences using Web Parts.</p> <p className={ styles.description }>{escape(this.props.gitHubUserName)}</p> <a href="https://aka.ms/spfx" className={ styles.button }> <span className={ styles.label }>Learn more</span> </a> </div> </div> </div>Notice that the line that looks like:
<div className={ styles.gitHubBadge }>and the last
</div>isn’t included in the selected code.Copy the following code from Eric’s sample over the selected code:
<div class="${ styles.container }"> <div class="${ styles.row }"> <div class="${ styles.column }"> <div id="gitHubUserProfilePic"></div> <div id="gitHubUserName" class="${ styles.title }">${this.properties.gitHubUserName}</div> <div id="login" class="${ styles.label }"></div> <div id="id" class="${ styles.label }"></div> <div id="node_id" class="${ styles.label }"></div> <div id="avatar_url" class="${ styles.label }"></div> <div id="gravatar_id" class="${ styles.label }"></div> <div id="url" class="${ styles.label }"></div> <div id="html_url" class="${ styles.label }"></div> <div id="followers_url" class="${ styles.label }"></div> <div id="following_url" class="${ styles.label }"></div> <div id="gists_url" class="${ styles.label }"></div> <div id="starred_url" class="${ styles.label }"></div> <div id="subscriptions_url" class="${ styles.label }"></div> <div id="organizations_url" class="${ styles.label }"></div> <div id="repos_url" class="${ styles.label }"></div> <div id="events_url" class="${ styles.label }"></div> <div id="received_events_url" class="${ styles.label }"></div> <div id="type" class="${ styles.label }"></div> <div id="site_admin" class="${ styles.label }"></div> <div id="name" class="${ styles.label }"></div> <div id="company" class="${ styles.label }"></div> <div id="blog" class="${ styles.label }"></div> <div id="location" class="${ styles.label }"></div> <div id="email" class="${ styles.label }"></div> <div id="hireable" class="${ styles.label }"></div> <div id="bio" class="${ styles.label }"></div> <div id="public_repos" class="${ styles.label }"></div> <div id="public_gists" class="${ styles.label }"></div> <div id="followers" class="${ styles.label }"></div> <div id="following" class="${ styles.label }"></div> <div id="created_at" class="${ styles.label }"></div> <div id="updated_at" class="${ styles.label }"></div> <div id="notfound" class="${styles.label}"></div> </div> </div> </div>You will get some errors. Don’t panic.
React doesn’t like it when you use the word
classto define the CSS class name. It is a reserved word. Instead, you must useclassName. Luckily, you can replace all instances of the wordclassby using a trick I’ve described in my multi-cursor editing in Visual Studio Code article. Select the first instance of the wordclass=(including the=sign) and hitCTRL-SHIFT-L, then typeclassName=instead, followed by theESCkey to stop multi-cursor editing. This should replace all instances ofclasstoclassName.The keyword
propertiesis also unique to the WebPart-derived classes. In React, the properties for a component are calledprops. Find the line that looks like this:<div id="gitHubUserName" className="${ styles.title }">${this.properties.gitHubUserName}</div>and replace it for this:
<div id="gitHubUserName" className="${ styles.title }">{this.props.gitHubUserName}</div>
In Eric’s code, he defines an ID for all the elements he wants to populate with data and dynamically inserts the text once he has retrieved it by calling every element by ID. It is a really efficient way to dynamically update web part content.
Although you technical can refer to HTML elements by IDs in React, it is rarely encouraged. One of the reasons for this is that if you add the web part twice on the same page, you’ll get conflicts.
Instead, we’ll later bind each element to the component’s state, then populate the state when we receive the data from GitHub.
For now, let’s just get rid of all those ID on every element.
Thankfully, you can do this by using multi-cursor editing!
- From the code, select the first instance of
id=". Make sure to include the=and the double quotes". - Just like you did before hit
CTRL-SHIFT-Lto select all instances of the currently selected text. You should see that all instances ofid="got selected. - Now hold
SHIFTandCTRLand press theRIGHTarrow key. It should automatically select the word to the right ofid=". (SHIFTmeans to extend the selection, whileCTRL-RIGHTselects the next word). - Hold
SHIFTagain (you can let go ofCTRL) and hit theRIGHTarrow again. That should select the last double-quotes (") to the right of the text you’ve already selected. - Hit
BACKSPACEto delete the text you have selected. If you want, hitBACKSPACEonce more to remove the extra space that is left after every<div.
Isn’t multi-cursor editing cool?
TSX files in React TypeScript projects make it easy to combine HTML with React. To insert dynamic text as an HTML attribute, you just need to use { }. You don’t even need the quotes around the attribute. This means that every instance of className="${ in Eric’s former JavaScript code can be simply replaced by className={.
You can do so by using multi-cursor editing again:
- In the code, find the first instance of
"${and select it. - Hit
CTRL-SHIFT-Lto select all instances. - Hit
DELto delete the selected text. - Type
{instead and hitESCto stop multi-cursor editing. - Go to the end of the className attribute, and select
}" - Hit
CTRL-SHIFT-Lto select all instances of}". - Hit
DELand type}instead, followed byESC.
The final render function should look like this:
public render(): React.ReactElement<IGitHubBadgeProps> {
return (
<div className={ styles.gitHubBadge }>
<div className={ styles.container }>
<div className={ styles.row }>
<div className={ styles.column }>
<div></div>
<div className={ styles.title }>{this.props.gitHubUserName}</div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={ styles.label }></div>
<div className={styles.label}></div>
</div>
</div>
</div>
</div>
);
}
Finally, open replace the content of the src\webparts\gitHubBadge\components\GitHubBadge.module.scss with Eric’s original SCSS:
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.gitHubBadge {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}
You deserve a reward! Run gulp serve again from your Node.js command prompt and refresh your web part. You should see something that looks like this:
We’re getting there!
Let’s retrieve the data next!
Creating an IGitHubServices interface
Eric’s example keeps things simple by putting the code to retrieve the user’s GitHub profile in the web part class.
However, React solutions benefit from breaking things into smaller components with a clear division of responsibilities.
For example, the code that calls the GitHub API to retrieve the GitHub user’s profile can be separated from the code that is responsible for rendering the profile information.
This is done because React makes it easy to create individualized components that do specific things. By keeping the code that retrieves the data separate from the code that renders the data, we could re-use the GitHub profile component in different ways.
It also makes it easier to create unit tests and mock services without having to change your GitHub profile component.
It doesn’t make the React code better than the no-framework code. It’s just a different approach.
Since the purpose of this example is to demonstrate converting a no-framework web part to a React web part, I’ll show you the extra steps of creating a separate IGitHubService interface, with a mock service and a real service.
- First, let’s create a new folder called
servicesunder thesrcfolder. It should be at the same level as thewebpartsfolder.
You may find other examples that place their
servicesunder the web part folder for the web part that calls it, but I like to design my services so that they can be used by more than one web part — hence placing it at the same level as thewebpartsfolder. Feel free to place it where you prefer.
Under the
src\servicesfolder, create another folder calledGitHubServices.In the
src\services\GitHubServicesfolder, create a new file calledGitHubServices.types.tsLet’s create an interface that represents the returned data from GitHub APIs. From your browser, visit
[https://api.github.com/users/skaggej](https://api.github.com/users/skaggej). It will return the JSON for Eric’s profile.Copy the content of the JSON. It should look like this:
{ "login": "skaggej", "id": 1846656, "node_id": "MDQ6VXNlcjE4NDY2NTY=", "avatar_url": "https://avatars1.githubusercontent.com/u/1846656?v=4", "gravatar_id": "", "url": "https://api.github.com/users/skaggej", "html_url": "https://github.com/skaggej", "followers_url": "https://api.github.com/users/skaggej/followers", "following_url": "https://api.github.com/users/skaggej/following{/other_user}", "gists_url": "https://api.github.com/users/skaggej/gists{/gist_id}", "starred_url": "https://api.github.com/users/skaggej/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/skaggej/subscriptions", "organizations_url": "https://api.github.com/users/skaggej/orgs", "repos_url": "https://api.github.com/users/skaggej/repos", "events_url": "https://api.github.com/users/skaggej/events{/privacy}", "received_events_url": "https://api.github.com/users/skaggej/received_events", "type": "User", "site_admin": false, "name": "Eric Skaggs", "company": "http://www.catapultsystems.com", "blog": "http://www.ericskaggs.net", "location": "Phoenix, AZ", "email": null, "hireable": null, "bio": "Fuse Solution Architect at Catapult Systems", "public_repos": 29, "public_gists": 3, "followers": 8, "following": 33, "created_at": "2012-06-13T14:01:52Z", "updated_at": "2019-04-09T00:18:35Z" }(don’t worry too much if there are minor differences)
If you don’t have the awesome JSON to TS extension for Visual Studio, go install it now. It will allow us to convert the JSON you just copied into a TypeScript interface. (Did I mention I’m the world’s laziest developer?)
Place your cursor in the
GitHubServices.types.tsfile. We’re about to insert some code in there.With JSON to TS installed, hit
F1to launch the Visual Studio Code command line and start typingJSON to TS. You should see **JSON to TS: Convert from clipboard. Select it and be patient. The extension will insert a TypeScript Interface calledRootObject` that contains a property for every attribute returned by the GitHub API. The code should look like this:interface RootObject { login: string; id: number; node_id: string; avatar_url: string; gravatar_id: string; url: string; html_url: string; followers_url: string; following_url: string; gists_url: string; starred_url: string; subscriptions_url: string; organizations_url: string; repos_url: string; events_url: string; received_events_url: string; type: string; site_admin: boolean; name: string; company: string; blog: string; location: string; email?: any; hireable?: any; bio: string; public_repos: number; public_gists: number; followers: number; following: number; created_at: string; updated_at: string; }Rename the
RootObjecttoIGitHubUserProfileand export the interface so that it can be accessed in other files. The code should look like this:export interface IGitHubUserProfile { login: string; id: number; node_id: string; avatar_url: string; gravatar_id: string; url: string; html_url: string; followers_url: string; following_url: string; gists_url: string; starred_url: string; subscriptions_url: string; organizations_url: string; repos_url: string; events_url: string; received_events_url: string; type: string; site_admin: boolean; name: string; company: string; blog: string; location: string; email?: any; hireable?: any; bio: string; public_repos: number; public_gists: number; followers: number; following: number; created_at: string; updated_at: string; }We use the convention
I[Something]to indicate that this is an interface, not a class. That way, we can take any TypeScript object that implements the properties defined in the interface (login,id,node_id,avatar_url, etc.) — regardless of how it was created. That way, we’ll be able to convert the JSON we retrieved from the GitHub API to theIGitHubUserProfileinterface and pass it around.We’ll add an
IGitHubServiceinterface to the same file. The interface will implement one method calledgetUserProfilethat receives analiasstring and returns an asynchronous promise of anIGitHubUserProfile. Just paste the following code below theIGitHubUserProfile:export interface IGitHubService { getUserProfile(alias: string): Promise<IGitHubUserProfile>; }Again, we use an interface so that we can later have a
MockGitHubServiceand a (real)GitHubServicethat both implement theIGitHubService. Our component won’t care whether it is using a real service or a mock service, because it will expect an object that implementsIGitHubService.
For your reference, the entire content of GitHubServices.types.ts should look like this:
export interface IGitHubUserProfile {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
name: string;
company: string;
blog: string;
location: string;
email?: any;
hireable?: any;
bio: string;
public_repos: number;
public_gists: number;
followers: number;
following: number;
created_at: string;
updated_at: string;
}
export interface IGitHubService {
getUserProfile(alias: string): Promise<IGitHubUserProfile>;
}
Now let’s create the mock service!
Creating MockGitHubService
We’ll use a mock service to return test data so that we don’t have to worry about getting blocked by GitHub for calling the API too many times when we’re testing the look and feel of the web part. The mock service will be interchangeable with the real service at a later time.
In the
src\services\GitHubServicesfolder, create a new file calledMockGitHubServiceIn the now empty file, paste the following code:
import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types"; export class MockGitHubService implements IGitHubService { public getUserProfile(alias: string): Promise<IGitHubUserProfile> { // This space for rent } }
The import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types"; line tells the TypeScript transpiler that those two interfaces are located in another file in the same folder.
The export class MockGitHubService implements IGitHubService says that this class (MockGitHubService) will do (or implements) everything the IGitHubService interface does, and that it should be available outside of this file by other files (or exported).
Let’s add some code to return sample data after simulating some delays.
In the code, replace the line that says
// This space for rentwith the following code:return new Promise<IGitHubUserProfile>((resolve) => { // pretend we're getting the data from the GitHub API by adding a delay setTimeout(() => { const fakeProfile: IGitHubUserProfile = { login: "skaggej", id: 1846656, node_id: "MDQ6VXNlcjE4NDY2NTY=", avatar_url: "https://avatars1.githubusercontent.com/u/1846656?v=4", gravatar_id: "", url: "https://api.github.com/users/skaggej", html_url: "https://github.com/skaggej", followers_url: "https://api.github.com/users/skaggej/followers", following_url: "https://api.github.com/users/skaggej/following{/other_user}", gists_url: "https://api.github.com/users/skaggej/gists{/gist_id}", starred_url: "https://api.github.com/users/skaggej/starred{/owner}{/repo}", subscriptions_url: "https://api.github.com/users/skaggej/subscriptions", organizations_url: "https://api.github.com/users/skaggej/orgs", repos_url: "https://api.github.com/users/skaggej/repos", events_url: "https://api.github.com/users/skaggej/events{/privacy}", received_events_url: "https://api.github.com/users/skaggej/received_events", type: "User", site_admin: false, name: "Eric Skaggs", company: "http://www.catapultsystems.com", blog: "http://www.ericskaggs.net", location: "Phoenix, AZ", email: null, hireable: null, bio: "Fuse Solution Architect at Catapult Systems", public_repos: 29, public_gists: 3, followers: 8, following: 33, created_at: "2012-06-13T14:01:52Z", updated_at: "2019-04-09T00:18:35Z" }; resolve(fakeProfile); }, 500); });
The entire content of the MockGitHubService.ts file should be as follows:
import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types";
export class MockGitHubService implements IGitHubService {
public getUserProfile(alias: string): Promise<IGitHubUserProfile> {
return new Promise<IGitHubUserProfile>((resolve) => {
// pretend we're getting the data from the GitHub API by adding a delay
setTimeout(() => {
const fakeProfile: IGitHubUserProfile = {
login: "skaggej",
id: 1846656,
node_id: "MDQ6VXNlcjE4NDY2NTY=",
avatar_url: "https://avatars1.githubusercontent.com/u/1846656?v=4",
gravatar_id: "",
url: "https://api.github.com/users/skaggej",
html_url: "https://github.com/skaggej",
followers_url: "https://api.github.com/users/skaggej/followers",
following_url: "https://api.github.com/users/skaggej/following{/other_user}",
gists_url: "https://api.github.com/users/skaggej/gists{/gist_id}",
starred_url: "https://api.github.com/users/skaggej/starred{/owner}{/repo}",
subscriptions_url: "https://api.github.com/users/skaggej/subscriptions",
organizations_url: "https://api.github.com/users/skaggej/orgs",
repos_url: "https://api.github.com/users/skaggej/repos",
events_url: "https://api.github.com/users/skaggej/events{/privacy}",
received_events_url: "https://api.github.com/users/skaggej/received_events",
type: "User",
site_admin: false,
name: "Eric Skaggs",
company: "http://www.catapultsystems.com",
blog: "http://www.ericskaggs.net",
location: "Phoenix, AZ",
email: null,
hireable: null,
bio: "Fuse Solution Architect at Catapult Systems",
public_repos: 29,
public_gists: 3,
followers: 8,
following: 33,
created_at: "2012-06-13T14:01:52Z",
updated_at: "2019-04-09T00:18:35Z"
};
resolve(fakeProfile);
}, 500);
});
}
}
Adding an index.ts file to GitHubServices
Now that we’ve defined some exports in our GitHubServices, we’ll want to make it easy for the component to use them.
The problem is that if we want to import our IGitHubService, IGitHubUserProfile and MockGitHubService in our GitHubBadge component, we’ll have to import each item from the files that contain them, like this:
import { IGitHubService, IGitHubUserProfile } from '../../../services/GitHubServices/GitHubServices.types';
import { MockGitHubService } from '../../../services/GitHubServices/MockGitHubService';
However, if we ever decide to move the various elements of the GitHubServices to different files, we’ll have to update all the import statements in all the components that use the services.
But why should the components know about the internal structure of the GitHubServices? Wouldn’t it be better to abstract all that stuff from the components?
Luckily, we can use index.ts to do just that!
In the
src\services\GitHubServicesfolder, add a new file calledindex.ts.In the
index.tsfile, export all the things that you want the components to have access to, as follows:export * from './GitHubServices.types'; export * from './MockGitHubService';
Now we can just add the following line in our src\webparts\gitHubBadge\components\GitHubBadge.tsx file to import everything we need:
import { IGitHubService, IGitHubUserProfile, MockGitHubService } from '../../../services/GitHubServices';
Adding some state to GitHubBadge
State is a funny concept in React.
It allows us to temporarily capture the information we need to support the different “ways” we want our component can be in (I’m really trying hard not to use the word state here).
For example, our GitHubBadge component potentially has 5 states:
- Not configured
- Loading
- Loaded with data
- Error because the user was Not found
- Error while calling GitHub API (network, throttling, etc.)
This is something that’s represented as follows:
Title: GitHubBadge States
Not configured->Loading: When user sets web part properties
Loading->Loaded: Normal scenario
Loading-->Not found: Invalid user name
Loading-->Error: Exception with GitHub API
I promised Eric I’d keep this simple, so I’ll ignore the Not configured and we’ll combine the Not found and Error states for now. (I can’t promise I won’t come back to this in a later article though).
To represent these states in out GitHubBadge component, we’ll use the following variables:
isLoading: abooleanthat will be set totruewhen the web part loads.userProfile: aIGitHubUserProfilevariable that can be set toundefined(if there is no data to show)errorMessage: astringcontaining an error message that can also be set toundefinedif nothing went wrong.
If isLoading is false, it will mean that the service call is complete. If errorMessage contains a message, it means there was an error. Otherwise, if userProfile contains data, it means that we received our data and that we want to show it.
Let’s start implementing this by creating an IGitHubBadgeState interface in our src\webparts\gitHubBadge\components\GitHubBadge.types.ts file:
Open the
GitHubBadge.types.tsfileAdd the following code below the
IGitHubBadgePropsinterface:export interface IGitHubBadgeState { isLoading: boolean; userProfile?: IGitHubUserProfile; errorMessage?: string; }Make sure to add the following line at the top of the file:
import { IGitHubUserProfile } from "../../../services/GitHubServices";
The ? at the end of the variable names means that the variables can be nullable.
Now let’s use the state in our component!
Adding state to the GitHubBadge component
Now we’re finally getting somewhere!
Let’s start by telling the GitHubBadge that is has a state:
Open the
src\webparts\gitHubBadge\components\GitHubBadge.tsxfileAt the top of the file, replace the following line:
import { IGitHubBadgeProps } from './GitHubBadge.types';for this:
import { IGitHubBadgeProps, IGitHubBadgeState } from './GitHubBadge.types';Replace the following line:
export default class GitHubBadge extends React.Component<IGitHubBadgeProps, {}> {with this:
export default class GitHubBadge extends React.Component<IGitHubBadgeProps, IGitHubBadgeState> {This tells the
GitHubBadgeclass that it should useIGitHubBadgePropsfor its properties, andIGitHubBadgeStatefor its state.Add a constructor to define a default state by adding the following code below the code you just changed, and above the
public render()method:constructor(props:IGitHubBadgeProps) { super(props); this.state = { isLoading: true }; }Note that the constructor is the only time you can change the state directly by using
this.state =. Everywhere else, you’ll only be able to usethis.setState().For now, let’s some conditional rendering logic in the
rendermethod so that when the web part is loading (i.e.:isLoadingequalstrue), we’ll write “Loading…” in the top of the web part. Replace the following line (just below<div className={ styles.column }>:
<div></div>
with this:
<div>{ this.state.isLoading && "Loading..." }</div>
If you try to use gulp serve now, you’ll notice that the web part always displays “Loading…” because the isLoading state variable is set to true at in the constructor and we never change that.

But we’ll fix that right now…
Loading and displaying mock data
React applications typically try to be responsive (as in “fast”) by avoiding any delays in rendering the components.
It is better to render a “Loading…” web part and immediately change it to show the data that’s you just retrieved than not rendering anything until the data has returned.
To achieve this, we’ll call the getUserProfile method from the MockGitHubService after the GitHubBadge is mounted. Once the MockGitHubService returns data, we’ll call this.setState() and set isLoading to false and populate the userProfile state variable with whatever data we received.
Calling this.setState will automatically trigger any elements that are bound to state variables on the component to re-render.
To the code!
In
src\webparts\gitHubBadge\components\GitHubBadge.tsx, add a method calledcomponentDidMountabove therendermethod, as follows:public componentDidMount(): void { }(It will still work if you put the code after the
rendermethod, I suggest where to put it in the code so that your code looks like mine once completed).Add the following code inside the
componentDidMountfunction:// Create an instance of the GitHub service const service: IGitHubService = new MockGitHubService(); // Call the GitHub service // In real-life, we would only call it when we're sure that there is a username service.getUserProfile(this.props.gitHubUserName).then((results: IGitHubUserProfile)=>{ // Set the userProfile with the results we got and isLoading to false, because we're done // loading. It'll make things redraw magically. this.setState({ userProfile: results, isLoading: false }); });Replace the entire
renderfunction with the following code. Don’t worry, I’ll explain shortly:public render(): React.ReactElement<IGitHubBadgeProps> { const { userProfile, isLoading, errorMessage } = this.state; return ( <div className={ styles.gitHubBadge }> <div className={ styles.container }> <div className={ styles.row }> { isLoading && <div className={ styles.column }> <div>Loading...</div> </div> } { !isLoading && userProfile && <div className={ styles.column }> <div><img src={userProfile.avatar_url} alt="GitHub User Profile Picture" /></div> <div className={ styles.title }>{this.props.gitHubUserName}</div> <div className={ styles.label }>{ userProfile.login}</div> <div className={ styles.label }>{userProfile.id}</div> <div className={ styles.label }>{userProfile.node_id}</div> <div className={ styles.label }>{userProfile.avatar_url}</div> <div className={ styles.label }>{userProfile.gravatar_id}</div> <div className={ styles.label }>{userProfile.url}</div> <div className={ styles.label }>{userProfile.html_url}</div> <div className={ styles.label }>{userProfile.followers_url}</div> <div className={ styles.label }>{userProfile.following_url}</div> <div className={ styles.label }>{userProfile.gists_url}</div> <div className={ styles.label }>{userProfile.starred_url}</div> <div className={ styles.label }>{userProfile.subscriptions_url}</div> <div className={ styles.label }>{userProfile.organizations_url}</div> <div className={ styles.label }>{userProfile.repos_url}</div> <div className={ styles.label }>{userProfile.events_url}</div> <div className={ styles.label }>{userProfile.received_events_url}</div> <div className={ styles.label }>{userProfile.type}</div> <div className={ styles.label }>{userProfile.site_admin}</div> <div className={ styles.label }>{userProfile.name}</div> <div className={ styles.label }>{userProfile.name}</div> <div className={ styles.label }>{userProfile.company}</div> <div className={ styles.label }>{userProfile.location}</div> <div className={ styles.label }>{userProfile.email}</div> <div className={ styles.label }>{userProfile.hireable}</div> <div className={ styles.label }>{userProfile.bio}</div> <div className={ styles.label }>{userProfile.public_repos}</div> <div className={ styles.label }>{userProfile.public_gists}</div> <div className={ styles.label }>{userProfile.followers}</div> <div className={ styles.label }>{userProfile.following}</div> <div className={ styles.label }>{userProfile.created_at}</div> <div className={ styles.label }>{userProfile.updated_at}</div> </div> } { !isLoading && errorMessage && <div className={ styles.column }> <div className={styles.label}>WARNING - error when calling URL https://api.github.com/users/{this.props.gitHubUserName}. Error = {errorMessage}</div> </div> } </div> </div> </div> ); }
Now save your code and treat yourself to a gulp serve. Refresh your web part and you should see the web part say Loading… for half a second, then load Eric’s profile.
“But that was a lot of weird code you just introduced!”, you’ll say. I know! Let me walk you through it.
The first line in the render function:
const { userProfile, isLoading, errorMessage } = this.state;
defines a “shortcut” to the state variables isLoading, userProfile, and errorMessage. That way, in the rest of the code, we don’t have to say this.state.userProfile, we can simply use userProfile.
The first section:
{ isLoading &&
<div className={ styles.column }>
<div>Loading...</div>
</div>
}
says: “if isLoading is true, render the HTML between { and }”.
Similarly, this line:
{ !isLoading && userProfile &&
says “If isLoading is not true, and there is a userProfile render the HTML between the {}.
Guess what:
{ !isLoading && errorMessage &&
does? It only renders the HTML between the {} if the web part is done loading and there is an error message.
Everywhere else in that function uses {} to bind to a state or prop. For example:
<div><img src={userProfile.avatar_url} alt="GitHub User Profile Picture" /></div>
Renders an image that binds the src attribute to the avatar_url attribute of the userProfile state variable, while:
<div className={ styles.label }>{userProfile.login}</div>
Insert the value of the login attribute of the userProfile state variable inside the <div/> element.
Everywhere else in the render function works the same way.
We’re almost done! We just need to retrieve the real data by passing the HTTP context and implementing the GitHubService to use it to call the real GitHub API.
Passing HTTP Context
In order to make HTTP requests, the component needs to use the HttpClient object exposed by the web part.
That means that the GitHubBadge component needs to add an HttpClient variable to its IGitHubBadgeProps interface.
Let’s do this:
Open
src\webparts\gitHubBadge\components\GitHubBadge.types.tsAt the top, import
HttpClientfrom ‘@microsoft/sp-http’, as follows:import { HttpClient } from '@microsoft/sp-http';Add a prop variable that will store the
HttpClientto theIGitHubBadgeProps. TheGitHubBadge.types.tsfile will look as follows:import { IGitHubUserProfile } from "../../../services/GitHubServices"; import { HttpClient } from '@microsoft/sp-http';```typescript
export interface IGitHubBadgeProps {
gitHubUserName: string;
httpClient: HttpClient;
}
export interface IGitHubBadgeState {
isLoading: boolean;
userProfile?: IGitHubUserProfile;
errorMessage?: string;
}
4. In the `src\webparts\gitHubBadge\GitHubBadgeWebPart.ts` web part, pass the new prop to the GitHubBadge component by changing the render as follows:
```typescript
public render(): void {
const element: React.ReactElement<IGitHubBadgeProps> = React.createElement(
GitHubBadge,
{
gitHubUserName: this.properties.gitHubUserName,
httpClient: this.context.httpClient
}
);
ReactDom.render(element, this.domElement);
}</code></pre>
<blockquote>
<p>You may have seen some samples that pass the entire web part context to the components (including some of my samples). Waldek has a <a href="https://blog.mastykarz.nl/dont-pass-web-part-context-react-components/">great article</a> that explains why you shouldn't. In this example, we choose to pass the <code>HttpClient</code> object from the web part's <code>context</code> instead of passing the entire <code>context</code>.
As a general rule, always listen to Waldek :-)</p>
</blockquote>
<h2>Creating the GitHubService</h2>
<p>The moment of truth!</p>
<ol>
<li>Go to the <code>src\services\GitHubServices</code> folder and add a file called <code>GitHubService.ts</code></li>
<li>At the top of the file, add the following imports:
<pre><code class="language-TypeScript">import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types";
import { HttpClient, HttpClientResponse } from '@microsoft/sp-http';</code></pre></li>
<li>Create the <code>GitHubService</code> class that implements the <code>IGitHubService</code> interface:
<pre><code class="language-TypeScript">
export class GitHubService implements IGitHubService {</code></pre></li>
</ol>
<p>}</p>
<pre><code>4. Add a <code>private</code> variable of type <code>HttpClient</code> that will be used to store the HTTP client object passed into the GitHubService:
```typescript
export class GitHubService implements IGitHubService {
private _httpClient: HttpClient;
}
Add a
constructorthat receives theHttpClientobject and stores it to theprivatevariable:constructor(httpClient: HttpClient) { this._httpClient = httpClient; }Finally, implement the
getUserProfilemethod that calls the GitHub API using theHttpClientpublic getUserProfile(alias: string): Promise<IGitHubUserProfile> { const gitHubUrl: string = "https://api.github.com/users/"+alias; // call the GitHub API return this._httpClient.get(gitHubUrl, HttpClient.configurations.v1, {}).then((response: HttpClientResponse) => response.json()) .then((profile: IGitHubUserProfile) => { return profile; }); }The code almost seems magical: it calls the API and converts the received JSON to an
IGitHubUserProfileinterface automatically.
The final GitHubService code looks like this:
import { IGitHubService, IGitHubUserProfile } from "./GitHubServices.types";
import { HttpClient, HttpClientResponse } from '@microsoft/sp-http';
export class GitHubService implements IGitHubService {
private _httpClient: HttpClient;
constructor(httpClient: HttpClient) {
this._httpClient = httpClient;
}
public getUserProfile(alias: string): Promise<IGitHubUserProfile> {
const gitHubUrl: string = "https://api.github.com/users/"+alias;
// call the GitHub API
return this._httpClient.get(gitHubUrl,
HttpClient.configurations.v1, {}).then((response: HttpClientResponse) => response.json())
.then((profile: IGitHubUserProfile) => {
return profile;
});
}
}
To allow the GitHubBadge component to access the GitHubService, we need to add GitHubService to the src\services\GitHubServices\index.ts, making the entire index.ts as follows:
export * from './GitHubServices.types';
export * from './MockGitHubService';
export * from './GitHubService';
Note that unlike the MockGitHubService, the GitHubService needs the HttpClient to work. Because the getUserProfile function is defined in the IGitHubService interface, we can’t change the function to pass the HttpClient when we need it.
However, we can change the constructor of the GitHubService to accept the HttpClient object we need.
Calling the GitHubService
To change the code in the GitHubBadge component to use the GitHubService, we simply need to change the componentDidMount by following these steps:
Open the
src\webparts\gitHubBadge\components\GitHubBadge.tsxfileChange the
importstatement at the top to includeGitHubService:import { IGitHubService, IGitHubUserProfile, MockGitHubService, GitHubService } from '../../../services/GitHubServices';In the
componentDidMountfunction, comment out this line:const service: IGitHubService = new MockGitHubService();and add the following line just below:
const service: IGitHubService = new GitHubService(this.props.httpClient);
Nothing else needs to change.
Run gulp serve and try the web part. The data will be really coming from GitHub.
However, if you try to change the user name property and click Apply, you won’t see any changes unless you refresh the page.
We can fix that.
Responding to changing props
While React is happy to automatically redraw the components when their state changes, our component only changes the state once it receives the data from the GitHubService.
And the GitHubService is only called once after the component is mounted.
To call the GitHubService when the component is mounted and when the gitHubUserName prop changes, we need to move some code around.
To do so:
In the
src\webparts\gitHubBadge\components\GitHubBadge.tsxfile, add a private function calledgetUserProfilethat calls the web service:private getUserProfile() { // Create an instance of the GitHub service //const service: IGitHubService = new MockGitHubService(); const service: IGitHubService = new GitHubService(this.props.httpClient); // Call the GitHub service // In real-life, we would only call it when we're sure that there is a username service.getUserProfile(this.props.gitHubUserName).then((results: IGitHubUserProfile)=>{ // Set the userProfile with the results we got and isLoading to false, because we're done // loading. It'll make things redraw magically. this.setState({ userProfile: results, isLoading: false }); }); }Change the
componentDidMountfunction to call thegetUserProfileprivate function:public componentDidMount(): void { this.getUserProfile(); }Add a
componentDidUpdatethat will compare if the previous props are different than the current props and will call the privategetUserProfilefunction if it is different:public componentDidUpdate(prevProps: IGitHubBadgeProps, prevState: IGitHubBadgeState): void { if (prevProps.gitHubUserName !== this.props.gitHubUserName) { this.getUserProfile(); } }
componentDidUpdate gets triggered any time the component’s state or props change. In this case, we use it to compare gitHubUserName and react accordingly.
Try your changes now using gulp serve and see it all work when you update the GitHub user name.
The final result looks like this:
Conclusion
In this article, we took the GitHub Badge WebPart that Eric Skaggs wrote using only JavaScript (no framework!) and converted it to a React web part that does the same thing.
We took a few detours on the way to convey some different concepts in React, but the result is mostly the same.
There are still a few things the sample web part should do:
- Add error handler
- Add a “Loading…” spinner
- Add a placeholder when the web part isn’t configured
- Cache the results to avoid getting throttled by GitHub for making too many calls.
…but this article is already long enough.
You can find the entire solution on my GitHub repo.
Thanks again to Eric for writing such an awesome sample web part. I hope that I did your sample justice with the React version.
