B
B
BonBon Slick2021-12-03 02:05:12
redux
BonBon Slick, 2021-12-03 02:05:12

State machine isolation of mutations or how to properly manage the state of complex modules with deep objects?

We have state = storage
getter = only return values
​​action = execute and contain all the logic
mutation = only mutates the

state

state

spoiler
export interface IAbstractFormWithLocalizedFields extends IAbstractForm,
                                                          IFormWithLocalizedFieldsGetters,
                                                          IFormWithLocalizedFieldsActions {
    _fields?: {
        _localizedFields?: Array<ILocalizedFields>,
    }
    _violations?: {
        _localizedFields?: Array<ILocalizedFields>,
    }
}

let locales = [
        {
            isDefault:  true,
            isSelected: true,
            locale:     {
                language: {
                    code:       {
                        alpha2: 'en',
                        alpha3: 'eng',
                    },
                    nativeName: 'English',
                    commonName: 'English',
                },
                country:  {
                    code:       {
                        alpha2: 'ua',
                        alpha3: 'ukr',
                    },
                    nativeName: 'Украина',
                    commonName: 'Ukraine',
                },
            },
            fields:     [
                {
                    fieldName: 'title',
                    values:    [
                        {localizedValue: 'Some titles 1', isDefault: true},
                        {localizedValue: 'Some titles 2', isDefault: false},
                        {localizedValue: 'Some titles 3', isDefault: false},
                    ],
                },
                {
                    fieldName: 'description',
                    values:    [
                        {localizedValue: 'Some description 1', isDefault: true},
                    ],
                },
            ],
        },
        {
            isDefault:  false,
            isSelected: false,
            locale:     {
                language: {
                    code:       {
                        alpha2: 'de',
                        alpha3: 'deu',
                    },
                    nativeName: 'Deutch',
                    commonName: 'Deutch',
                },
                country:  {
                    code:       {
                        alpha2: 'ge',
                        alpha3: 'ger',
                    },
                    nativeName: 'Germany',
                    commonName: 'Germany',
                },
            },
            fields:     [
                {
                    fieldName: 'title',
                    values:    [
                        {localizedValue: 'Some titles 11', isDefault: true},
                        {localizedValue: 'Some titles 22', isDefault: false},
                        {localizedValue: 'Some titles 33', isDefault: false},
                    ],
                },
                {
                    fieldName: 'description',
                    values:    [
                        {localizedValue: 'Some description 11', isDefault: true},
                    ],
                },
            ],
        },
    ]
;

export interface ILocalizedFields {
    locale: ILocale,
    isDefault: boolean,
    isSelected: boolean,
    fields: Array<ILocalizedField>,
}

export interface ILocalizedField {
    fieldName: string,
    values: Array<ILocalizedValue>,
}

export interface ILocalizedValue {
    localizedValue: string,
    isDefault: boolean,
}

const local = {
    /** @override **/
    _fields:     {
        _localizedFields: locales,
    },
};

export default (): IStrAny => merge({}, common(), local) as IStrAny;


mutation
spoiler
[SET_ACTIVE_LOCALE_ISO](state: any, activateLocaleWithISO: ISOAlpha2Type): void {
        let fields: Array<ILocalizedFields> = state._fields._localizedFields;

        // can  moved to getter function
        const disableByIndex: number = findIndex(
            state._fields._localizedFields,
            (field: ILocalizedFields): boolean => field.isSelected,
        );
        if (-1 < disableByIndex) {
            let disabledItem: ILocalizedFields = fields[disableByIndex];
            disabledItem.isSelected            = false;
            fields.splice(disableByIndex, 1, disabledItem);
        }

        // can  moved to getter function
        const enableByIndex: number = findIndex(
            state._fields._localizedFields,
            (field: ILocalizedFields): boolean =>
                formatLocaleToISOCode(field.locale).toLowerCase() === activateLocaleWithISO,
        );
        if (-1 < enableByIndex) {
            let enabledItem: ILocalizedFields = fields[enableByIndex];
            enabledItem.isSelected            = true;
            fields.splice(enableByIndex, 1, enabledItem);
        }
    },


action
spoiler
setActiveLocaleISO({state, commit, getters}, isoCode: ISOAlpha2Type): void {
        if (isoCode !== getters.activeLocalizedFieldsLocaleISO2) {
            commit(SET_ACTIVE_LOCALE_ISO, isoCode.toLowerCase());
        }
    },


getter
spoiler
localeLocalizedFieldsIndexByLocaleISO2: (state: any, getters: any) => (localeISO_2: ISOAlpha2Type): ILocalizedFields | undefined => {
        return  findIndex(
            getters.localizedFields  as Array<ILocalizedFields>,
            (field: ILocalizedFields): boolean => localeISO_2 ===  formatLocaleToISOCode(field.locale).toLowerCase(),
        );
    },
    locali


From the example above, it can be seen that the search by index can be moved to getters, called in actions and passed already found indexes on which to make modifications in the mutation. But in the example, lookup and mutation happen in mutation.
That is, the search and mutation code is duplicated.

Now the example above is working, the approach that I described will also work and reduce the amount of code. the search logic for what to mutate, modify goes into the getter and is passed to the mutation from the action.
And there is also an approach in which we only pass the path along which to modify, conditionally
licalizedFieldsSelectedPath (state : any,  paramsForDeepObjSearch: {localeIso2 : string,  fieldName: string} ) : string {
   return '_fields._localizedFields.2.isSelected' // get path we want to update in deep object
}

activateLocalizedFieldsTab (getters: any, dispatch: any) : void {
  dispatch('mutationName', {mutationPath: getters.licalizedFieldsSelectedPath, value: true}); // pass path and value
}

setActiveLocalizedFields (state : any, update: {mutationPath: string, value: any}) : void {
 state[update.mutationPath] = update.value;  // state mutated
}


I have already described 3 approaches and while I'm rushing between them I can't choose and use what is in the example. Each has its pros and cons. For example
1 - option is the current one, getter logic is duplicated in mutations
2 - option where getters pass indexes to mutate. Conditionally in this approach, we can create a new object in the action and stupidly assign a new state in the mutation, which makes it a one-line
3 - approach where we pass the path to mutate, thanks to this you can see in the debugger that the

pros / cons is mutated
1 - more code, but it comes out more isolated and less subject to external changes, the only dependency is the state structure itself, which must be identical in both getters and mutations. As soon as somewhere there is an attempt to access or assign a field that does not exist, bugs appear
2 - we take out the entire logic of obtaining data from the state into getters, less code, more reusability, but harder to debug. In this case, most likely, a new object of the entire state is already being transferred and sits in mutation
3 - it’s very flexible here, we specify the path and pass the value, but here is the writing of path getters, I foresee then there will be a lot of getters, because they need to be done at each level according to the method or how then so, take into account the depth. For example
return stateDeepMapStructure // {level_1_path_1 : '_fields', level_2_path_1: '_localizedFields'}


And so on, while thinking about which approaches to use when and how.
If we translate it into another plane, then in RDBMS it is column type text inside with json and we need to modify the value of one field inside json N depth in the SQL query.
NoSql seems to be more suitable for such structures, but I have not yet picked how the same mongo updates the deep fields of documents.

UPD. forgot about flat structures
spoiler
{
{
  localeCountryISO2Code : 'ru',
  localeLanguageISO2Code : 'ru',
  fieldName: 'title',
  fieldValue: 'Крутой тайтл для крутого поста',
  isSelected:  true,
  isDefault: true,
},
{
  localeCountryISO2Code : 'en',
  localeLanguageISO2Code : 'en',
  fieldName: 'title',
  fieldValue: 'This is cool title for this cool post',
  isSelected:  false,
  isDefault: false,
}
}


Your ideas?
How would you manage such states?
Why?

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question