TABLE OF CONTENTS
- Getting started with React in SPFx projects
- Project setup and structure
- Referencing a React component in your SPFx solution
- Creating a React class component
- Best practices
- Taking advantage of the already available controls
- React fundamentals
- Splitting components
- Passing state between components
- Things to avoid
- 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.
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.
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.
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.
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.
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.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.
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
2. In the render method, the parent component passes the currently selected item from its state to the child component — which contains 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.
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.
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.
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.
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.
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.
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