Decoding json payload expects unit

reasonreact

#1

I’m trying to decode a json payload but stumbling on an error

let str = ReasonReact.string;

let url = "https://api.spacexdata.com/v2/launchpads";

type launchpad = {
  padid: int,
  id: string,
  full_name: string,
  status: string,
  location,
  vehicles_launched: list(string),
  details: string,
}
and location = {
  name: string,
  region: string,
  latitude: int,
  longitude: int,
};

type state =
  | Loading
  | Failure
  | Success(list(launchpad));

module Decode = {
  let location = json =>
    Json.Decode.{
      name: json |> field("name", string),
      region: json |> field("region", string),
      latitude: json |> field("latitude", int),
      longitude: json |> field("longitude", int),
    };

  let launchpad = json =>
    Json.Decode.{
      padid: json |> field("padid", int),
      id: json |> field("id", string),
      full_name: json |> field("full_name", string),
      status: json |> field("status", string),
      location: json |> field("location", location),
      vehicles_launched: json |> field("vehicles_launched", list(string)),
      details: json |> field("details", string),
    };

  /*let launchpads = json => json |> Json.Decode.array(launchpad);*/
  let launchpads = json: array(launchpad) =>
    Json.Decode.array(launchpad, json);
};

let fetchLaunchPads = () =>
  Js.Promise.(
    Fetch.fetch(url)
    |> then_(Fetch.Response.json)
    |> then_(json =>
         json |> Decode.launchpads |> (launchpads => launchpads |> resolve)
       )
  );

let component = ReasonReact.statelessComponent("Launches");

let make = _children => {
  ...component,
  didMount: _self => fetchLaunchPads(),
  render: _self => <div />,
};

My json payload:

[{"padid":6,"id":"vafb_slc_4e","full_name":"Vandenberg Air Force Base Space Launch Complex 4E","status":"active","location":{"name":"Vandenberg Air Force Base","region":"California","latitude":34.632093,"longitude":-120.610829},"vehicles_launched":["Falcon 9"],"details":"SpaceX primary west coast launch pad for polar orbits and sun synchronous orbits, primarily used for Iridium. Also intended to be capable of launching Falcon Heavy."},{"padid":3,"id":"ccafs_lc_13","full_name":"Cape Canaveral Air Force Station Space Launch Complex 13","status":"active","location":{"name":"Cape Canaveral","region":"Florida","latitude":28.4857244,"longitude":-80.5449222},"vehicles_launched":["Falcon 9"],"details":"SpaceX east coast landing pad, where the historic first landing occurred. Originally used for early Atlas missiles and rockets from Lockheed Martin. Currently being expanded to add two smaller pads for Falcon Heavy RTLS missions."},{"padid":7,"id":"vafb_slc_4w","full_name":"Vandenberg Air Force Base Space Launch Complex 4W","status":"active","location":{"name":"Vandenberg Air Force Base","region":"California","latitude":34.6332043,"longitude":-120.6156234},"vehicles_launched":["Falcon 9"],"details":"SpaceX west coast landing pad, has not yet been used. Expected to first be used during the Formosat-5 launch."},{"padid":1,"id":"kwajalein_atoll","full_name":"Kwajalein Atoll Omelek Island","status":"retired","location":{"name":"Omelek Island","region":"Marshall Islands","latitude":9.0477206,"longitude":167.7431292},"vehicles_launched":["Falcon 1"],"details":"SpaceX original launch site, where all of the Falcon 1 launches occured. Abandoned as SpaceX decided against upgrading the pad to support Falcon 9."},{"padid":2,"id":"ccafs_slc_40","full_name":"Cape Canaveral Air Force Station Space Launch Complex 40","status":"active","location":{"name":"Cape Canaveral","region":"Florida","latitude":28.5618571,"longitude":-80.577366},"vehicles_launched":["Falcon 9"],"details":"SpaceX primary Falcon 9 launch pad, where all east coast Falcon 9s launched prior to the AMOS-6 anomaly. Initially used to launch Titan rockets for Lockheed Martin. Back online since CRS-13 on 2017-12-15."},{"padid":8,"id":"stls","full_name":"SpaceX South Texas Launch Site","status":"under construction","location":{"name":"Boca Chica Village","region":"Texas","latitude":25.9972641,"longitude":-97.1560845},"vehicles_launched":["Falcon 9"],"details":"SpaceX new launch site currently under construction to help keep up with the Falcon 9 and Heavy manifests. Expected to be completed in late 2018. Initially will be limited to 12 flights per year, and only GTO launches."},{"padid":4,"id":"ksc_lc_39a","full_name":"Kennedy Space Center Historic Launch Complex 39A","status":"active","location":{"name":"Cape Canaveral","region":"Florida","latitude":28.6080585,"longitude":-80.6039558},"vehicles_launched":["Falcon 9","Falcon Heavy"],"details":"NASA historic launch pad that launched most of the Saturn V and Space Shuttle missions. Initially for Falcon Heavy launches, it is now launching all of SpaceX east coast missions due to the damage from the AMOS-6 anomaly. After SLC-40 repairs are complete, it will be upgraded to support Falcon Heavy, a process which will take about two months. In the future it will launch commercial crew missions and the Interplanetary Transport System."},{"padid":5,"id":"vafb_slc_3w","full_name":"Vandenberg Air Force Base Space Launch Complex 3W","status":"retired","location":{"name":"Vandenberg Air Force Base","region":"California","latitude":34.6440904,"longitude":-120.5931438},"vehicles_launched":["Falcon 1"],"details":"SpaceX original west coast launch pad for Falcon 1. Performed a static fire but was never used for a launch and abandoned due to scheduling conflicts."}]

my error:

This has type:
    Js.Promise.t(array(launchpad)) (defined as
      Js.Promise.t(array(launchpad)))
  But somewhere wanted:
    unit    at Array.map (<anonymous>)
    at <anonymous>

#2

In fetchLaunchPads,

what do you want to do with the result? I haven’t run this code, but this looks like fetchLaunchPads returns a Promise which contains the decoded JSON result, but the caller in didMount doesn’t do anything with it, which means it is expecting a unit. If you want this code to just type-check, append an |> ignore in didMount. ignore takes 'a, does nothing with it, and returns a unit; this is used to avoid errors/warnings like this.

But ideally you would want to make your component stateful, and update the state when you get this value decoded.


#3

that fixed it…the final code:

let str = ReasonReact.string;

let url = "https://api.spacexdata.com/v2/launchpads";

type launchpad = {
  padid: int,
  id: string,
  full_name: string,
  status: string,
  location,
  vehicles_launched: list(string),
  details: string,
}
and location = {
  name: string,
  region: string,
  latitude: float,
  longitude: float,
};

type state =
  | Loading
  | Failure
  | Success(array(launchpad));

module Decode = {
  let location = json =>
    Json.Decode.{
      name: json |> field("name", string),
      region: json |> field("region", string),
      latitude: json |> field("latitude", float),
      longitude: json |> field("longitude", float),
    };

  let launchpad = json =>
    Json.Decode.{
      padid: json |> field("padid", int),
      id: json |> field("id", string),
      full_name: json |> field("full_name", string),
      status: json |> field("status", string),
      location: json |> field("location", location),
      vehicles_launched: json |> field("vehicles_launched", list(string)),
      details: json |> field("details", string),
    };

  /*let launchpads = json => json |> Json.Decode.array(launchpad);*/
  let launchpads = json: array(launchpad) =>
    Json.Decode.array(launchpad, json);
};

let fetchLaunchPads = () =>
  Js.Promise.(
    Fetch.fetch(url)
    |> then_(Fetch.Response.json)
    |> then_(json =>
         json |> Decode.launchpads |> (launchpads => launchpads |> resolve)
       )
  );

type action =
  | LoadingLaunchPads
  | LoadedLaunchedPads(array(launchpad))
  | LoadLaunchPadFailed;

let component = ReasonReact.reducerComponent("Launches");

let make = _children => {
  ...component,
  initialState: _state => Loading,
  didMount: self => self.send(LoadingLaunchPads),
  reducer: (action, _state) =>
    switch (action) {
    | LoadingLaunchPads =>
      ReasonReact.UpdateWithSideEffects(
        Loading,
        (
          self =>
            Js.Promise.(
              Fetch.fetch(url)
              |> then_(Fetch.Response.json)
              |> then_(json =>
                   json
                   |> Decode.launchpads
                   |> (
                     launchpads => self.send(LoadedLaunchedPads(launchpads))
                   )
                   |> resolve
                 )
              |> catch(_err =>
                   Js.Promise.resolve(self.send(LoadLaunchPadFailed))
                 )
              |> ignore
            )
        ),
      )
    | LoadedLaunchedPads(launchpads) =>
      ReasonReact.Update(Success(launchpads))
    | LoadLaunchPadFailed => ReasonReact.Update(Failure)
    },
  render: self =>
    switch (self.state) {
    | Loading => <div> {str("Loading...")} </div>
    | Failure => <div> {str("Error...")} </div>
    | Success(launchpads) =>
      Js.log(launchpads);
      <div>
        <h2> {str("Launchpads")} </h2>
        <ul>
          {
            launchpads
            |> Array.map(launchpad =>
                 <li key={launchpad.id}>
                   <b> {str(launchpad.full_name)} </b>
                   {str(":")}
                   {str(launchpad.details)}
                 </li>
               )
            |> ReasonReact.array
          }
        </ul>
      </div>;
    },
};