How to create a Redux store in extensions?

Creating and using a Redux store can be simple, yet annoying. Think whether you really need a redux store, maybe you could use a Context instead?

Creating redux store in extensions is not much different from anywhere else, apart of an additional step with a plugin. First, in your extension’s package folder, create a store subfolder, in which, you should create 3 files:

  1. Example.action.js - will hold the action creators (which are used for dispatching data which is passed to reducers for state updates):

    export const EXAMPLE_SHIPPING_DATA = 'EXAMPLE_SHIPPING_DATA';
    export const EXAMPLE_IS_LOADING_SHIPPING_DATA = 'EXAMPLE_IS_LOADING_SHIPPING_DATA';
    
    export const updateShippingData = (shippingData) => ({
        type: EXAMPLE_SHIPPING_DATA,
        shippingData
    });
    
    export const updateIsLoadingShippingData = (isLoading) => ({
        type: EXAMPLE_IS_LOADING_SHIPPING_DATA,
        isLoading
    });
    
  2. Example.dispatcher.js - will hold a class with dispatchers. Dispatchers are just intermediary functions between calling a state update and actually updating state in reducer. You can use them to read state, do other async or sync calls (If you don’t need to do any intermediary calls, you can avoid creating this file):

    import { 
    	updateShippingData,
    	updateIsLoadingShippingData
    } from './Example.action';
    
    export class ExampleDispatcher {
    	async fetchAndUpdateShippingData(dispatch) {
    		dispatch(updateIsLoadingShippingData(true));
    		
    		const result = await fetchQuery(ExampleQuery.getShippingDataQuery());
    		
    		dispatch(updateShippingData(result));
    		dispatch(updateIsLoadingShippingData(false));
    	}
    }
    
  3. Example.reducer.js - Your custom store, with initial state and reducer for updating it:

    import { 
    	EXAMPLE_SHIPPING_DATA,
    	EXAMPLE_IS_LOADING_SHIPPING_DATA
    } from './Example.action';
    
    export const getInitialState = () => ({
    	isLoading: false,
    	shippingData: {}
    });
    
    export const ExampleReducer = (
    	state = getInitialState(),
    	action
    ) => {
    	switch (action.type) {
        case EXAMPLE_SHIPPING_DATA:
            const { shippingData } = action;
    
            return {
                ...state,
                shippingData
            };
    
        case EXAMPLE_IS_LOADING_SHIPPING_DATA:
            const { isLoading } = action;
    
            return {
                ...state,
                isLoading
            };
    
        default:
            return state;
        }
    }
    
    export default ExampleReducer;
    

How to add your reducer into global redux store with plugins?

But creating actions, dispatchers and reducers is not enough for reducer to start working. It should also be added into global Store/Index/getStaticReducers object which holds all the reducers in the app, through the use of a plugin:

import { ExampleReducer } from '../store/Example.reducer';

const getStaticReducers = (args, callback) => ({
    ...callback(...args),
    ExampleReducer
});

export default {
    'Store/Index/getStaticReducers': {
        function: getStaticReducers
    }
};

What are the real examples of this?

/**
 * PayPal payments compatibility for ScandiPWA
 * @copyright Scandiweb, Inc. All rights reserved.
 */

import { PaymentMethodsReducer } from '../store/PaymentMethods/PaymentMethods.reducer';
import { PayPalPaymentReducer } from '../store/PayPalPayment/PayPalPayment.reducer';

const getStaticReducers = (args, callback) => ({
    ...callback(...args),
    PayPalPaymentReducer,
    PaymentMethodsReducer
});

export const config = {
    'Store/Index/getStaticReducers': {
        function: getStaticReducers
    }
};

export default config;

In this example with PayPal, PayPalPaymentReducer, and PaymentMethodsReducer are added to the global store.