2

In a state-managing javascript framework (eg: React), if you have a collection of objects to store in state, which is the more useful and/or performant dataset type to hold them all, an object or an array? Here are a few of the differences I can think of that might come up in using them in state:

Referencing entries: With objects you can reference an entry directly by its key, whereas with an array you would have to use a function like dataset.find(). The performance difference might be negligible when doing a single lookup on a small dataset, but I imagine it gets larger if the find function has to pore over a large set, or if you need to reference many entries at once.

Updating dataset: With objects you can add new entries with {...dataset, [newId]: newEntry}, edit old entries with {...dataset, [id]: alteredEntry} and even edit multiple entries in one swoop with {...dataset, [id1]: alteredEntry1, [id2]: alteredEntry2}. Whereas with arrays, adding is easy [...dataset, newEntry1, newEntry2], but to edit you have to use find(), and then probably write a few lines of code cloning the dataset and/or the entry for immutability's sake. And then for editing multiple entries it's going to either require a loop of find() functions (which sounds bad for large lists) or use filter() and then have to deal with adding them back into the dataset afterwards.

Deleting To delete a single entry from the object dataset you would do delete dataset[id] and for multiple entries you would either use a loop, or a lodash function like _.omit(). To remove entries from an array (and keep it dense) you'd have to either use findIndex() and then .slice(index, 1), or just use filter() which would work nicely for single or multiple deletes. I'm not sure about the performance implications of any of these options.

Looping/Rendering: For an array you can use dataset.map() or even easily render a specialized set on the fly with dataset.filter() or dataset.sort(). For the object to render in React you would have to use Object.values(dataset) before running one of the other iteration functions on it, which I suppose might create a performance hit depending on dataset size.

Are there any points I'm missing here? Does the usability of either one depend perhaps on how large the dataset is, or possibly how frequent the need to use "look up" operations are? Just trying to pin down what circumstances might dictate the superiority of one or the other.

1 Answer 1

4

There's no one real answer, the only valid answer is It dependsTM.

Though there are different use-cases that requires different solutions. It all boils down to how the data is going to be used.

A single array of objects

Best used when the order matters and when it's likely rendered as a whole list, where each item is passed from the list looping directly and where items are rarely accessed individually.

This is the quickest (least developer-time consuming) way of storing received data, if the data is already using this structure to begin with, which is often the case.

Pros of array state

  • Items order can be tracked easily,
  • Easy looping, where the individual items are passed down from the list.
  • It's often the original structure returned from API endpoints,

Cons of an array state

  • Updating an item would trigger a render of the full list.
  • Needs a little more code to find/edit individual items.

A single object by id

Best used when the order doesn't matter, and it's mostly used to render individual items, like on an edit item page. It's a step in the direction of a normalized state, explained in the next section.

Pros of an object state

  • Quick and easy to access/update by id

Cons of an object state

  • Can't re-order items easily
  • Looping requires an extra step (e.g. Object.keys().map)
  • Updating an item would trigger a render of the full list,
  • Likely needs to be parsed into the target state object structure

Normalized state

Implemented using both an object of all items by id, and an array of all the id strings.

{
  items: {
    byId: { /**/ },
    allIds: ['abc123', 'zxy456', /* etc. */],
  }
}

This becomes necessary when:

  • all use-cases are equally likely,
  • performance is a concern (e.g. huge list),
  • The data is nested a lot and/or duplicated at different levels,
  • re-rendering the list as undesirable side-effects

An example of an undesirable side-effect: updating an item, which triggers a full list re-render, could lose a modal open/close state.

Pros

  • Items order can be tracked,
  • Referencing individual items is quick,
  • Updating an item:
    • Requires minimal code
    • Doesn't trigger a full list render since the full list loops over allIds strings,
  • Changing the order is quick and clear, with minimal rendering,
  • Adding an item is simple but requires adding it in both dataset
  • Avoids duplicated objects in nested data structures

Cons

  • Individual removal is the worse case scenario, while not a huge deal either.
  • A little more code needed to manage the state overall.
  • Might be confusing to keep both state dataset in sync.

This approach is a common normalization process used in a lot of places, here's additional references:

Sign up to request clarification or add additional context in comments.

4 Comments

Very interesting 3rd option. Thank you for that. Question: with the synced array + object setup, would just the one list item re-render if you changed an entry, or would nothing re-render and you'd have to trigger a render manually somehow so see the change?
@Brimby the array only keeps track of the id strings, so it doesn't change when an item is updated. The component that renders the item will get it from the itemsById object, and only the one being updated will see that it's a different object, automatically triggering a render of this single component tree.
Great. I really like that technique so thank you for showing me. Did you come up with that pattern on your own, or did you learn it from somewhere? I'd like to find some good React sites or blogs that feature articles that are performance related or showcase advanced coding patterns like this.
@Brimby I didn't come up with it, it's a best practice documented on the Redux website. I've added relevant links and explanation in my answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.