Last Update: Sep 04, 2024 | Published: Jul 31, 2017
Over the last few months, I have written several articles detailing how to create client web parts in the SharePoint Framework (SPFX) using ReactJS. More recently, I dove deeply into a single web part to show how to interact with SharePoint lists, create component trees, and manage communication between parent and child components. In all of those articles, I had to manage state for the various web parts but I chose to keep the management simple and traditional using properties. In this article, I will discuss state management more deeply. I will explain why a structured approach is important in complex SPFX projects and better than a more traditional approach.
In my recent articles, I have been working on a spreadsheet web part that I called CRUDSheet, which you can download from my GitHub repository. This web part consists of a hierarchy of components representing a sheet, rows, and cells in a spreadsheet all backed by a SharePoint list. In one article, I discussed managing state between the components using a traditional React approach with properties.
The traditional React approach involves state management through the use of properties and state information passed between parent and child components. Parents can communicate with children by passing data down to them. This data can also contain callback functions that the child can use to communicate with the parent. Within a component, state can be maintained in an object. If state changes, then a component can call the setState method to force a redraw of itself and any child components in the tree.
The traditional approach to state management in React works fine in small projects but suffers from some challenges in larger projects. In order to keep things simple, state should be managed at the top of the component tree. Changes to state are then addressed by redrawing the entire component tree. This approach, however, means that the components making up the leaves in the tree must relay state change information all the way to the root. Consequently, every intermediate component must also participate in the relay operation resulting in a ton of unnecessary code. You can see this if you go back and review my previous articles describing the CRUDSheet.
Instead of relaying state information up and down the tree, a better approach is to move state management out of the component tree into a separate object store object. The object store is then made accessible to all the components in the tree. If any component needs to change state, it simply does so directly in the object store. The root of the component tree is alerted to these changes and will force a redraw of the entire tree to reflect the changes. This makes for a much simpler state management infrastructure that works nicely in small and large projects.
Redux is a state container for JavaScript and can be used with any framework. I will be using it with React and SPFX. Redux stores the entire application state in a single object tree within a single Store. The application state can only be changed by emitting an Action that describes what happened in the system. Actions provide a defined set of behaviors, ensuring that state is never changed directly by application code. Redux changes state in response to an Action by processing the state through a function known as a Reducer. Reducers take the previous state and return the current state, which can then be used as the basis for redrawing the component tree.
Initially, Redux can seem complex but it is actually easy to get going. Like many programming concepts, it is also much easier to understand through example than through theory. With that in mind, I am going to walk you through creating a web part that allows a user to cast votes for their favorite family game. This will be a simple web part with voting buttons and progress bars to display the results as shown in Figure 1. The completed code is available in my GitHub repository.
Figure 1 — The Voting Web Part
In order to get started, I simply used the Yeoman Generator for SPFX and created a new web part based on the React framework. Next, I added the NPM package for Redux. I also added the package for the Office UI Fabric React components to create the voting buttons and progress bars. Once the basics were in place, I set about creating the Actions, Reducer, and Store.
Redux uses Actions to define all of the possible behaviors that can change application state. Actions are nothing more than JavaScript objects that get dispatched to the Store. So, you must define a JavaScript object for every Action in the application. In my case, I have only one Action and that is to Vote. The Vote action also contains data indicating for which game the user voted. To represent this Action, I created enumerations and interfaces culminating in an IAction interface defining the action object. The following code shows how it was done.
Examining the definitions, you can see that the only possible action type is vote but the data can be one of three game types: checkers, chess, or fish (for "Go Fish"). Actions are typically created by functions, so for each combination, I created a function to return the appropriate action object.
Defining the Reducer Function
The application state is stored in a single object tree. Therefore, I had to create a definition of the object tree and provide an initial state. The following code shows the interface that defines my state object tree and its initial value, which just sets all the vote tallies to zero.
Redux uses a Reducer function to transform the previous state to the current state. The function for my web part will produce a new object of type IApplicationState by transforming the current state in response to a received IAction. In all cases, the vote tally is simply incremented in response to an Action.
Initializing the Store
The last thing to setup is the Store. Redux supports store creation through the createStore method. When a Store is created, it must be created by passing in the Reducer function, which then determines the type based on the definition of the state object tree. For an SPFX web part, you should create the Store in the OnInit event of the web part. This ensures that the Store is created once at the beginning of the web part lifecycle.
Once the Store is created, you must subscribe it to the render method of the web part. This ensures that the web part is redrawn whenever the state changes.
this.store.subscribe(this.render); Finally, you must pass the Store instance down the component tree to every component. This relaying only happens once and it ensures that any component in the tree may dispatch an Action to the Store. Regardless of which component in the tree changes the application state, the web part will redraw causing all of the components to redraw. The following code shows the properties interface for a component in the tree.
Changing Application State
Once all the pieces are in place, you can wire in the user interface to dispatch Actions to the Store. The dispatch method of the Store accepts an Action and changes the state accordingly. The following code shows a PrimaryButton component from the Office UI Fabric React library dispatching a vote when it is clicked.
<PrimaryButton onClick={() => { store.dispatch(voteChess()); }}>Chess</PrimaryButton>Summary and Conclusions
This article shows how to implement a state management Store with Redux in an SPFX web part project utilizing the React framework. I covered the creation of Actions to define all possible behaviors, a Reducer function for transforming state, and the initialization of the Store. The approach shown in this article stands in contrast to the more traditional approach of relaying messages up and down the component tree. In my opinion, Redux is a better choice because the definition of Actions provides a much stronger separation of concerns than properties and callback functions. Additionally, maintenance is easier because you can often change the definition of Actions or Reducers without altering the component tree itself. All-in-all, Redux represents a strong approach to state management in SPFX.