You simply cannot use the native Map because it only provides an imperative interface.
You could reach for an open source library such as the popular ImmutableJS.
Or you could write your own persistent (immutable) data structures. The essential requirement is that the operations provided by your data structure do not modify the inputs. Instead a new data structure is returned with each operation -
const PersistentMap =
{ create: () =>
({})
, set: (t = {}, key, value) =>
({ ...t, [key]: value }) // <-- immutable operation
}
We first look at an empty map, the result of a set operation, and then make sure that empty map is not modified -
const empty =
PersistentMap.create()
console.log
( empty
, PersistentMap.set(empty, "hello", "world")
, empty
)
// {}
// { hello: "world" }
// {}
Now let's look at a new intermediate state, m1. Each time we see set returns a new persistent map and does not modify the input -
const m1 =
PersistentMap.set(empty, "hello", "earth")
console.log
( m1
, PersistentMap.set(m1, "stay", "inside")
, m1
)
// { hello: "earth" }
// { hello: "earth", stay: "inside" }
// { hello: "earth" }
Now to answer your question, we can add a push operation to our PersitentMap - we only have to ensure we do not modify the input. Here's one possible implementation -
const PersistentMap =
{ // ...
, push: (t = {}, key, value) =>
PersistentMap.set // <-- immutable operation
( t
, key
, Array.isArray(t[key])
? [ ...t[key], value ] // <-- immutable operation
: [ value ]
)
}
We see push in action below. Note that m2 nor empty are changed as a result -
const m2 =
PersistentMap.push(empty, "fruits", "apple")
console.log
( m2
, PersistentMap.push(m2, "fruits", "peach")
, m2
, empty
)
// { fruits: [ "apple" ] }
// { fruits: [ "apple", "peach" ] }
// { fruits: [ "apple" ] }
// {}
Expand the snippet below to verify the results in your own browser
const PersistentMap =
{ create: () =>
({})
, set: (t = {}, key, value) =>
({ ...t, [key]: value })
, push: (t = {}, key, value) =>
PersistentMap.set
( t
, key
, Array.isArray(t[key])
? [ ...t[key], value ]
: [ value ]
)
}
const empty =
PersistentMap.create()
console.log
( empty
, PersistentMap.set(empty, "hello", "world")
, empty
)
// {}
// { hello: "world" }
// {}
const m1 =
PersistentMap.set(empty, "hello", "earth")
console.log
( m1
, PersistentMap.set(m1, "stay", "inside")
, m1
)
// { hello: "earth" }
// { hello: "earth", stay: "inside" }
// { hello: "earth" }
const m2 =
PersistentMap.push(empty, "fruits", "apple")
console.log
( m2
, PersistentMap.push(m2, "fruits", "peach")
, m2
, empty
)
// { fruits: [ "apple" ] }
// { fruits: [ "apple", "peach" ] }
// { fruits: [ "apple" ] }
// {}