SortedMap & SortedRenderMap

SortedMap and SortedRenderMap are key-based data structures. They are designed to store and sort the data inside of them by the position that is defined by a developer or generated automatically.

Motivation

We wanted a data structure that will be flexible and works well with our Extensibility mechanism. More specifically we wanted it to:

  • Sort content by the position that is assigned to each element manually or automatically,

  • Give the ability to retrieve any element from the list by some unique key,

  • Be easily extensible with our plugin mechanism,

  • Be convenient and easy to use.

Usage

Both SortedMap and SortedRenderMap behave similarly, but the SortedRenderMap is a preferred way of storing React Components.

A new instance is declared by using createSortedMap() or createSortedRenderMap() functions from the @scandipwa/framework package:

import {
    createSortedMap,
    createSortedRenderMap
} from '@scandipwa/framework/src/util/SortedMap';

export class MyComponent extends Component {
    sortedMap = createSortedMap({
        firstItem: 1,
        thirdItem: 3
    });
    
    sortedRenderMap = createSortedRenderMap({
        firstComponent: this.renderFirstComponent.bind(this),
        thirdComponent: this.renderThirdComponent.bind(this)
    });
}

Make sure that values in the createSortedMap declaration return functions.

Both functions accept a single argument that is an object containing the data. As you might notice, we don't pass the position argument in any of the items and this is because the position is assigned automatically for items in the initial declaration. Now let's fill in the gap between one and three using the Extensibility mechanism.

Adding an item

const addSecondItem = (member) => {
    const SECOND_ITEM_POSITION = 1500;
    member.addItem(2, 'secondItem', SECOND_ITEM_POSITION);
    return member;
};

export default {
    'MyModule/Component/MyComponent/sortedMap': {
        'member-property': addSecondItem
    }
};

In this code snippet, we plug into the sortedMap property to add a new item to the sorted map. The addition of new items is done via the addItem() method that is present on all instances of SortedMap and SortedRenderMap. This method accepts 3 arguments:

  • item, which is the item that you want to add to the list.

  • key, which is the unique identifier for the newly added item.

  • position, which is optional and specifies the position that the item should be inserted at.

The SECOND_ITEM_POSITION variable was used to define the position of the new item. Its value is 1500 because of the positions that were assigned to the initial declaration: the first item is always assigned a position of 1000 and it increments by another 1000 for each item. So, in our example that would be:

sortedMap = createSortedMap({
    firstItem: 1, // Position is 1000
    thirdItem: 3 // Position is 2000
});

In order to add an item in-between two items, a position value has to be between the position values of the surrounding items as well. I chose 1500 just for convenience, but it can be any number between 1001 and 1999 in this case.

If the position argument is missing, the item will be assigned to the end of the map.

The process of adding a new item is very straightforward. Just make sure that:

  • The key is unique to that declaration of SortedMap/SortedRenderMap

  • The member argument is returned at the end

member.addItem(2, 'secondItem', SECOND_ITEM_POSITION);
return member;

Removing an item

In order to remove an item from the SortedMap, you will need the key of that item and the removal process looks like this:

const removeFirstItem = (member) => {
    const ITEM_KEY = 'firstItem'; 
    member.removeItemBykey(ITEM_KEY);
    return member;
};

export default {
    'MyModule/Component/MyComponent/sortedMap': {
        'member-property': removeFirstItem
    }
};

Positions of items in the SortedMap are not affected by this action and remain the same.

Retrieving an item

In order to get an item from the SortedMap, you will need the key of that item and the process looks like this:

import { createSortedMap } from '@scandipwa/framework/src/util/SortedMap';

export class MyComponent extends Component {
    sortedMap = createSortedMap({
        firstItem: 1,
        thirdItem: 3
    });
    
    getFirstItem() {
        return this.sortedMap.getItemByKey('firstItem');
    }
}

Retrieving sorted map

At this point, the only thing that's left is to sort the data inside the map and retrieve it! With SortedRenderMap we can even get the array of React Components that are ready to be rendered:

export class MyComponent extends Component {
    sortedMap = createSortedMap({
        firstItem: 1,
        thirdItem: 3
    });
    
    sortedRenderMap = createSortedRenderMap({
        firstComponent: this.renderFirstComponent.bind(this),
        thirdComponent: this.renderThirdComponent.bind(this)
    });
    
    getSortedMapValues() {
        return this.sortedMap.getSortedMap();
    }
    
    renderContent() {
        return this.sortedRenderMap.render();
    }
}

Extension

Why would you extend?

You would want to extend SortedMap and SortedRenderMap for various reasons:

  1. Rendering a new HTML block inside a React Component. For example, to add product recommendations on the product page

  2. Adding a new React Context to the root component. For example, to make shop data available to every component in the app

  3. Defining a new route for the app. For example, to add the login page under the route /login.

Examples

Learn how to extend SortedMap and SortedRenderMap with these examples taken directly from the code.

Add a new item to SortedRenderMap

import { createElement } from 'react';

import ProductCardPrice from '../component/ProductCardPrice';

const addPriceRender = (member) => {
    const PRICE_POSITION = 2500;
    member.addItem(
        () => createElement(ProductCardPrice),
        'productCardPrice',
        PRICE_POSITION
    );

    return member;
};

export default {
    'ShopifyProducts/Component/ProductCard/Component/ProductCardComponent': {
        'member-property': {
            sortedRenderMap: addPriceRender
        }
    }
};

Add a new route to SortedRenderMap

import { createElement, lazy, Suspense } from 'react';
import { Route } from 'react-router';

import ProductFallbackPage from '../component/ProductFallbackPage';

const ProductPage = lazy(() => import('../component/ProductPage'));

const addProductPage = (member) => {
    const PRODUCT_PAGE_POSITION = 5000;

    member.addItem(
        () => createElement(Route, {
            path: [
                '/products/:handle',
                '/collections/:collectionHandle/products/:handle'
            ],
            exact: true,
            render: (props) => (
                <Suspense fallback={ <ProductFallbackPage /> }>
                    <ProductPage { ...props } />
                </Suspense>
            )
        }),
        'routerProductPage',
        PRODUCT_PAGE_POSITION
    );

    return member;
};

export default {
    'Router/Component/Router/Component/RouterComponent': {
        'member-property': {
            _switchRenderMap: addProductPage
        }
    }
};

Add a Higher-Order Component (HOC) to SortedMap

import CustomerProvider from '../context/Customer.provider';

const addCustomerProvider = (member) => {
    const CUSTOMER_POSITION = 2000;

    member.addItem(
        (children) => (
            <CustomerProvider>
                { children }
            </CustomerProvider>
        ),
        'appCustomerProvider',
        CUSTOMER_POSITION
    );

    return member;
};

export default {
    'Framework/Component/App/Component/AppComponent': {
        'member-property': {
            contextProvidersRenderMap: addCustomerProvider
        }
    }
};

Last updated