Resolving error `This call is missing an argument of type (~authState: option(Authenticator.authData)) => React.element`

reasonreact

#1

I have this module that is compiling. When I use the module, like so,

ReactDOMRe.renderToElementWithId(
  <Authenticator.WithAuthenticator>
    <ReasonApollo.Provider client=Client.instance>
      <App />
      /* <App /> */
    </ReasonApollo.Provider>
  </Authenticator.WithAuthenticator>,
  "root",)

it is producing the following error.

  17 │ Js.log(token);
  18 │ ReactDOMRe.renderToElementWithId(
  19 │   <Authenticator.WithAuthenticator>
  20 │     <ReasonApollo.Provider client=Client.instance>
  21 │       <App />

  This call is missing an argument of type
  (~authState: option(Authenticator.authData)) => React.element

How would I resolve this error?

The full module is:

[@bs.module "aws-amplify-react"] [@react.component]
external make: ('a) => React.element = "Authenticator";

type authData = string;

let getToken: authData => string = [%raw
  {|
  function(a){
    return a.currentAuthenticatedUser.idToken.jwtToken;
  }
  |}
];

module Authenticator = {
  type authState = string;
  type onStateChange = (authState, authData) => unit;

  [@react.component]
  let make = (~onStateChange, ~children) => {
    children
  };
};

module WithAuthenticator = {
  type state = {authData: option(authData)};

  type action =
    | Update(authData);

  let initialState = {authData: None};
  let reducer = (state, action) =>
    switch (action) {
    | Update(authData) => {authData: Some(authData)}
    };

  [@react.component]
  let make = children => {
    let (state, dispatch) = React.useReducer(reducer, initialState);
    <Authenticator onStateChange={authData => dispatch(Update(authData))}>
    {children(~authState=state.authData)}
    </Authenticator>;
  };
};

Thank you.


#2

I’m not 100% sure, but doesn’t your WithAuthentificator HOC expect as children a React component with an authState prop? Whereas what it gets is a result of <ReasonApollo.Provider />, i.e., a React element?


#3

So how would I call that?


#4

First of all, there’s also a problem with your WithAuthentificator.make: children should be a labeled argument. This is what your error actually tells you: WithAuthentificator expects a non-labeled parameter, which JSX doesn’t give, because it compiles to passing a labeled one. But my point about components vs elements is still likely to be true.

As for how to call it, depends on where you want your authState, I guess. Probably you need something like this:

// that "children" component
[@react.component]
  let make = (~authState) => {
    <ReasonApollo.Provider client=Client.instance authState>
        // because if you need the authState deeper, why not
        // use that Authentificator wrapper deeper
      <App />
    </ReasonApollo.Provider>
  };

// the usage
ReactDOMRe.renderToElementWithId(
  <Authenticator.WithAuthenticator>
    {ThatChildrenComponent.make}
  </Authenticator.WithAuthenticator>,
  "root",
)

Or, if ReasonReact types allow it (I haven’t checked):

ReactDOMRe.renderToElementWithId(
  <Authenticator.WithAuthenticator>
    {(~authState) => {
      <ReasonApollo.Provider client=Client.instance authState>
        <App />
      </ReasonApollo.Provider>
    }}
  </Authenticator.WithAuthenticator>,
  "root",
)

And by the way, I was wrong. Even though you’ve named it WhitAuthentificator, that component is not a HOC, it’s a render prop component.

Which begs a question, why go with a HOC or a render prop? I mean, ReasonReact 0.7 has hooks, aren’t you HYPED? :grin:


#5

I started trying to switch this to a hook, actually, as the same thought occurred to me.

This is where I am at:

module Authenticator = {
   type authState = string;
   type onStateChange = (authState, authData) => unit;
   [@react.component]
   let make = (~onStateChange,~children) => {
     <div>
       children
     </div>
   }
 };

 module WithAuthenticator = {
   type state = {authData: option(authData)};

   /* type action =
     | Update(authData); */
   type action =
     | Update;

   let initialState = {authData: None};
   let reducer = (state, action) =>
   switch (action) {
     | Update => {authData: Some(authData)}
     };
   /* switch (action) {
     | Update(authData) => {authData: Some(authData)}
     }; */

  
 [@react.component]
   let make = (~onStateChange) => {
    let (state, dispatch) = React.useReducer(reducer, initialState);
    /* let (authData, setAuthData) = React.useState(() => None); */
       <Authenticator onStateChange={onStateChange dispatch(Update(authData))}>
       /* <Authenticator onStateChange={onStateChange => setAuthData(authData)}> */
         {children(~authState=authData)}
       </Authenticator>
  };

which doesnt work but that was a few days ago. I need to revisit. What do you think about this start?


#6

Are you sure you need your Authentificator to be a HOC? As in, pass ~authState to a wrapped component (children)? I was thinking more along the lines of creating a useAuth hook and using it inside the child that needs the auth data.

Then again, you may have good reasons for doing it the other way, so maybe the useAuth idea doesn’t make sense in your particular case.

Also, both your Authentificator.make and WithAuthentificator.make refer to children, but where does the latter take it from?


#7

No good reason, brother. Coding blindly, here. While i have started to use hooks uniformly as a matter of making myself learn it, I have not at all grokd when I should use a useEffect or useReducer or custom hook. So I am going to try you useAuth suggestion to see what comes of it. Be back soon.