Categories
Microsoft Flow Microsoft Graph O365 PNPJS REACT SharePoint SharePoint Online

Create a SharePoint App with React: Setup

Please NOTE: This blog series is currently a work in progress and subject to change.

This is part 2 in a series about creating a React App, using the Microsoft PNPJS library and Ui-Fabric in SharePoint.

Disclaimer


WebDev By The Bay is not affiliated with nor is this post endorsed by any company mentioned in this post. 

All opinions belong to the author of this post.

All Copyrights and Trademarks belong to their respective owners.

Full disclosure!

I am not a Software Engineer, I’m a Web Developer of almost 20 years. In my day job, for the past 6 years, I have been tasked with developing solutions in SharePoint.

This series is my journey in learning not only SharePoint but various other technologies.

Please Let me be clear! We will create some custom code but most of the code will be from the library examples to make learning the technology easier, at least for me.

This series is not about showing you how to code the various pieces but rather how you can piece the pieces together to make an application.

The basic setup of the React App is based on react-starter by Ted Pattison.

I will be adding how to use the PNPJS Library to surface SharePoint list data, modifying webpack.config and providing some tips on using the Ui-Fabric.

App Purpose

Dashboard app to help manage Long Term Parking Requests and Approvals.

Setup SharePoint

In your SharePoint tenant, create a new folder. I named my folder AppsLibrary. This will be the main home for all the React apps I create in SharePoint.

Navigate into the AppsLibrary folder and create a sub folder for each of your app projects. Within the Project folder, create another folder called scripts.

You should have a structure similar to the following now:
AppsLibrary
– SafePark
– scripts
– Project 2
– scripts
– Project 3
– scripts

Setup the React App

Create a folder some where on your hard drive to be the home of the src app.
I gave my local project source folder the same name as the SharePoint project folder, safepark

[December 14, 2019]: This project is being changed to use Functional components and React Hooks. Therefore redux, react-redux, and react-thunk node modules are no longer required.

This project uses TypeScript and Redux.

Please note the following:
1. These libraries may be out of date at the time you read this post.
Please be sure to check for updates.
2. You will need to upload the bundle js file and index.aspx to SharePoint before you can debug.

Step 1:
Clone or copy the react starter app from Critical Path’s react-starter on githhub to your local machine and rename the folder from react-starter to safepark. Let’s make some changes to our package.json before we do an npm install.

Step 2:
Open package.json and change the name field to our project name: “safepark”

Step 3:
We need to add several new @types and dependencies. To save some typing, replace the contents of package.json with the following:

{
  "name": "safepark",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack",
    "start": "webpack-dev-server --open --history-api-fallback"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.6.4",
    "@babel/preset-env": "^7.6.3",
    "@types/es6-promise": "^3.3.0",
    "@types/node": "^10.9.4",
    "@types/react": "^16.4.14",
    "@types/react-dom": "^16.0.7",
    "@types/react-router": "^5.1.2",
    "@types/react-router-dom": "^5.1.2",
    "awesome-typescript-loader": "^5.2.0",
    "clean-webpack-plugin": "^0.1.19",
    "copy-webpack-plugin": "^4.5.2",
    "css-loader": "^0.28.11",
    "expose-loader": "^0.7.5",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.9.3",
    "office-ui-fabric-react": "^6.69.0",
    "react": "^16.5.1",
    "react-dom": "^16.5.1",
    "react-router": "^5.0.1",
    "react-router-dom": "^5.0.1",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.21.0",
    "typescript": "^3.0.1",
    "url-loader": "^1.0.1",
    "webpack": "^4.19.0",
    "webpack-cli": "^3.1.0",
    "webpack-dev-server": "^3.1.5"
  },
  "dependencies": {
    "@babel/polyfill": "7.6.0",
    "@microsoft/sp-core-library": "1.9.1",
    "@microsoft/sp-lodash-subset": "^1.9.1",
    "@pnp/common": "^1.2.7",
    "@pnp/graph": "^1.3.6",
    "@pnp/logging": "1.3.6",
    "@pnp/odata": "1.3.6",
    "@pnp/sp": "1.3.6",
    "babel-loader": "^8.0.6",
    "node-sass": "4.13.0",
    "popper.js": "1.16.0",
    "prop-types": "15.7.2",
    "query-string": "^6.8.3",
    "querystringify": "^2.1.1",
    "react-app-polyfill": "1.0.4",

//REMOVE
    "react-redux": "^7.1.1",
    "redux": "4.0.4",
    "redux-thunk": "^2.3.0"
  }
}

Step 4:
On the cmd line, in the same dir that contains package.json, do an npm install

While the node modules are installing, let’s create our projects folder structure.

We need to create the following folders and a new file, configureStore.ts, in the src folder:

[December 14, 2019]: This project is being changed to use Functional components and React Hooks. Therefore folders: redux, actions, and reducers are no longer required. Also we no longer need configureStore.ts

  • api
  • models

    //REMOVE
  • redux
    – actions
    – reducers
  • configureStore.ts

We should now have a structure like this:

safepark folder structure

Let’s tackle models first:

Create two files in the models folder, one named IVehicle.ts and the other named ILocation.ts

export default interface IVehicle {
    Id: number;
    LicensePlate: string;
    Color: string;
    Make: string;
    Model: string;
    Year: string;
}
export default interface ILocation {     
      Id: number;     
      Building: string;     
      DirectionFromBuilding: string;     
      ParkingStructure: boolean;     
      PoleNumber: string;     
      NearestLandmark: string;     
      DirectionFromLandmark: string; 
}

[December 14, 2019 UPDATE] actionTypes are no longer necessary.

In the actions folder, create a file named actionTypes.ts and add the following.

export const SET_REQUEST_TYPE = "SET_REQUEST_TYPE";
export const LOAD_REQUESTS = "LOAD_REQUESTS";
export const LOAD_HOMEPAGE = "LOAD_HOMEPAGE";
export const LOAD_CAMPUSES = "LOAD_CAMPUSES";
export const LOAD_BUILDINGS = "LOAD_BUILDINGS";
export const SHOW_FEEDBACK_FORM = "SHOW_FEEDBACK_FORM";

[December 14, 2019 UPDATE] index.ts is no longer necessary.

In the reducers folder, create three files index.ts, buildingsReducer.ts and initialState.ts

index.ts

import { combineReducers } from 'redux';
import buildings from './buildingsReducer';


const rootReducer =  combineReducers((
    buildings
));

export default rootReducer;

[December 14, 2019 UPDATE] buildingReducer.ts is no longer necessary.

buildingsReducer.ts

import * as types from "../actions/actionTypes";
import initialState from "./initialState";

export default function buildingsReducer(state = initialState.buildings, action) {
    switch (action.type) {
        case types.LOAD_BUILDINGS:
            return action.buildings

        default: return state;
    }
}

[December 14, 2019 UPDATE] initialState.ts is no longer necessary.

initialState.ts

export default {
    buildings: []
}

[December 14, 2019 UPDATE] configureStore.ts is no longer necessary.

Add the following to configureStore.ts

import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from './reducers';
import thunk from 'redux-thunk';

export default function configureStore(initialState) {
    const composeEnhancers = compose; // add support for Redux dev tools

    return createStore (
        rootReducer,
        initialState,
        composeEnhancers(applyMiddleware(thunk))
    );

}

Now let’s create our index.tsx and index.html files in the src dir.

index.tsx

import * as React from 'react';

// REMOVE
import { render } from 'react-dom';

import App from './components/App';
import { HashRouter } from 'react-router-dom';

// REMOVE
import configureStore from './redux/configureStore';
import {Provider as ReduxProvider } from 'react-redux';

// ADD
import { initializeIcons } from '@uifabric/icons';
import { render } from 'react-dom';

// REMOVE
const store = configureStore({});

// REMOVE
render(
    <ReduxProvider store={store}>
        {topLevelAppComponent}
    </ReduxProvider>, target);

// ADD
initializeIcons();

var topComponent = <HashRouter><App/></HashRouter>;
var target = document.getElementById('react-target');

render(topComponent, target);
======================================================

// NEW VERSION

import * as React from 'react';
import App from './components/App';
import { HashRouter } from 'react-router-dom';
import { initializeIcons } from '@uifabric/icons';
import { render } from 'react-dom';

initializeIcons();

var topComponent = <HashRouter><App/></HashRouter>;
var target = document.getElementById('react-target');

render(topComponent, target);
index.html

<!DOCTYPE html>
<html>

<head>
    <title>Extended Parking</title>
    <meta charset="utf-8" />
</head>

<body>
    <div id="react-target" />
</body>

</html>

Next, create another folder in the src folder called environments

In the environments folder create two files, environment.prod.ts and environment.ts. We will change the


SHAREPOINT_BASE_URL: '',
API_URL: ''

to match our environment in later posts. For now,

environment.prod.ts

export const environment = {
    production: true,
    SHAREPOINT_BASE_URL: '',
    API_URL: ''
};
environment.ts

export const environment = {
    production: false,
    SHAREPOINT_BASE_URL: '',
    API_URL: ''
};

Configure webpack

At this point, in the same dir as project.json, on the command line, if you execute npm start the app will launch in the browser but you will see the error Cannot GET /

Even if you add publicPath: ‘/’ and historyApiFallback: true, so for now I’m commenting publicPath out.

The only way to debug at this point is to upload to SharePoint and launch the browser.

Not sure if this would be the same on Windows, I have a MAC. But we also need to change the HtmlWebpackPlugin line or we will only see a blank page.

See the bolded code below:

const webpack = require("webpack");
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin')
const rand =  Math.floor(Math.random() * 999999);

module.exports = {
    entry: ['@babel/polyfill','./src/index.tsx'],
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'scripts/bundle'+rand+'.js'
 // REMOVE - Need to look into this further, now getting Cannot Get error message on all local launches.
         //,publicPath: '/' 
    },
    resolve: {
        extensions: ['.js', '.json', '.ts', '.tsx'],
    },
    devServer: {
        historyApiFallback: true,
    },
    plugins: [
        // UNCOMMENT FOR BUILD:
        // new HtmlWebpackPlugin({ template: path.join(__dirname, 'src', 'index.html'), filename: "./index.aspx" }),

        // COMMENT OUT FOR BUILD - WE ONLY NEED THIS FOR LOCAL TESTING ON A MAC
        new HtmlWebpackPlugin({ template: path.join(__dirname, 'src', 'index.html') }),

        new webpack.DefinePlugin({
            "process.env.API_URL":JSON.stringify("{YOUR SHAREPOINT URL}")
        }),
        new CleanWebpackPlugin(['dist']),
        new CopyWebpackPlugin([{ from: './src/favicon.ico', to: 'favicon.ico' }])
    ],
    module: {
        rules: [
            { test: /\.js$/, enforce: 'pre', exclude: /node_modules/,
            use: [{ loader: `babel-loader`, query: { presets: ['@babel/preset-env','@babel/preset-react']}}]},
            { test: /\.(ts|tsx)$/, loader: 'awesome-typescript-loader' },
            { test: /\.css$/, use: ['style-loader', 'css-loader'] },
            { test: /\.scss$/, use: ["style-loader", "css-loader", "sass-loader"] },
            { test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }] }
        ],
    },
    mode: "development",
    devtool: 'source-map',
    devtool: 'cheap-eval-source-map'
};

There are a few more steps to do before we can build and launch the app in our browser from SharePoint. In the next post, Part 3: Style with the Ui-Fabric library, of the series we will improve on this by adding Ui-Fabric elements and styles.

References

Register your app with Azure Active Directory
Overview