Share state across modules
This article describes how to share state across multiple modules by using data actions in Microsoft Dynamics 365 Commerce.
Data actions perform the important role of state management in situations where you must share state across multiple modules on the same page. In general, state is shared within the application state of the running Node application.
Examples
In this example, two modules share basic interaction. One module (sample-button) has a button, and the other module (sample-message) shows a message when that button is selected.
First, you must have a data action that returns an object that contains the number of times that the button has been selected. Here is what the code looks like.
// sample-state.ts
import { CacheType, createObservableDataAction, IAction, IActionContext, IActionInput, IAny, ICreateActionContext, IGeneric } from '@msdyn365-commerce/core';
export interface ISampleState {
clickCount: number;
}
/**
* SampleState - action input
*/
export class SampleStateInput implements IActionInput {
public getCacheKey = () => `SampleState`;
public getCacheObjectType = () => 'SampleState';
public dataCacheType = (): CacheType => 'request';
}
/**
* SampleState - action
*/
export async function sampleStateAction(input: SampleStateInput, ctx: IActionContext): Promise<ISampleState> {
return { clickCount: 0 };
}
/**
* SampleState - create new input for create action
*/
const createInput = (inputData: ICreateActionContext<IGeneric<IAny>>): IActionInput => {
return new SampleStateInput();
};
/**
* SampleState - create action
*/
export default createObservableDataAction<ISampleState>({
action: <IAction<ISampleState>>sampleStateAction,
input: createInput
});
In its current state, this data action has no implementation. It just creates a place to store an object in the cache. Because the two modules in this example must communicate with each other, it's helpful if they both observe this object. To give modules access to this object, you must make sure that the modules register the data action that you created earlier as a page load data action.
Here is the code for the sample-button module.
// sample-button.definition.json
{
"$type": "contentModule",
"friendlyName": "Sample Button",
"name": "sample-button",
"description": "Sample Button",
"categories": ["sample-button"],
"tags": ["samples"],
"module": {
"view": "./sample-button",
"dataActions": {
"sampleState": {
"path": "../../actions/sample-state/sample-state"
}
}
}
}
Here is the code for the sample-message module.
// sample-message.definition.json
{
"$type": "contentModule",
"friendlyName": "Sample Message",
"name": "sample-message",
"description": "Sample Message",
"categories": ["sample-message"],
"tags": ["samples"],
"dataActions": {
"sampleState": {
"path": "../../actions/sample-state/sample-state"
}
}
}
Both modules are now registered to the data action. Therefore, they both observe the same object in the application state. The next step is to update the application state when the sample-button module has a user click event. All modules that observe the application state should then be automatically updated. Here is the code for the sample-message module.
// sample-message.data.ts
import { AsyncResult } from '@msdyn365-commerce/retail-proxy';
import { ISampleState } from '../../actions/sample-state/sample-state';
export interface ISampleMessageData {
sampleState: AsyncResult<ISampleState>;
}
// sample-message.tsx
import * as React from 'react';
import { ISampleMessageData } from './sample-message.data';
import { ISampleMessageProps } from './sample-message.props.autogenerated';
/**
* SampleMessage Module used for showcasing cross-module communication
* @extends {React.Component<ISampleMessageProps<ISampleMessageData>>}
*/
export default class SampleMessage extends React.Component<ISampleMessageProps<ISampleMessageData>> {
constructor(props: ISampleMessageProps<ISampleMessageData>) {
super(props);
}
public render(): JSX.Element {
if(this.props.data.sampleState.result) {
return (<h3>The Button has been clicked {this.props.data.sampleState.result.clickCount} times.</h3>);
}
return (<h3>Error: No Sample State Detected</h3>);
}
}
The sample-message module is very straightforward. It asks for the ISampleState value by using a page load data action. Then, based on the data that is returned, it renders a simple message. Because the application state is internally powered by MobX, this module can automatically react when the data that it's observing changes.
Finally, here is the code for the sample-button module that updates the application state in response to on a user click event.
// sample-button.data.ts
import { AsyncResult } from '@msdyn365-commerce/retail-proxy';
import { ISampleState } from '../../actions/sample-state/sample-state';
export interface ISampleButtonData {
sampleState: AsyncResult<ISampleState>;
}
// sample-button.tsx
import * as React from 'react';
import { ISampleButtonData } from './sample-button.data';
import { ISampleButtonProps } from './sample-button.props.autogenerated';
import { SampleStateInput } from '../../actions/sample-state/sample-state';
/**
* SampleButton component used for showcasing cross-module communication
* @extends {React.Component<ISampleButtonProps<ISampleButtonData>>}
*/
export default class SampleButton extends React.Component<ISampleButtonProps<ISampleButtonData>> {
constructor(props: ISampleButtonProps<ISampleButtonData>) {
super(props);
this._onClick.bind(this);
}
public render(): JSX.Element {
return (
<button onClick={this._onClick}>
Click Me!
</button>
);
}
// OnClick Handler should update application state
private _onClick = (e: React.MouseEvent): void => {
if (this.props.data.sampleState.result) {
// This will directly update our application state, which should trigger all modules observing the state to update
this.props.context.actionContext.update(new SampleStateInput(), { clickCount: this.props.data.sampleState.result.clickCount + 1 });
}
}
}
As you can see, the onClick handler makes a call to the actionContext.update(). This method lets you directly change the application state. When the state is changed, MobX takes over and re-renders all the modules that are observing the state that includes the sample-message module.