Using ES6 Map with React and/or Redux
Asked Answered
R

6

17

I am looking at using the ES6 Map object to do my hashing for me, instead of creating a custom hashing function. However, it seems to me that it doesn't have much support for immutability, which is a key thing for React as well as Redux.

I am used to not using any libraries for immutability, but use just plain destructuring:

const newObj = { ...prevObj, newKey: "value" }

or map/filter with arrays.

However, ES6 Map has methods that directly update the object itself.

What I could think of was to do something like this:

var myMap = new Map()
Var myNewMap = { ...myMap.set() }
this.setState({ myMap:myNewMap })

But I am not sure if that would work.

Rip answered 15/12, 2018 at 20:6 Comment(9)
I'd avoid using Map in the redux store. github.com/reduxjs/redux/issues/1499Barthelemy
okay, but how will I then go about getting the same benefits of 0(1) lookup?Rip
interested to know what you are doing where the lookup speed of map over object makes a difference worth worrying about. Are you looking up hundreds of thousands of values at a time?Barthelemy
Well, the answer to this is kinda two-fold. I am in a position where I have to program a web-application which will be used by users which are siting on RDP connections to a VM, which immensely slows down the client's processing power, and javascript runs slower than ever. So if I can shave off any time in doing lookups for redux, I am gaining alot. Second, I might or might now have a cached data of some 80-120.000 rows of customer records that needs to be searched for, so improving the search time instead of having to do a database look up, I thought might help.Rip
I guess the only question then is, have you actually experienced issues using plain objects? If not then maybe this is a bit of premature optimisation. If so, then you maybe want to look at some alternatives to redux, even if only for the problematic data. It seems like the complexity of making Maps work in Redux would not be worth the effort and may even counteract the benefits of Maps, eg creating new Maps is much slower than creating objects. Definitely worth getting some real measurements before committing to a whole lot of work.Barthelemy
Basically I agree with you. I did have some optimization issues with the current redux set-up I had. I then disconnected completely from Redux and went back to local states, that obviously worked, but didn't solve the basic issue I had with redux. I am starting a new project, where I am estimating I will have similar problem, however it's a bit of a bigger project, so using Redux will have some benefits, so I wanted to do the proper research ahead of time. I am also looking into reselect, which should handle a part of it.Rip
cool, optimising your selectors sounds like the way to go. There's also re-reselect which adds a bit more flexibility. *I haven't used it yet though. This is a good write-up I just found.. medium.com/riipen-engineering/…Barthelemy
Another explanation. Might benefit someone. https://mcmap.net/q/737779/-can-a-redux-toolkit-createslice-use-a-js-map-as-stateSoundboard
@VincentRye What about using an object, with an indexesKey key that stores all the other keys in an array, any new addition is just a push to the indexesKey (granted, that needs an array recreation) but you can use it to iterate over the object keys in a safe ordered way, you still keep that O(1) lookup and (except for the indexesKey update) you also have a O(1) set for the upsert. I tested that today because I had the exact same issue (some algorithms I use need to have the O(1) lookup and to be able to be searched in an ordered manner)Bushido
R
6

If you just want a type-safe key-value store, you can use the following syntax:

entities: { [id: number]: MyEntity },
...
Rubberize answered 13/8, 2021 at 13:10 Comment(0)
B
5

I thought about using Map data structure with react +redux recently and decided against it.

Though there is a slight performance benefit, for some use cases, in using the Map, mostly because the compiler knows better what to expect. And you get a size prop for free :)

I think there are more reasons agains this approach:

First of all redux doesn't like non serializable object - faq. it won't work if you want to use SSR or save your data in localStorage.

You can't enjoy many features in the redux toolkit, because they assume / create simple object data structures. for example the the createEntityAdapter - which I personally think is great

You can create a new map in each reducer, but it might get annoying pretty fast, and I'm not sure about the performance.

React doesn't care that much though :)

Breve answered 1/5, 2022 at 21:27 Comment(0)
S
0

All objects in JavaScript are reference types by nature (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#comparing_objects). The only reason that the state object in Redux is immutable is because the library itself doesn't let you directly modify the state object return by getState.

Additionally, a JavaScript object is itself a dictionary, so fetching data from that state object by property name is still O(1) time complexity

Spieler answered 12/8, 2021 at 19:28 Comment(0)
O
0

If you want to use a map-like structure in Redux that is serializable, O(1), and syntactically efficient - using an enum as the key, this is what I did:

export type MapLikeType = {
  [K in EnumType]?: valueType;
};
Osteophyte answered 1/4 at 16:53 Comment(0)
A
-1

I am using an ES6 in React project that uses Redux as well.

use-case #1

Within a component:

I receive the data, then I need to process it, e.g. add some flags and/or additional parameters to each of the elements, then I store it within the component as ES6 Map(not in the component state).

Later when I need to update the data (e.g. new elements were fetched) I'm using Thunk that sets a loading flag in redux, which, later is back to false which triggers the update.

I update my ES6 Map with new data, mutating it, but not creating a new Map. My component gets updated because of the loading flag and re-renders with the new data from internal Map

use-case #2

In Redux state

I have an ES6 Map in my Redux state because I want to share it with multiple components. I keep it immutable like so in a reducer:

const newMap = new Map(oldMap.entries())

but creating new Map on each update is only useful if you don't update it too often, but do often read from it.

In my case I have to use Map in Redux state because ES6 Map is keeping the order of elements while providing O(1) access to them.

So I'd suggest you to figure out parts where it's beneficial to use ES6 Map and where it's fine to just go along with regular Object.

Andyane answered 14/1, 2021 at 18:56 Comment(1)
"In my case I have to use Map in Redux state because ES6 Map is keeping the order of elements while providing O(1) access to them." The common way to solve this is to keep a simple array with keys to maintain the order, and an object to map keys to values with constant lookup time. Yes it's slightly redundant, since the keys are repeated in the array and in the object, but it fits much nicer into the idea of the redux store being based on primitives only and being serializable. You can write a selector that allows you to iterate over key/value pairs in order for convenience.Carleton
L
-3

It's actually very easy to use ES6's Map within the state. You need to use ReactDOM and ReactDOM.render(document.getElementById('someid'), map.get("123"));. But for this to have any advantageous benefits in terms of optimization, it's a bit more complicated. And it might only be useful for certain applications.

To avoid speaking strictly in the abstract, let's take a sample application: the DOM is an HTML <table> and the ReactJS state is a Map of coordinates and values. The <td> elements would have coordinate IDs, like id="pos-1,2" would be the position of the 1st row, the second column. Now the state could be looking like...

this.state = {
    'coordinates': Map({'1,2':'my 1/2 text!', '4,5':'my 4/5 text!'}),
};

To update state, you could have updateState()...

updateState(newmap) {
    const coordinates = this.state.coordinates;
    newmap.keys().forEach((key) => {
        const parent = document.getElementById('pos' + key);
        const text = newmap.$key;
        ReactDOM.unmountComponentAtNode(parent);
        ReactDOM.render(parent, (<span>{text}</span>);
        coordinates.set(key, text);
    });
    this.state.setState({'coordinates':coordinates});
}

So, what is happening in here? We could update the state, whenever we want, with any position on the table grid. For instance, if we had 500 grids already with text in them, I could add just one more grid text field, with this.updateState(Map({'1000,500':'my text at position 1000x500!'}));. In this way, the Map() can be used within the state and it retains its benefit of an O(1) lookup for a single change, because our updateState() handles it.

This also bypasses much of the use of render(), but that is a design decision. I used the example of a 1,000x1,000 table grid, because having 1,000^2 unique, stateful elements has a tremendous load/handling time when using render(), and in that case, Map() would work just fine.

Larissa answered 12/9, 2020 at 20:33 Comment(2)
I think there's been a misunderstanding. The question is about the Map object, but this answer is about the Array.prototype.map() function. Similar name, different concept.Degradable
Hey, Kostas: Thanks for the heads up, I added some caps for map to be Map(), should be fixed now.Larissa

© 2022 - 2024 — McMap. All rights reserved.