I'm using Reason-Apollo to parse a pretty nested GraphQL response from my server. I'm having trouble parsing the hairy tree of options returned from my GraphQL server (I'm using django-graphene).
Here is the GraphQL query and the Reason React module using Reason Apollo:
module GroupQuery = [%graphql {|
query GetChatGroup($chatGroupId: ID!){
chatGroup(id: $chatGroupId) {
id
users {
edges {
node {
id
name
isCurrentUser
}
}
}
messages {
edges {
node {
id
text
author {
name
abbreviation
photoUrl
isCurrentUser
}
}
}
}
}
}
|}];
/*eventually will be a reducerComponent*/
let component = ReasonReact.statelessComponent("RechatWindow");
module Query = RechatApollo.Instance.Query;
let parseMessages = chatGroup =>
switch chatGroup {
| Some(chatGroup) =>
switch chatGroup##messages {
| Some(messages) =>
let edges = messages##edges;
switch edges {
| Some(edges) =>
let parsedNodes =
Js.Array.map(
node =>
switch node {
| Some(node) =>
let id = node##id;
let text = node##text;
let author = node##author;
switch (id, text, author) {
| (Some(id), Some(text), Some(author)) =>
let name = author##name;
let abbrev = author##abbreviation;
let isCurrentUser = author##isCurrentUser;
switch (name, abbrev, isCurrentUser) {
| (Some(name), Some(abbrev), Some(isCurrentUser)) =>
id ++ " - " ++ text ++ " - " ++ name ++ " - " ++ abbrev ++ " - "
| _ => "Error retrieving message 3"
};
| _ => "Error retrieving message 2"
};
| _ => "Error retrieving message 1"
},
edges
);
parsedNodes;
| None => [||]
};
| None => [||]
};
| None => [||]
};
let make = (_children) => {
...component,
render: (_) => {
let unexpectedError = <div> (ReasonReact.stringToElement("There was an internal error")) </div>;
let groupQuery = GroupQuery.make(~chatGroupId="Q2hhdEdyb3VwVHlwZTox", ());
<Query query=groupQuery>
...((response, parse) => {
switch response {
| Loading => <div> (ReasonReact.stringToElement("Loading")) </div>
| Failed(error) => <div> (ReasonReact.stringToElement(error)) </div>
| Loaded(result) => {
let chatGroup = parse(result)##chatGroup;
let parsedMessages = parseMessages(chatGroup);
<ul>
(
ReasonReact.arrayToElement(
Array.map(message => <li> (ste(message)) </li>, parsedMessages)
)
)
</ul>;
}
}
})
</Query>
}
};
Here is the return data from the GraphQL query from GraphiQL:
{
"data": {
"chatGroup": {
"id": "Q2hhdEdyb3VwVHlwZTox",
"users": {
"edges": [
{
"node": {
"id": "VXNlclR5cGU6MzQ=",
"name": "User 1",
"isCurrentUser": false
}
},
{
"node": {
"id": "VXNlclR5cGU6MQ==",
"name": "User 2",
"isCurrentUser": true
}
}
]
},
"messages": {
"edges": [
{
"node": {
"id": "Q2hhdE1lc3NhZ2VUeXBlOjE=",
"text": "my first message",
"author": {
"name": "User 1",
"abbreviation": "U1",
"photoUrl": "",
"isCurrentUser": true
}
}
}, ...
I have a syntax error somewhere ...
137 ┆ | Loaded(result) => {
138 ┆ let chatGroup = parse(result)##chatGroup;
139 ┆ let parsedMessages = parseMessages(chatGroup);
140 ┆ <ul>
141 ┆ (
This has type:
option(Js.t({. id : string,
messages : option(Js.t({. edges : array(option(Js.t(
{. node :
option(
Js.t(
{. author :
Js.t(
{. abbreviation :
option(
string),
isCurrentUser :
option(
Js.boolean),
name :
option(
string),
photoUrl :
option(
string) }),
id :
string,
text :
string })) }))) })),
users : option(Js.t({. edges : array(option(Js.t({. node :
option(
Js.t(
{. id :
string,
isCurrentUser :
option(
Js.boolean),
name :
option(
string) })) }))) })) }))
But somewhere wanted:
option(Js.t({.. messages : option(Js.t({.. edges : option(Js.Array.t(
option(
Js.t({.. author :
option(
Js.t(
{.. abbreviation :
option(
string),
isCurrentUser :
option('a),
name :
option(
string) })),
id :
option(
string),
text :
option(
string) })))) })) }))
Types for method edges are incompatible
My immediate question: what is the error here?
On a deeper level, parsing all of these options to render the desired response seems like it would generally produce pretty unclear code. So what is the common paradigm around parsing options in JS when using ReasonML / OCaml? Is there an idiomatic way to get all of the options that will be there most of the time? Should I be creating an object type or a record type and parsing into those, and then rendering from the "known" object or record structures?
Or perhaps my graphql_schema.json
and endpoint needs to have more required options?
Also, I'm using Relay's GraphQL convention of having edges { node { ... node fields ... } }
, and it seems like if there are any edges then there should be at least one node. Is there any way to cut down on the option verbosity when using relay-style GraphQL?
The large types in the error message can make it hard to see what's going on, so it's helpful to boil it down to just the type differences. It's complaining about the messages
field that it says has the type:
option(Js.t({. edges : array(option(Js.t(...
while it's actually used as:
option(Js.t({.. edges : option(Js.Array.t(Js.t(...
So edges
is actually a non-optional array whereas you are using it as an option(Js.Array.t)
. You do not need to check if it is Some
, perhaps just if it is an empty array []
. Then you'll want to use Array.map
to handle the non-empty case.
Try going through and fixing your usage so that the inferred type matches the the type you're getting from your query until it compiles successfully.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With