How to use new async/await syntax in the context of react component?

I have an async function that gets data from server

let loadStoreFromServer = async () => {
   ...
}

I want to initialize my main component state with this function, how should I proceed ?

Should the component itself be an async function ?

Here are some attempts

@react.component
let make = () => {
  let (store, setStore) = React.useState(() => loadStoreFromServer ())
  // OR
  let (store, setStore) = React.useState(() => await loadStoreFromServer ())
  // OR
  let (store, setStore) = React.useState(() => { /* init data*/ })
  React.useEffect0((() => {
    setStore(_ => loadStoreFromServer ())
  }))
  // OR
  let (store, setStore) = React.useState(() => { /* init data */ })
  React.useEffect0((() => {
    setStore(_ => await loadStoreFromServer ())
  }))
  // OR
  React.useEffect0((() => {
    let load = async () => {
      let serverStore = await loadStoreFromServer ()
      setStore(_ => serverStore)
    }
    load()
  }))
}

From what I understand await must be used inside and async function. So make should be async as well, but does react has a mechanism to handle async component ?

AFAIK, this is the approach recommended by the React-team (ignoring Suspense).

2 Likes

Thanks, but compiler would not allow me to do that.

This has type: promise<unit>
Somewhere wanted: option<unit => unit>

I’ve made it work doing like so :

React.useEffect0((() => {
    let load = async () => {
      let serverStore = await loadStoreFromServer ()
      setStore(_ => serverStore)
    }
    let _ = load()
    None
  }))

Could you tell me what you mean by “ignoring Suspense” ? I’ve found this doc but it doesn’t seem related.

1 Like

Instead of using useEffect and useState to fetch data while mounting a component, you can use Suspense to work with async data directly. You can define a component, which will be rendered until the data is there or if an error occurs.

So if you don’t use suspense the recommended way would probably be like in the example of lessp.
Don’t know if suspense would be the preferred way otherwise. When I tried it the last time, it was an experimental feature of react.

Anyways, I don’t see any advantage in simple component trees. Rescript is a wonderful language to model the state like this:

type state<'a> = | Loading | Loaded('a) | Error(string) ...

and use useEffect and a switch statement to render the right component at the right time.

1 Like

Thanks for clarification about Suspense. I think I’ll use your switch over states suggestion.

Now if I go back to my initial question, I’d like to better understand the use of async functions in react.

From the docs

You may only use await in async function bodies

But there from what I see, the function I pass to useEffect is not async. So I cannot write

await load()

However compiler lets me write

let _ = load()

But then I completly ignore the fact that it’s async.

The produced js looks like

React.useEffect((function () {
    var load = async function (param) {
      var serverStore = await loadStateFromServer(undefined);
      return Curry._1(setLoad, (function (param) {
            return {
                TAG: /* Loaded */0,
                 _0: serverStore
            };
       }));
    };
    load(undefined);
), []);

Here again load is called ignoring async, and it works perfectly fine.

So maybe this is more a javascript question like why can we call an aync function synchronously ?

An asynchronous function is a function that returns a promise, nothing more. It can be called as any other function. The only reason you can’t use async directly in useEffect is because it expects the return to be option<() => ()> while with async it becomes promise<option<() => ()>>.