Arrays vs Lists for ReasonReact

reasonreact

#1

I know the answer to array vs list is generally “use lists for almost everything”, but for ReasonReact, I find lists a tad inconvenient: adding elements is fine, but on every render you have to do reverse and toArray.

What do you guys use?

P.S. It’s just occurred to me you can add new items to the end of the list, especially when additions happen much rarer than rendering. And of course, renders are still diffed and all. And of course, lists can be pattern matched and all. And of course, sometimes you don’t need a list at all, but rather something like Elm’s SelectList. But still: should I be worried about mapping from lists to arrays


#2

See https://twitter.com/rickyvetter/status/1119696093044981761?s=12 , as pr Ricky Vetter (designer of ReasonReact 0.7.0 API):

Working with arrays instead of lists is a best practice with ReasonReact because it is the structure that React expects. If you create with [|a, b|] syntax you only need the one function.


#4

Well, it’s not that everyone agreed in that thread. :slight_smile: But ReasonReact 0.7 API is pretty rad, I must admit.

Maybe ImmutableArray is not such a bad idea? Or anything that is opaque and exposes toArray. After all, rendering convenience is more or less solved by toArray, but business logic convenience and correctly representing domain is at least of equal importance.

Sorry for arguing with myself here. :blush:


#5

When in Rome, do as the Romans do :slight_smile: sure, you could set up an immutable array type but imho it doesn’t really buy you all that much because the underlying array is mutable anyway and could potentially be modified. If you want to avoid that, the only real solution is to copy over the array from mutable to immutable for rendering, but then you’re doing extra copying, and it’s not really idiomatic anyway to modify arrays in React components, so…

Think of it like this, a React component is a specialized view rendering tool that requires arrays. If you have business and domain logic, that should not be inside a React component render function. It should be outside, using lists and records and other functional data structures. When you pass data into a React component though, you need to convert it into React’s ‘native’ data types, which is arrays and objects.


#6

Yeah, I think you’re right: domain data shouldn’t be designed around the rendering layer, at least if you buy into something DDD-friendly, like Onion Architecture :slight_smile:


#7

I agree with the consensus that using arrays instead of lists is preferable. In the few situations where I’m using lists anyway and happen to need to render it to a React component, I use this function:

let listToReactArray = (list, func) => {
  list
  ->Belt.List.reduce([||], (acc, item) =>
      acc |> Js.Array.concat([|func(item)|])
    )
  ->React.array;
};

// in our component:
some_list->listToReactArray(data => <p>{React.string(data)}</p>)

So it’s just as simple as using Array.map.


#9

Doesn’t your reduce effectively does the same as Belt.List.toArray? Or is it more like toArrayReverse?


#10

Effectively, yes. The main advantage (imo) is that you don’t have to call Array.map afterwards with a function that renders JSX components. It basically combines toArray and map into one loop.


#11

Yes, that is convenient. One thing that makes me a bit apprehensive is that it’s based on the assumption that you mostly just add elements to your lists (my case too, but only so far), and since a singly-linked list is basically FILO, you have to always reverse it when rendering, because semantically, it should actually be FIFO.

That assumption and that convention may be quite OK for a lot of use cases, but if listToReactArray were a library function, its name would probably have to say something about that reversal, because types obviously say nothing about the elements’ order.


#12

Oops, I misread earlier. The code I posted doesn’t reverse the list. However, you can easily make it reverse by switching the order of arguments in Js.Array.concat.

If a library included it, it would be simple to include the vanilla and the reverse versions.

Considering the cost (however small) of converting a list to an array, reversing the array, and mapping the output, this gives you three functions for the price of one!


#13

If you have a larger list, it’s more efficient to push elements into the result array than it is to repeatedly concat arrays. Might as well take care of that scenario as well, since it’s simple. I would recommend doing something like this:

let listToReactArray(list, func) = {
  let result = [||];

  List.iter(
    item => result |> Js.Array.push(func(item)) |> ignore,
    list);
  React.array(result);
};