Update Lists in State
Python lists are mutable, but you should treat them as immutable when you store them in state. Just like with dictionaries, when you want to update a list stored in state, you need to create a new one (or make a copy of an existing one) and then set state to use the new list.
Update lists without mutation
Like with dictionaries, you should treat lists in deephaven.ui state as read-only. This means that you should not reassign items inside a list like my_list[0] = "bird", and you also should not use methods that mutate the list, such as append() and remove().
Instead, every time you want to update a list, you will want to pass a new list to your state setting function. To do that, you can create a new list from the original list in your state by copying the list or creating a new list via list comprehension. Then, you can set your state to the resulting new list.
Add to a list
append() and insert() will mutate a list, which you do not want.
Instead, create a new list that contains the existing items and a new item at the end. There are multiple ways to do this: you can use the copy() method, concatenation, or unpacking. Now it works correctly:
The concatenation syntax also lets you prepend by placing it before the original list:
Remove from a list
remove(), pop(), del, and clear() will mutate a list. Instead, you can slice or use a list comprehension to filter out unwanted items.
Here, [artist for artist in artists if artist["id"] != id] means create a list that consists of those artists whose IDs are different from artist["id"]. In other words, each artist’s “Delete” button will filter that artist out of the list, and then request a re-render with the resulting list. Note that list comprehension does not modify the original list.
Transform a list
If you want to change some or all items of the list, you can use list comprehension to create a new list.
In this example, a list holds three values. Two are “mutable” and one is “immutable”. Clicking on the button will increment only the “mutable” values. It does this by producing a new list using list comprehension.
Replace items in a list
It is particularly common to want to replace one or more items in a list. Assignments like values[0] = "bird" are mutating the original list, so instead, you will want to use list comprehension for this as well.
Insert into a list
You may want to insert an item at a particular position that is neither at the beginning nor at the end. To do this, you can the slice syntax. The slice syntax lets you cut a “slice” of the list. To insert an item, you will create a slice before the insertion point, then the new item, and then a slice that is the rest of the original list.
In this example, the Insert button always inserts at the index 1:
Make other changes to a list
There are some things you cannot do with non-mutating methods. For example, you may want to reverse or sort an array. The Python list reverse() and sort() methods mutate the original list, so you cannot use them directly.
However, you can copy the list with unpacking first, and then make changes to it.
For example:
Here, you use the copy() method to create a copy of the original list first. Now that you have a copy, you can use mutating methods like reverse() or sort(), or even assign individual items.
However, even if you copy the list, you cannot mutate existing items inside of it directly. This is because copying is shallow and the new list will contain the same items as the original one. So, if you modify a dictionary inside the copied list, you are mutating the existing state. For example, code like this is a problem.
Although artists_copy and artists are two different lists, artists_copy[0] and artists[0] point to the same dictionary. So by changing artists_copy[0]["name"], you are also changing artists[0]["name"]. This is a state mutation, which you should avoid. You can solve this issue in a similar way to updating nested Python dictionaries by copying individual items you want to change instead of mutating them. Here’s how.
Update dictionaries inside arrays
When updating nested state, you need to create copies from the point where you want to update all the way up to the top level. Let’s see how this works.
In this example, two separate artwork lists have the same initial state. They are supposed to be isolated, but because of a mutation, their state is accidentally shared, and checking a box in one list affects the other list:
The problem is in code like this:
Although the my_list_copy list is new, the items themselves are the same as in the original my_list. Therefore, changing artwork["seen"] also changes the original artwork item. Since that artwork item is also in your_list, this causes the bug. Such bugs can be tricky to debug, but they can be avoided by not mutating state.
Use deepcopy to substitute an old item with its updated version without mutation.
In general, you should only mutate items that you have just created. If you were inserting a new artwork, you could mutate it, but if you are dealing with something that is already in state, you need to make a copy.