Introduction
State management is a way of tracking the state of an application at any given time. It also helps us with communication and sharing of data across components in a consistent manner. Additionally, it improves the quality of code through making it more readable and predictable.
To begin with, what is a state? A state is linked to a component. It holds the value of variables and when the state of a component changes, Lightning Web Components re-renders the UI. The state is retained throughout the lifespan of the component unless it is explicitly cleared.
In the context of Lightning Web Components, state management is not intrinsic, and therefore the need arises to incorporate the same, which makes an application faster, lighter, and easier to maintain. This is when Redux comes into the picture.
The objective of this article is to make the reader aware of the key capabilities of state management. We are going to cover state management, Redux, along with some examples of code.
Why should we use State Management?
Without the use of state management, data flows everywhere in an application, making it difficult to find a single source of truth. In a situation where we need to keep track of different interactions on a page, maintaining the code can be cumbersome. This might result in an inefficient process of development. As an application grows, the number of features that we need to keep track of increases as well. Consequently, this makes it cumbersome to keep track of the state that each feature has.
At Deloitte, some of our clients require a great deal of customization in Salesforce environments. In addition to configuring out-of the-box features, we also build non-declarative processes. This is where building Lightning Web Components (LWCs) comes into play. LWCs enable developers to build front ends tailored to Salesforce environments. When an application makes use of numerous components, we use the state to keep track of them.
For instance, when we create complex UI for use within a community, we typically call these modules. These modules typically hold the business logic and stitch all of the smaller reusable components together. Each of the smaller components may need to be registered with the state.
State Management and Redux
Tracking the state without a State Management Tool
We can track the state of an application without using a state management tool, such as Redux, by sharing the state across LWCs. In this example we have the following two components:
- sharedStateComponent : Stores the shared state and grant access across multiple components.
let _ stateData;
const SharedStateObject = {
setData: (newVal) => {
_data = newVal;
},
getData: () => {
return _stateData;
}
};
Object.freeze(SharedStateObject);
export { SharedStateObject };
- exampleComponent : imports the object that was created in the shared state component and use it to update the shared state upon checking the checkbox.
import { LightningElement, track } from "lwc";
import { SharedStateObject } from "./sharedStateComponent";
export default class MyComponent extends LightningElement {
@track stateData;
refreshStateData() {
return (this.stateData = SharedStateObject.getData());
}
updateState(newValue) {
SharedStateObject.setData(newValue);
this.refreshStateData();
}
updateStateToChecked() {
this.updateState(true);
}
updateStateToUnchecked() {
this.updateState(false);
}
}
Ticking on the checkbox will update the state accordingly as is illustrated below. The initial state is set to unchecked. On ticking the checkbox, the state changes to the checked state, and on further unticking the checkbox, the state changes to the unchecked state.
However, for clients with a large customer base, daily UI interactions with customers are needed to be tracked in an efficient manner. This requires the usage of a state management tool. This is where Redux comes into the picture.
Tracking the state by using Redux
Redux is a standalone library that can be used with LWCs, and other frameworks like React, Angular, Vue, Ember, and vanilla JS. It consists of three main components:
Store: Stores the complete state of an application. The application should be able to save and recover its current state.
Actions: The interaction between the application and the store. The application sends an action with some data. These actions are then handled by a so-called reducer.
Reducer: A function that takes the current state and transforms it into a new state.
Let us illustrate this concept with a mock application. We have a webpage containing an overview of client information. It contains a modal that opens after the click of a button. It also contains a button that can download a file. The parts that need to be tracked are:
- The button for opening the modal, and
- The button that will track the progress of the download.
The reducer will contain all the states of this application. For instance, there can be a state that tracks the loading of the webpage. In our case we track the state of the two buttons we mentioned above, and each of them is used as a state within the reducer. The button contains the state of opening the modal, and the download button contains the state that tracks the downloading progress of the file.
export const OverviewReducer = (state, action) => {
// When initializing take a copy of the initial state
// We need to clone it to avoid modifying the original structure in case we need to reset
if(!state){
state = deepClone(initialState);
}
const { payload } = action;
let found;
switch (action.type) {
case 'SET_SELECTED_OVERVIEW_INFORMATION' :
return { ...state, overviewInformation: payload };
Coming to actions, these are events that mutate the state within the application. For instance, the state for showing the information on the webpage has two fields, the type, and the payload. The value assigned to type should be a string with a name describing the action. The value assigned to payload contains data that can be mutated in the action.
export const setOpenModal = openModal => ({
type: 'SET_OPEN_MODAL',
payload: openModal
});
export const setDownloadedDocument = document => ({
type: 'SET_DOWNLOAD_DOCUMENTS',
payload: document
});
To build a feature together with redux, we need to create multiple LWCs. Each component has a different responsibility. The components need to pass data between each other. From an elevated level, the diagram below is created. Each component has a different responsibility. The redux component should return the correct states for all the LWCs. The parent component needs to make sure that the user gets redirected to the correct page. The page loads, depending on valid user details. If the user details are valid, then the property from the state changes to true and the child component will be rendered. Also, we have a modal that will render value A or B depending on the Redux property.
Conclusion
Redux is a good solution for managing the state in large scale applications, however, it is not suitable for all projects that contain LWCs. We need to assess whether the implementation of redux makes sense for the project, depending on the scope of the components and time constraints of the project. We have listed considerations in which case you might want to use state management or not.
When to use Redux
Application Size
- A large and complex application with multiple LWCs that need to share states amongst each other, is a good candidate for using Redux. It will enable you to centralize the state management and make it more efficient to manage the flow of data between Lightning Web Components.
Scalability
- As soon as your application starts to grow, keeping track of the state of each LWC becomes harder to manage. Redux enables here a scalable solution for managing the state of your application, making maintenance more convenient.
Single source of truth
- If data needs to be managed, such as real-time API data, Redux can help simplify this process by providing a single source of truth for all your data.
Debugging
- The clear structure of Redux enables you to manage the state from the console window in your browser. Making it easier to detect any bugs.
When NOT to use Redux
Small applications
- A small and simple application with a few LWCs that does not require any or limited data sharing between components, does not require Redux.
Performance
- In case your application has performance constraints, adding Redux can bring an additional layer of complexity that can impact the performance.
Learning
- Investment in terms of learning is needed to fully grasp the principles and concepts behind state management and how to implement Redux in particular.