Search This Blog

Sunday, May 17, 2020

HOW TO GET STARTED WITH REACT FOR BUILDING ADVANCED SPFX SOLUTIONS

TABLE OF CONTENTS

  1. Getting started with React in SPFx projects
    1. Project setup and structure
    2. Referencing a React component in your SPFx solution
    3. Creating a React class component
    4. Best practices
    5. Taking advantage of the already available controls
  2. React fundamentals
    1. Props & state
    2. Lifecycle methods
    3. Event handlers
  3. Splitting components
  4. Passing state between components
    1. The render prop technique
    2. Redux
    3. React hooks
  5. Things to avoid
    1. Copying props to state
    2. Using unsafe lifecycle methods
  6. What next?

GETTING STARTED WITH REACT IN SPFX PROJECTS

PROJECT SETUP AND STRUCTURE

Creating an SPFx project with React happens the same way as setting up a solution without a framework, except now you need to select React as the “framework” you wish to use. Below you can see a picture of what the project structure looks like immediately after it has been generated. As the size of your project grows, consider adding more folders to organize your code.

REFERENCING A REACT COMPONENT IN YOUR SPFX SOLUTION

React components are the individual building blocks you implement and use for creating your complete React solution. There can be different sized components, and they can be nested with one another. A component can be as small as a single input field, or much larger, such as an entire form (which can contain the input field). Typically, each (class) component has its own file where its lifecycle methods, event handlers, and optionally also props and state are defined.
When you generate a new React SPFx project, you can see an example of how to reference a React component and get it rendered by your SPFx solution. When you reference a component, you need to initialize its props by passing values to all of the different properties it has. By default, the generated React component only has the description prop, and here it is getting its value based on what the user has set as the description in the SPFx web part properties.
public render(): void {
const element: React.ReactElement<ICatsProps> = React.createElement(
Cats,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
}
When you want to reference a React component inside another React component, it is enough that you just create the child component in the render method of the parent component by doing either one of the following.
{ React.createElement(Cat, { name: "Whiskers", color: "Brown" }) }
<Cat name={"Whiskers"} color={"Brown"} />

CREATING A REACT CLASS COMPONENT

In the components folder of a new SPFx project, you already have .tsx file named after your web part. In this component file, you’ll need the essential imports, interfaces, and class methods. Some people prefer to have the props and state interfaces in separate files, but I find it easier to have them in the same file.
import * as React from 'react';
export interface ICatsProps{
/* You will define your props here */
}
export interface ICatsState{
/* You will define your state here */
}
export default class Cats extends React.Component<ICatsProps, ICatsState> {
public constructor(props: ICatsProps) {
super(props);
this.state = { /* You will initialize your state here */ };
}
public render(): React.ReactElement<ICatsProps> {
return (
<div>
{ /* You will define the elements you want to render - possibly conditionally based on state - here */ }
</div>
);
}
}
The props interface contains read-only properties that you initialize when you call the component as a part of your solution. You can think of them as parameters. The state interface again is used for tracking the current state of the component, and – unlike props – it can be changed to reflect fetched information or user input. We’ll take a closer look at props and state a bit later in this article.

THE CONSTRUCTOR

If you are creating a class component and are planning on using state, you’ll need a constructor. In it, we must always call super() to instantiate the parent class which, for example, grants us the ability to use this. You can call super() with props or without, but if you use it without, this.props will be undefined if you attempt to use it in the constructor. To avoid getting confused if you later want to use this.props in your constructor and forget that you need to initialize props first, you can always call super(props) just in case.
You also need to initialize the component state in the constructor using the this.state = { ... } notation. If for some reason you don’t need to use state (in which case you are creating a fully controlled component), then you don’t necessarily even need the constructor, and you can get rid of the state interface completely and replace its reference in your class definition with {}. Also, if you don’t need to use state, you might want to consider creating a functional component instead of a class component.

THE RENDER METHOD

render() is the only truly compulsory method in a React class component. The method should return the elements you wish your component to render. By default, there should always be just one root element, so if you are returning multiple elements, make sure to wrap them inside a single div. Another option is to use React.Fragment.
When you define elements in a React component, the syntax is called JSX (JavaScript syntax extension). There are some differences between JSX and regular HTML when it comes to defining element properties. For example, class in HTML is className in JSX. You’ll learn these very quickly, especially when using a code editor with IntelliSense such as Visual Studio Code.
The render method gets executed only and always when the component props or state changes. This behavior allows us to make components look different in different situations. More about this when we discuss props and state.

BEST PRACTICES

There are a couple of best practices you should keep in mind when creating SPFx projects with React:
1. The SharePoint Framework web part context is not directly available in React components. If you need to use things like MSGraphClient or AadHttpClient in your React component, you might be tempted to pass the whole this.context down to your React component. Don’t do it! Instead, do either one of the following:
  • Pass only the individual objects from the context that you truly need to your child component. This is sufficient if your solution is very simple.
  • If your solution is more complex, doing the previous will most likely lead you to pass down a large number of different properties, objects, and methods to your child components as props. This will make your solution very hard to maintain. Instead, you should rather create a service (singleton) with methods where you do all the things that require information from the context. Then you can use that service and its methods from your React components. Check out my blog post How to use the service locator pattern in SPFx solutions for more detailed instructions.
2. Do all the SharePoint Framework specific things in the web part/extension part of the solution.
When you follow these two above principles, it helps with unit testing and moving the code away from SharePoint if needed.
3. Don’t manually upgrade the React version that is included when you generate the SPFx project if you want to avoid breaking things.

TAKING ADVANTAGE OF THE ALREADY AVAILABLE CONTROLS

Another nice thing about using React in your SPFx projects is that you get to take advantage of the Office UI Fabric React and PnP SPFx React controls. With these controls, you can easily build graphical user interfaces that have a high level of functionality and the same look and feel as the rest of Office 365. No need to reinvent the wheel yourself!
I also found the code samples on the Office UI Fabric website super helpful on my React learning journey. The samples show you how to use the controls, and you learn a thing or two about React at the same time.
Another great place to see how to do something is to look at the SPFx web part and extension samples. Can you find something similar to what you want to accomplish? Look at the source code of that sample and use it as an example!

REACT FUNDAMENTALS

PROPS & STATE

In my opinion, the most elementary thing you need to grasp about React is how to use props and state. Everything revolves around and always comes back to them. When I earlier said that React is more about learning a new way of thinking, this is it.
The props are read-only properties you set values to when you call your React component. They are a way to transfer information from the parent component to the child. You can also use them in your React component to control how it behaves in different situations – when different kind of data is passed down to it.
The state can also be used for controlling, e.g., how your component is rendered. What makes state different from props is that you can change its values. You typically initialize state with null values in the constructor and later change the values when something happens in the component to reflect that change – the new state of the component.
Remember when I said earlier that the render() method is executed whenever the props or state changes? You can change the state by calling the this.setState() method, and that will make your render method execute again, taking into account the new state values. There is also another way to set state: this.state = { ... } but that should only be used in the constructor as it does not trigger the render method execution.

CONDITIONAL RENDERING

Whenever you want to manipulate the DOM to, e.g., hide something, you do it via the state instead of changing element properties. In React, you never do things like document.getElementById() and then attempt to hide that element. You render your component conditionally based on its state.
Here I am controlling whether a button is enabled or disabled via state. By default, the button is enabled, because I am setting this.state = { buttonDisabled: false }; in the constructor. The button will remain enabled until I call this.setState({ buttonDisabled: true}) somewhere else in my code (e.g., immediately after a button click, so the user can’t click the button again). I am also rendering the button only when this.props.buttonText is something else than null: if someone decides to pass null to the component as the buttonText value, then the button will not be shown.
export interface ICatsProps {
buttonText: string;
}
export interface ICatsState {
buttonDisabled: boolean;
}
export default class Cats extends React.Component<ICatsProps, ICatsState> {
public constructor(props: ICatsProps) {
super(props);
this.state = { buttonDisabled: false };
}
public render(): React.ReactElement<ICatsProps> {
return (
this.props.buttonText ?
<button disabled={this.state.buttonDisabled}>{this.props.buttonText}</button>
: null
);
}
}
How you manage your component through its state might feel a bit hard to grasp at first, but it will become like a second nature soon enough. It is the single most crucial thing to understand about React, so don’t you dare to attempt to manipulate the elements in the traditional way in a React solution! *shakes finger* ðŸ˜€

USING STATE TO STORE AND DISPLAY DYNAMIC DATA

In addition to controlling how your component is rendered based on state values, the state can also be used for storing data. The data can first be fetched from somewhere and then set to the state by using this.setState(). That will then trigger the render() method, which allows you to display the data by referencing the state.
The state can also be used to store the values user inputs through input controls. When the user submits the form, the data contained in the state can be sent to an API to get processed.
Remember to always check the state for null values before rendering if you are using null for initializing the state in the constructor. If you attempt to render the properties of an object that is null, you’ll get an exception.
export interface ICatsState {
cats: ICat[];
}
export default class Cats extends React.Component<ICatsProps, ICatsState> {
public constructor(props: ICatsProps) {
super(props);
this.state = { cats: null };
}
public render(): React.ReactElement<ICatsProps> {
return (
<div>
{ this.state.cats ?
this.state.cats.map((cat) => (
{React.createElement(Cat, { name: cat.name, color: cat.color })}
))
: null }
</div>
);
}
}
The state is truly the thing where all the magic is happening. Whenever you find yourself thinking hmm, usually I’d start by calling document.getElementWithId, you want to use state instead.
Now you may be wondering, where should I call the this.setState() method to change the state and hence how the component is rendered? You have two options: lifecycle methods and event handlers.

LIFECYCLE METHODS

React has many lifecycle methods that allow you to change the state whenever the component is added, updated, or removed. You can find a list of all lifecycle methods in, e.g., the official React documentation but let me tell you a little bit about a couple of them I’ve found myself using the most.

COMPONENT DID MOUNT

If you need to fetch data from a data source – such as Microsoft Graph or a database via a custom API hosted in Azure – to be displayed by your component when the page is loaded, you should do it in the componentDidMount() method, because:
  • Fetching data in the componentDidMount() method of your component allows the rest of your SPFx solution to get rendered already before the slow data fetch has completed, leading to better performance and user experience. You can use the developer tools in Chrome to see how your SPFx solution gets rendered on slow connections.
  • You can’t use the async/await pattern all the way up to the render() method.
To get your component to display dynamic data immediately after the component is added to the DOM, fetch it in the componentDidMount() method and then set the data to the component state using this.setState(). When you do this, the render() method is fired automatically where you can reference the state value to display the fetched data. Here I am fetching some data based on the id the component receives as a prop, and setting it to the state.
public async componentDidMount() {
var cat = await this.getCat(this.props.id);
this.setState({ cat: cat });
}

COMPONENT DID UPDATE

Sometimes you might need to refresh the state of your child component when something happens in the parent component. In such a situation, your parent component calls the child component with new props, and fires the componentDidUpdate() lifecycle method. In that lifecycle method, you can then set the state of your child component to reflect those new properties.
Note that this lifecycle method also gets executed whenever the state of your component changes, and if you happen to setState() here, you have an infinite loop. To avoid this, you should check if the property you are basing your refresh behavior on has actually changed. For this purpose, you should utilize the prevProps argument that is automatically available in the componentDidUpdate() lifecycle method, and only proceed to update the state if that prop has a different value.
public async componentDidUpdate(prevProps) {
if (prevProps.id === this.props.id) return;
var cat = await this.getCat(id);
this.setState(cat: cat);
}
I’ve found this useful, for example, when I have been using the Office UI Fabric DetailsList and Panel components: the Panel content needs to change depending on the selected DetailsList item.
1. When an item is selected on the DetailsList, the parent component’s this.state.selection is updated which triggers the render method of the parent component.
2. In the render method, the parent component passes the currently selected item from its state to the child component — which contains the Panel — as a prop.
In the componentDidUpdate() method of the child component, we first need to check if the selection is different than previously. If yes, we set the information from the selection to an object and save it to the child component’s state.
public async componentDidUpdate(prevProps) {
if (prevProps.selection === this.props.selection) return;
var cat: ICat = {
id: selection["id"],
name: selection["name"],
color: selection["color"]
};
this.setState({ cat: cat });
}
3. We can then get the values from that state to be displayed in the text fields in the panel during the render() of the child component.

EVENT HANDLERS

Event handlers are methods you bind to certain control events, such as when a user presses a button. You might, for example, have a form with text fields, drop-downs, toggles, and so forth, and want to collect the data user inserts using those controls, and finally, send the information somewhere when the user clicks on a button.
You can collect the information the user inserts by binding event handlers to the controls, and calling this.setState() to save the entered data to the state. Note that you can’t directly update a specific object property stored in the state. You first need to get the entire object from the state, then update that single property, and then set that entire (now updated) object back to the state.
private onChangedColor = (value: string) => {
var cat = this.state.cat;
cat.color = value;
this.setState({ cat: cat });
}
When the user eventually clicks on the Save button and triggers the onSaveButtonClick() event handler, you can fetch all the information the user has previously inserted on the form from the state, and send the data forward to get processed.
private onSaveButtonClick = async (): Promise<void> => {
var cat = this.state.cat;
if (cat.name === null || cat.color === null) {
this.setState({ showError: true });
}
else {
await this.save(cat);
}
}
Using the arrow functions for binding event handlers hasn’t always been possible, and sometimes you might still encounter React projects where the event handlers get bound in the constructor, like below.
this.onChangedColor = this.onChangedColor.bind(this);
this.onSaveButtonClick = this.onSaveButtonClick.bind(this);
You can imagine that if there are a lot of event handlers, binding them in the constructor will lead to the constructor becoming enormous. Using the arrow functions allows us to save space. Also, it is far too easy to forget to bind a new event handler if you need to do it separately in the constructor. Forgetting to do that will result in incorrect behavior.

SPLITTING COMPONENTS

Splitting your solution to smaller nested React components is a common thing to do. Here are some simple guidelines for when you should create child components instead of having everything in one component.
  • Create a separate component when the component needs to appear multiple times or in several different places. As with any coding project, avoid repeating the same code in multiple places.
  • The component is a logically separate entity.
  • The component needs to have its own set of properties and state to accomplish what you want to do.

PASSING STATE BETWEEN COMPONENTS

Passing state between components is probably the first “advanced” case you’ll start looking into with React: you have nested components, and you need to share a state between them. Let’s use the already mentioned Office UI Fabric DetailsList and Panel as an example again.
Imagine a situation in which you have the Office UI Fabric DetailsList, and you want to edit one of the items via the Panel. You need to be able to control when that panel is open (displayed) and closed (hidden). That happens via a state that needs to be shared by both components: the list (parent) and the panel (child).

THE RENDER PROP TECHNIQUE

There is a native way of doing this for simple cases called the render prop technique. When using this technique, you have the shared state in the parent component, and in that parent component, you also have a function (event handler) that changes the state.
export interface ICatsState {
showPanel: boolean;
}
private hidePanel = async (): Promise<void> => {
this.setState({ showPanel: false });
}
You can then pass that function to the child component as a prop. When you need to change the state in the parent, you call the function in the child component by referencing the function in its props.
<EditCat hidePanel={this.hidePanel} />
export interface IEditCatProps {
hidePanel: any;
}
<button onClick={this.props.hidePanel}>{strings.Close}</button>

REDUX

If your solution is enormous, and you have multiple component layers (components, inside components, inside components, etc.), the render prop technique can make the project quite messy and hard to follow. In these more advanced scenarios, Redux can make it easier for you to handle state through all those layers.
Redux is a separate tool you can include in your React project to help you manage the state between components. It is yet another thing to learn, though. When people have experienced React to be difficult to learn, it has probably been because they’ve had to learn both React and Redux at the same time. If your project is small or medium, introducing Redux is probably overkill and will introduce unneeded complexity.

REACT HOOKS

React version 16.8 – which the current SPFx version supports – introduced a new feature called React Hooks that offers us an another native way of passing state between components. Ever since its release, there has been a lot of debate whether React Hooks can replace Redux entirely or not in this regard. Some say it can; some say it can’t. Perhaps React Hooks is the optimal solution for those middle-ground cases when you have more than two layers of components, but your project isn’t yet so big that you want to introduce Redux to it.

THINGS TO AVOID

There are many different ways of accomplishing the same things in React. Some are considered as the best practices while others as anti-patterns — the things you should not be doing. If you ever catch yourself doing something that feels a bit “hacky”, do a quick search to ensure you are not implementing an anti-pattern, and learn the right way of doing the thing instead.

COPYING PROPS TO STATE

One of the most common anti-patterns is copying props to state. When you do this, you create a “second source of truth”, which can lead to bugs. The only exception is if you need to seed the props to create the component’s initial state.

USING UNSAFE LIFECYCLE METHODS

There are some legacy lifecycle methods that have been marked as UNSAFE. Those methods will discontinue working in React version 17. As of this writing, the latest SPFx version (1.9.1) uses React version 16.8.5, so those unsafe lifecycle methods such as componentWillReceiveProps() currently work. However, to make your solution future-proof, avoid using these methods and use the recommended alternatives instead.

No comments:

Post a Comment