Unhandled Rejection (TypeError): l.map is not a function

Hey DAMLer’s!

I am working on updating our version of ex-cdm-swaps. With @cocreature help I converted the scenarios to scripts and am now building out a React UI to demo a workflow. I am using a base template similar to ex-bond-issuance.

I ran across an error that I haven’t seen before and am struggling to figure out what it means. After executing the DeriveEventsWorkflow.Trigger choice I get this error.

I saw some previous threads here and here that seemed a bit similar to what I am facing.

I tried the steps specified here but still got the same error.

Does anyone have any ideas as to what this might be?

1 Like

Looking through the code, it seems like this might be a result of your DAML code expecting a list in a template field (or nested somewhere within a field) or within a choice argument while you’re passing something else in JavaScript. Given that this happens on an exercise probably the latter. If you can share the definition of the choice in DAML and the JavaScript that exercises it, we might be able to pin it down.

1 Like

Hmm. That could make sense. This is the first time I have had to write a function where one of the fields, refData, is actually an object made of two lists. Here is what I wrote:

const ciCid    = "Contract Instance"
const fromDate = "From Date"
const toDate   = "To Date"
const holidayCalendarCids  = "Holiday Calendar Instance"
const observationCids = "Observation Calendar Instance"    
const doTrigger = function (contract, params) {
        const payload = {
          ciCid: params[ciCid],
          fromDate: params[fromDate],
          toDate: params[toDate],
          refData: {
                      holidayCalendarCids: params[holidayCalendarCids],
                      observationCids: params[observationCids]
          }
        }
        ledger.exercise(DeriveEventsWorkflow.Trigger, contract.contractId, payload);
      };

I then put this information in a ButtonToForm component where dialogs is defined nearly the same as many of the Contracts.js files found…

                <ButtonToForm 
                  contracts={deriveEvents.contracts} 
                  dialogs={[
                    ["Find Upcoming Event",
                    [
                      field(ciCid, "menu", contractInstance.contracts.map(c => c.contractId), contractInstance.contracts.map(c => c.payload.d.primeKey)),
                      field(fromDate, "date"),
                      field(toDate, "date"),
                      field(holidayCalendarCids, "menu", holidayCalendar.contracts.map(c => c.contractId), holidayCalendar.contracts.map(c => c.payload.d.label)),
                      field(observationCids, "menu", rateObservation.contracts.map(c => c.contractId), rateObservation.contracts.map(c => obsDataToText(c.payload)))
                    ],
                    doTrigger,
                    "Find Upcoming Event"
                  ],
                  ]}
                />

Do you see anything weird about the function call? I have read over it a few times and nothing jumps out at me. When I click the dialog box, I see all of the fields that I expect to see and am able to select each of them.

Perhaps I need to make an adjustment to addFormFields() because field holidayCalendarCids and observationCids need to accept a list (even though I am only passing one argument).

function addFormFields(name, dialogFieldSpec) {
    return (
      <>
      <Grid container spacing={2}>
      {dialogFieldSpec.map(spec =>
       <Grid item xs={6} key={spec["name"]} className={classes.formField}>
       {(spec["type"] === "menu")
        ?
        <FormControl className={classes.formControl} key={spec["name"]} fullWidth=true}>
          <InputLabel>{spec["name"]}</InputLabel>
          <Select value={getDialogState(name, spec["name"], spec["items"]0])} defaultValue={spec["items"]0]} onChange=(event) => setDialogState(name, spec["name"], event.target.value)}>               {
              spec["items"].map((item, i) =>
                <MenuItem key={item} value={item}>{spec["itemNames"][i]}</MenuItem>
              )
            }
          </Select>
        </FormControl>
        : <TextField
            required
            autoFocus
            fullWidth={true}
            key={spec["name"]}
            label={spec["name"]}
            type={spec["type"]}
            className={classes.textField}
            InputLabelProps={{
              shrink: true,
            }}
            onChange={(event) => setDialogState(name, spec["name"], event.target.value)}
            />}
      </Grid>
      )}
      </Grid>
      </>
    );
  }
1 Like

I recommend inserting a console.log(payload); before the exercise. It’s a bit tricky to piece the different pieces together to see what actually ends up being passed as the argument.

It would also be great if you could share the corresponding DAML choice definition so we can see where the mismatch is.

It might also be worth pointing out that if you use typescript, you will get compile-time errors for this type of mismatch which can be much easier to debug and to keep in sync than having this blow up on runtime.

2 Likes

I will add that and retest and get back to you.

Will this line add those details in the console? When I worked with the old SDK’s there was great visibility into the contents of a command in the console developer tools.

Here is the DAML template. It is the same as is found here:

template DeriveEventsWorkflow
  with
    sig      : Party
  where
    signatory sig
    controller sig can
      nonconsuming Trigger: [ContractId DerivedEvent]
        with
          ciCid     : ContractId ContractInstance
          fromDate  : Optional Date
          toDate    : Optional Date
          refData   : RefData
        do
          ci <- fetch ciCid
          pastEvents <- mapA (\enCid -> do en <- fetch enCid; return en.d) ci.lifecycleEventCids
          let spec = DerivedSpec with contract = ci.d, ..
          events <- mapA setEventPrimeKey =<< (flip Srm.buildDerivedEvents) spec <$> toSrmRefData refData
          contractIdentifier <-
            (\p -> findIdentifierByParty p ci.d.contractIdentifier)
            =<< (fromSomeTry "party not found" $ find (\ciSig -> ciSig.p == sig) ci.ps)
          mapA (\event -> create DerivedEvent with d = event, ps = ci.ps, ..) events

One other thing I checked was to make sure that my intended command worked inside my script. This is the command that I am trying to replicate.

edCidsReset <- submit p1.p do exerciseCmd dewCid Trigger with refData, ciCid, fromDate = Some (D.date 2019 Mar 4), toDate = Some (D.date 2019 Mar 24)

1 Like

Yes, that will log the payload in the console. You should get the same commands in the developer tools that you got on old SDKs but you probably only get info about commands that actually are sent to the ledger. This one is failing before it gets sent.

2 Likes

Thanks for the help, @cocreature. I was able to resolve the issue. Your intuition was correct! :brain:

I had to add square brackets around the params for holidayCalendarCids and observationCids:

const doTrigger = function (contract, params) {
    const payload = {
      ciCid: params[ciCid],
      fromDate: params[fromDate],
      toDate: params[toDate],
      refData: {
                  holidayCalendarCids: [params[holidayCalendarCids]],
                  observationCids: [params[observationCids]]
      }
    }
    ledger.exercise(DeriveEventsWorkflow.Trigger, contract.contractId, payload);
  };
1 Like

Note that this means your lists of contract ids will always only contain a single element. So you might want to rename observationCids to observationCid and similar for the other one to make it clearer that those are not lists.

1 Like