2

I thought ES6 module exports were always immutable, so I'm pretty confused about the behaviour I'm getting. I have a simple array of colors that I would like to use in multiple components of my Vue application. It is in it's own file like so:

export const colors = [
  '#ffb3ba',
  '#ffdfba',
  '#ffffba',
  '#bae1ff',
]

Then I import it into the component where I want to use it like this:

import { colors } from '../assets/colors';

I have a function for picking a random color and then removing it from the list so it isn't picked again for the same component. It's something like this.

descriptions.forEach(item => {
      const colorIndex = Math.floor(Math.random() * colors.length);
      item['color'] = colors[colorIndex];
      colors.splice(colorIndex, 1);
    });

The idea here is to pick a random color from the list, assign it a description and then remove it from the list so a different one is picked on the next iteration of the forEach.

The problem is that it seems to be removing the colors from the list permanently. So when I import and try to use the array in another component, there are no colors in it. How can I make it so there is a fresh instance of the colors array for every component?

Matt Gween
  • 177
  • 2
  • 12
  • Make a clone so that you have your list, and then an availableList which updates on a selected or de-selected color. You're importing an array, you can modify the values inside, but you can't re-assign it – Sterling Archer Jan 29 '18 at 21:48

2 Answers2

8

The imported bindings are not assignable, that's all. They are similar to const - you cannot change the variable, but you can mutate the object it holds. To prevent that, freeze the object when exporting it:

export const colors = Object.freeze([
  '#ffb3ba',
  '#ffdfba',
  '#ffffba',
  '#bae1ff',
]);

How can I make it so there is a fresh instance of the colors array for every component?

Have a look at Copying array by value in JavaScript for that: just colors.slice(). Also you'll want to check out How to randomize (shuffle) a JavaScript array? for how to efficiently get the random colors for your descriptions - there are even some answers that do not mutate the input.

import { colors } from '../assets/colors';
import { shuffle } from '…';
const randomColors = shuffle(colors.slice());
console.assert(descriptions.length <= randomColors.length);
for (const [i, description] of descriptions.entries())
  description.color = randomColors[i];
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks, but I do not understand your solution. When I use `Object.freeze` I get the error: `TypeError: Cannot add/remove sealed array elements`. And what is wrong with my randomization method? The ones you linked are much more complicated. – Matt Gween Jan 29 '18 at 22:14
  • Yes, freezing is just a safe-guard to prevent the removes from working. You will still need to make the actual copy using `.slice()` (or `Array.from` or whatever). – Bergi Jan 29 '18 at 22:20
  • Your randomisation method is really inefficient, repeatedly removing things from the middle of the array has `O(n²)` complexity. Just use the standard Fisher-Yates shuffle (it's not *that* complicated)… – Bergi Jan 29 '18 at 22:23
  • Does it really matter when there is only 5 items though? I can't make heads or tails of the Fisher-Yates shuffle. `var currentIndex = array.length, temporaryValue, randomIndex;` I've never seen variables assigned like this. What does the comma separation mean? – Matt Gween Jan 29 '18 at 22:26
  • 1
    @MattGween It's just [declaring multiple variables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var). You might find the code from [another answer](https://stackoverflow.com/a/12646864/1048572) more readable… – Bergi Jan 29 '18 at 22:30
  • Okay. That's better. I'll try to implement it. Thanks for the help. – Matt Gween Jan 29 '18 at 22:32
4

ES6 module imports are not immutable, as you have correctly observed.


You could create a shallow copy of the array and operate on that one:

const copiedColors = [...colors];

descriptions.forEach(item => {
  const colorIndex = Math.floor(Math.random() * colors.length);
  item['color'] = copiedColors[colorIndex];
  copiedColors.splice(colorIndex, 1);
});
TimoStaudinger
  • 41,396
  • 16
  • 88
  • 94