I am very new to ReasonML. I am able to successfully create a stateless component with ReasonReact, but I have not figured out how I might add a custom method to the component (e.g. Next.js' static getInitialProps
).
When attempting to define the getInitialProps
method on the component, I receive a The field getInitialProps does not belong to type ReasonReact.componentSpec
error.
How should I add this define this custom method on the React component?
let str = ReasonReact.stringToElement;
let component = ReasonReact.statelessComponent("Index");
let make = (~items: Listings.items, _children) => {
...component,
getInitialProps: () =>
Js.Promise.(
Endpoints.fetchListings()
|> then_(Fetch.Response.json)
|> then_((json) => Js.Json.decodeArray(json) |> resolve)
),
render: (_self) =>
<div>
(List.length(items) > 0 ? <Listings items /> : <Loading />)
</div>
};
let default =
ReasonReact.wrapReasonForJs(
~component,
(jsProps) => make(~items=jsProps##items, [||])
);
We've found a bug for you!
/Users/davidcalhoun/Sites/web/evergreen-roots/pages/Index.re 7:3-17
5 │ let make = (~items: Listings.items, _children) => {
6 │ ...component,
7 │ getInitialProps: () =>
8 │ Js.Promise.(
9 │ Endpoints.fetchListings()
This record expression is expected to have type
ReasonReact.componentSpec ('a, 'b, 'c, 'd, 'e)
The field getInitialProps does not belong to type ReasonReact.componentSpec
Answered in Discord, reposting here:
You can't define arbitrary methods on the component like that. See that ...component
? That's spread a record with static fields and updating a few fields like you did, e.g. render
.
Also, you can't directly pass a ReasonReact component into a next method, I believe. It's probably asking for a react component class. See the interop section on how to expose the underlying js class for the js side.
In the meantime you can probably just wrap that component in a thin layer of js component that defines the getInitialProps
So you'd have a reactjs component that has getInitialProps
, that renders a ReasonReact component that exposed the underlying class through interop.
If I'm understanding you right, you'd also write ReasonReact bindings to that js wrapper if you're using that wrapper from Reason. Which makes this process a bit contrived, but you're hitting a pathological case here.
As an side: ideally, the Next.js API would ask for a component class, and a getInitialProps
on the side, a totally agnostic function that's not attached onto the component class. That'd greatly simplify the binding process on the Reason side.
I wanted to get context in my reason-react Next.js page and found a solution. Here is an example of accessing the context to detect if we are rendering on the server and passing a boolean into your ReasonReact component:
let make = (~onServer, _children) => {
/* component code goes here */
};
let default =
ReasonReact.wrapReasonForJs(~component, jsProps => make(~onServer=jsProps##onServer, [||]));
let getInitialProps = context =>
Js.Promise.make((~resolve, ~reject as _) => {
let onServer =
switch (Js.Nullable.toOption(context##req)) {
| None => false
| Some(_) => true
};
resolve(. {"onServer": onServer});
});
let inject = [%bs.raw {| (cls, fn) => cls.getInitialProps = fn |}];
inject(default, getInitialProps);
~ https://github.com/zeit/next.js/issues/4202#issuecomment-439175214 All credit to @tmepple and @-rase who posted solution there!
Alternative solution would be to leave your "_app.js" in plain js and pass stuff from that getInitialProps to <Component someParam=manualStuff {...pageProps} />
which can go in as a parameter to your reason-react page.
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