reactjsreact-hooksreasonreason-react

ReasonReact useState with fetched data


I have been using React for a long time and I would like to give a chance to Reason React. I created then a small project to fetch data from a Github repository and then display results.

I would like to use React Hooks and store results into my state.

Here's what I'm trying to do:

type githubRepo = {
  name: string,
  url: string,
};

module Decode = {
  let repo = (repo): githubRepo =>
    Json.Decode.{
      name: repo |> field("name", string),
      url: repo |> field("url", string),
    };
};

let useFetch = (~url: string, ~options=?, ()) => {
  let (loading, setLoading) = React.useState(() => true);
  let (result, setResult) = React.useState(() => []);

  React.useEffect1(
    () => {
      ignore(
        Js.Promise.(
          Fetch.fetch(url)
          |> then_(Fetch.Response.json)
          |> then_(json =>
               json
               |> Decode.repo
               |> (
                 decodedRepo => {
                   Js.log(decodedRepo);
                   setResult(result => decodedRepo);
                   setLoading(_ => false);
                   Some(decodedRepo);
                 }
               )
               |> resolve
             )
          |> catch(_err => resolve(None))
        ),
      );

      None;
    },
    [||],
  );

  (loading, result);
};

When I try to do setResult(_ => decodedRepo) it throws an error

This has type: githubRepo But somewhere wanted: list('a)

I know I've initialized my React.useState to an empty list but I can't figure out how to set my decodedRepo inside.

Here's the result of Js.log(decodedRepo):

Array [ "Node.js", "https://api.github.com/orgs/nodejs" ]
  0: "Node.js"
  1: "https://api.github.com/orgs/nodejs"
  length: 2

I would also like to know if there is a way to init useState with no value?


Solution

  • You can prepend an item to a list by using the list spread syntax, similarly to how you would in JavaScript:

    setResult(result => [decodedRepo, ...result]);
    

    But if you don't want to deal with the possibility of more than one item, you need to use a container that is restricted to either zero or one item only. This is the option type. In use it would look something like this:

    let (result, setResult) = React.useState(() => None);
    setResult(result => Some(decodedRepo));
    

    Then when you use result you are forced to deal with the possibility that it may or may not contain an item:

    switch (result) {
    | Some(decodedRepo) => ...
    | None => ...
    }