Typing a generic array of objects

Hi!

I am working with React and trying to build a common component that is used across the app, so some of the props need to be generic to adapt nicely to the different requirements. This is how I currently define it:

@react.component
let make = (
  ~data: array<'a>, 
  ~yAxisKeys: array<string>,
 ...
) => {
   ...
}

Specifically, this component is a graph, that takes a set of data in the form of an array of objects. Each key in the object can be of type string or number. Then, it also takes a yAxisKeys prop, that determines which of the keys in the objects will be used for the y axis in the graph.

The main problem I am having is that at some point in the logic, I need to find the max value of these keys, for which I wanted to loop through them to find out the max value. My initial approach was getting the array of keys, and then using them to map the data array and generate an array of the values, which I can then use to find the maximum with other logic (max is coming from lodash). This is how I did it for it with JS:

const keys = Object.keys(data[0]).filter(d => d !== xAxisKey)
const dataMaxValue = _.max(data.map(d => _.max(keys.map(k => Number(d[k]))))),

How would I translate this into ReScript? From my attempts, I am always running into a type issue for the last step d[k], since it doesn’t recognise that k can be a key of the data object.

Thanks!

It might help to use the variant type for the data.

I don’t fully understand the problem from your code. It would be helpful to fully describe what you want to obtain.

Like Rodion said above, it would be helpful to show what you tried. However, if I understand what you are trying to do, it will not work as you are currently trying to do it.

Take a look at the types of your JS code (as if those types were rescript types). Note that I will use {..} for some object.

// keys: array<string>
// data: array<{..}>
// data[0]: {..}
const keys = Object.keys(data[0]).filter(d => d !== xAxisKey)

// data: array<{..}>
// d: {..}
// k: string
// d[k]: trying to access the value of d whose key is k, but k is a binding.  Won't work!
const dataMaxValue = _.max(data.map(d => _.max(keys.map(k => Number(d[k])))))

To make it clear that accessing an object this way won’t work. Check out this playground:

let o = {"a": 23, "b": "whatever"}
let a = o["a"] // okay!
// Bind k to a valid key.
let k = "a"
// But you still can't access it "dynamically" through a binding.
let a = o[k] // Error: This has type: {"a": int}. Somewhere wanted: array<'a>

If you need “dynamic” access to an object (i.e., your keys are in a binding), maybe the Js.Dict will serve your needs. The values all must be of the same type however.

More info that may be useful:

Hi there! Thanks for offering your help :slight_smile:

To provide a bit of a better context, imagine I have a data object that is like this:

data: array<teamScores> =[
  {
    "team": "Team A",
    "scoreA": 9.1,
    "scoreB": 21.1,
    "scoreC": 30.2,
  },
  {
    "team": "Team B",
    "scoreA": 21.1,
    "scoreB": 70.3,
    "scoreC": 9.5,
  }]

My intention was that I would pass this data array to the component as per below:
<Graph data={data} xAxisKey={"team"} />

In order for the graph to know how to scale the Y axis, I was trying to find the maxValue available in the dataset, which in this example would be 70.3, that’s what I was trying to achieve with the const dataMaxValue function in the previous comment, look through the array of objects and find the maximum score.

From Ryan’s comment, it looks like this kind of approach that I was attempting does not work with ReScript. I think I found a different way to achieve this, by passing a function to the component that determines the maximum instead of trying to achieve it in the component itself. i.e.:

<Graph data={data} xAxisKey={"team"} yMax={(d: teamScores)=> Lodash.max([d["scoreA"], d["scoreB"], d["scoreC"]])} />

And then inside the Graph component, just use that function:

const dataMaxValue = Lodash.max(Belt.Array.map(data, yMax))

Using rescript I would write code like this:

type teamScores = {"team": string, "scoreA": float, "scoreB": float, "scoreC": float}

let data: array<teamScores> = [
  {
    "team": "Team A",
    "scoreA": 9.1,
    "scoreB": 21.1,
    "scoreC": 30.2,
  },
  {
    "team": "Team B",
    "scoreA": 21.1,
    "scoreB": 70.3,
    "scoreC": 9.5,
  },
]

let dataMaxValue =
  data->Belt.Array.reduce(0.0, (acc, teamScore) =>
    Js.Math.maxMany_float([acc, teamScore["scoreA"], teamScore["scoreB"], teamScore["scoreC"]])
  )
2 Likes