TypeScript and Redux Immutability Functions<!-- --> | <!-- -->Patrick Desjardins Blog
Patrick Desjardins Blog
Patrick Desjardins picture from a conference

TypeScript and Redux Immutability Functions

Posted on: June 5, 2018

The actual system I am working in my daily job is using TypeScript, React and Redux. I am not relying on any framework to do the immutability. I am using pure JavaScript cloning mechanism and have unit tests for each of my reducer that makes sure that the instance returned is not the same. It works great, it's easy to understand, and doesn't require any dependencies. However, few utility functions ease the whole process mostly when using dictionary which is present everywhere since I an normalizing my data in the Redux store to avoid having any duplicated values.

First, I am using an alias for the index signature just to avoid repeating the square brackets syntax everywhere. This doesn't provide much but is worth mentioning because all the future function I'll share use this interface.

1export interface GenericMap<T> { [id: string]: T; }

The first useful function is to add into an array an object without mutating this one. This function relies on the array's function slice to return a copy of the array.

1export function addInArrayIfNotPresent<T>(array: T[], item: T): T[] {
2 let returnArray: T[] = [];
3 if (array !== undefined) {
4 returnArray = array.slice();
5 if (array.indexOf(item) === -1) {
6 returnArray.push(item);
7 }
8 }
9 return returnArray;
10}
11
12// Usage:
13const newReferenceArrayWithItemsAdded = addInArrayIfNotPresent(existingArray, itemsToAdd);

The second function is to add a new element in a map without mutating the existing dictionary. This is useful because it handles the cloning and swap the value into the cloned dictionary.

1export function alterImmutablyMap<T>(stateMap: GenericMap<T>, key: number | undefined, modelMember: T): GenericMap<T> {
2 if (key !== undefined) {
3 const cloneStateMap = Object.assign({}, stateMap);
4 cloneStateMap[key] = modelMember;
5 return cloneStateMap;
6 }
7 return stateMap;
8 }
9
10// Usage:
11const newDictionary = alterImmutablyMap(existingDictionary, key, value);

The third function allows changing a property of an existing object in a dictionary. The function is useful if the user change a single property and you do not want to change to extract the current object and manually clone it to set the new value into a new clone of the object.

1export function alterImmutablyMapMember<T, K extends keyof T>(stateMap: GenericMap<T>, key: number | undefined, modelMember: K, value: T[K]): GenericMap<T> {
2 if (key !== undefined) {
3 if (stateMap[key] !== undefined) {
4 const cloneStateMap = Object.assign({}, stateMap);
5 const modelFromState = Object.assign({}, cloneStateMap[key]) as T;
6 if (modelFromState !== undefined) {
7 modelFromState[modelMember] = value;
8 cloneStateMap[key] = modelFromState;
9 }
10 return cloneStateMap;
11 }
12 }
13 return stateMap;
14}
15// Usage:
16const newDictionary = alterImmutablyMapMember(existingDictionary, key, member, value);
17const newDictionary2 = alterImmutablyMapMember(existingDictionary, 1, "name", "Patrick"); // Change the item 1's property name to Patrick