0

I am making an API call, and successfully updating the state. The data from the API looks like this:

{ 
 "data": [{"name": string}, {"name":string}, ...]
}

I am making making the API in my didMount lifecycle method. Just for testing, I log the state using a onClick on a button. When I have a logging statement like this Js.log(self.state.projects[0);, the first object in the array gets logged like this:

{ name: "Flaherty" }

Then I log for the name property like this soJs.log(self.state.projects[0].name. I expect "Flaherty" to get logged, but only undefined gets logged.

I have also tried to render name from state like so: <div>(ReasonReact.stringToElement(self.state.projects[0].name)</div> and I get this cryptic error: Cannot read property '__reactInternalInstance$fljwvs5b8u5' of null. I think due to inconsistent state.

I can inspect in react dev tools, that the state indeed has been updated. I am perplexed why then I cannot access the name property in state.projects[0]. Please advise if anyone has a clue!

I am new to ReasonReact, maybe I am making the API call and updating the state not in the best way. If anyone has any suggestions on doing it a better way --I welcome your feedback. Thanks.

Here is my what my component looks like:

type project = {
  name: string
};

type state = {
  projects: array(project)
};

type action =
  | FetchData(array(project))
  | Search;

let component = ReasonReact.reducerComponent("Projects");
let make = (_children) => {
  ...component,
  initialState: () => {
    projects: [||]
  },
  reducer: (action, _state) =>
    switch(action) {
    | FetchData(data) => ReasonReact.Update({projects: data})
    | Search => ReasonReact.NoUpdate
    },
  didMount: (self) => {
    Js.Promise.(
        Axios.get("/api/projects")
        |> then_((response) => resolve(self.send(FetchData(response##data))))
        |> ignore
    );
    ReasonReact.NoUpdate;
  },
  render: (self) =>
    <div>
      <div>
        <span>(ReasonReact.stringToElement("SEARCH"))</span>
        <input _type="text"/>
      </div>
      <div>
        <button onClick=((_) => Js.log(self.state.projects[0].name))>(ReasonReact.stringToElement("log data"))</button>
      </div>
      <div>
        (ReasonReact.arrayToElement(
          Array.mapi((i, project) => {
            <div key=string_of_int(i) >(ReasonReact.stringToElement(project.name))</div>
          }, self.state.projects)
        ))
      </div>
    </div>
};
benschinn
  • 41
  • 5

2 Answers2

2

Thanks to @rickyvetter taking the time to help and working through this with me, the problem was identified as type definition for data from the API. Instead of being defined as a record, it should be defined as a javascript object like so:

type project = {
  .
  "name": string
};

The strings around the property name is sugar.

Then instead of using dot notation the js object accessor should be used. Like so:

self.send.projects[0]##name
benschinn
  • 41
  • 5
2

The issue is a type incompatibility.

type project = {
  name: string
};

is a record type and the data the API is giving you is JS objects which is not compatible:

{"name": string}

The Axios API is giving you a JS object of unknown shape and on this line:

|> then_((response) => resolve(self.send(FetchData(response##data))))

Reason is inferring that response##data is an array of records instead of the array of objects it really is. This can be fixed by changing the type of project:

type project = {.
  "name": string
};

to indicate that it's actually a JS object, or by converting to a record from the api response with something like:

let projObjToRecord = projObj => {name: projObj##name};

and mapping over objects to convert them. If you choose the former then you'll also have to update callsites to use ## instead of . access.