Getting started with Redux in SharePoint Framework – Part 2

As you can read in part 1 we are converting a ‘plain’ React SPFx webpart to a version with Redux to have an external stage management mechanism.

We are starting from the basic idea of having a webpart that displays a list of sessions of a SharePoint Saturday. Next to displaying the sessions, the user can also remove a session from the list and add a new session the list.

To convert this to a working Redux application, we need to follow the next steps:

  1. Install necessary npm packages
  2. Create and configure store
  3. Create actions
  4. Create reducer
  5. Decouple components from each other (remove callback functions)
  6. Connect components to the reducer
Overview of the webpart

NPM packages

Basicly you need to install Redux with the following command

npm install redux

You also needs to add the React binding for Redux

npm install react-redux

Finally you need to install Redux-Thunk for middleware operations (more information later on this post).

npm install redux-thunk

Create and configure the Redux store

Using Redux, the store is THE place where all the application data (state) is stored. The only way to change the data is to dispatch an action and to process that action with a reducer.
Note: In comparison to other state managers Redux only has one store.

Creating the store is very simple. Create a new folder called store (not mandatory but recommended), create a new file configurestore.ts and add the following code to the file:

After creating the store, the application and components need to know that there is a store and that it can be used to save, update and retrieve data.
This needs to be done in the top level component of our component structure => in our example ReduxSessionWp.tsx. Call the configureStore() function and pass it to the Provider.

A Provider is a wrapper component that makes it possible for the nested components (the ‘normal’ components of our application) to access the store by using the connect() function.

Actions

Now that the basic one-time-only configuration part is over, we can start defining the actions. Actions are pieces of information that the application and individual components can send to the reducer to update the global application state.

An action is an object built up with 2 basic properties:

  • Type
  • Data (string, number, complex object…)

When a component wants to update the global application state, it needs to dispatch an action (for example: {Type: “Add_Session”, Data: {speaker:”Yannick”, session: “ABC”}}). The reducer will pick up the trigger and execute that specific action.

For each action that is possible within your application, you need to define it by using the ActionTypes. Basically it needs to be a const string but you can also manage it with an enum for each possible action. By using the enum you can reuse it more easily in your application.

Dispatching an action is possible by calling the corresponding function. Those functions are called action creators. The only thing they do is create an action object of type and data.

Reducer

The only location where the global application state in Redux can be changed is inside the Reducer.

The reducer is composed of pure functions that respond to the actions that are sent to the store. It will take in the old state and an action and returns the new (updated) state to the store.

Note: Pure functions are functions that return the same output for the same given input, and don’t produce any side-effects.

If we take a look at the code of the reducer it ‘just’ contains a large switch statement for each possible action(Type).

First, the reducer creates a new clone of the current/old state. Next the switch decides, based on the action.type, which specific action needs to be done against the state.

Decoupling of components

In our React example some components were coupled with each other to pass data and functions from one component to another. Now with Redux, that coupling of components becomes unnecessary. We have the possibilty to individually couple/connect the components to the state.

Connecting components

At this moment the components are unaware of the global application state and can’t update it with new information. Connecting a component to the store is done in multiple steps.

mapStateToProps

This function will connect specific (you can define which ones) information from the state to properties of the component. So instead of passing data from one component to another we now connect directly to the data.

Your application state (probably) contains a lot of data/objects, so loading all those objects to every component is overkill. Redux is smart enough to provide the possibilty to only load the objects you need. In the example above we only load the sessionItems from the state.

As with the local state of components, when there is a change of the application state, every component (that is connected to that specific updated object) will immeditialy receive the updated object.

mapDispatchToProps

Next to passing the objects between components in the React-only example, we also passed callback functions between components. This becomes also unnecessary with Redux and mapDispatchToProps. It allows the component to load the (pure function) actions into the properties of the component. This allows the component to simply call the action by this.props.ACTION.

Connect

So now all the converting is done of the data and functions inside the component, the only thing you need to do is one simple line of code for each component (that needs to be connected):

It let’s the component listen to the store and provides all the data and functions it needs. The properties mapStateToProps and mapDispatchToProps are optional. So it is possible to have components only having dispatched actions (Form) and components only reading data from the store (SessionList).

Middleware for async calls

As you maybe noticed, the last thing we did not yet talk about, was the async call to our API.

By default actions are synchrounsly in Redux. To achieve an async action, like for example calling an API, we need to implement Middleware. There are two popular libraries available for this, I have choosen Thunk.

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

https://github.com/reduxjs/redux-thunk

Here is a nice animation availble which explains this concept:

When a view (component) triggers an action, the Middleware takes care of your API calls and updates your state.

How to implement?

Maybe you noticed it already, when I configured the store, I already included the Middleware. It is actually as simple as importing applyMiddleware (redux module), importing thunk and ThunkDispatch (redux-thunk module), and passing through applyMiddleware(thunk) in the createStore function.

Next, create a new file AsyncFunctions.ts under the actions folder. This files contains the httpClient call to the API and will dispatch (after retrieving the data) the addNewSession action. This action is exactly the same as we use inside the form component.

Recap

Tadaaa! Normally after following all the previous steps, you should have a working Redux version of your application.

The initial setup looks very difficult, especially the first time, but eventually it saves you a tremendous lot of work compared to when you needed to handle all the state changes by propagating the props and functions.

Feel free to contact me by my blog, Twitter, LinkedIn.. if you have any questions or comments!

Extra references can be found here:

https://github.com/yborghmans/SPFxReduxDemo
https://redux.js.org/
https://github.com/reduxjs/redux-thunk

One thought on “Getting started with Redux in SharePoint Framework – Part 2

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.