Skip to content

Instantly share code, notes, and snippets.

@ryyppy
Last active May 8, 2020 11:29
Show Gist options
  • Save ryyppy/e7a74889cc1b1c2c7754d3909a525d5c to your computer and use it in GitHub Desktop.
Save ryyppy/e7a74889cc1b1c2c7754d3909a525d5c to your computer and use it in GitHub Desktop.
ReasonReact useReducer example with a loading component

ReasonReact + useReducer hooks

This Gist is based on a tweet & blog post by Prateek Pandey on how to use a record based reducerComponent with ReasonReact : https://blog.theporter.in/reason-react-component-29fbffd784d6

It's based on the old record API, so I wanted to make an example which uses the newest ReasonReact API which is based on React hooks. IMO the hooks based API is much more lightweight and easier to understand. You can find more infos about it in the official ReasonReact docs.

Below you will find a similar solution to the Loadable state tracking as described in the blog post. The first example shows state tracking via a reducer (useReducer), the second example shows a more simplistic version with useState (the one I would prefer for this specific use-case).

To conclude, the latest ReasonReact update is amazing. If you haven't had the time to look into it, I strongly encourage to check it out :-)

module Post = {
type t = {
id: int,
title: string,
};
/* Simulates the request with a 3 seconds delay */
let fetchPosts = (): Js.Promise.t(array(t)) =>
Js.Promise.make((~resolve, ~reject as _) =>
Js.Global.setTimeout(
() =>
resolve(. [|{id: 1, title: "Post 1"}, {id: 2, title: "Post 2"}|]),
3000,
)
|> ignore
);
};
module Loadable = {
type t('result) =
| Init
| Loading
| Failed(string)
| Success('result);
};
module PostView = {
[@react.component]
let make = (~posts: array(Post.t)) =>
<ul>
{
Belt.Array.map(posts, post =>
<li key={post.id->string_of_int}>
<h1> {ReasonReact.string(post.title)} </h1>
</li>
)
|> ReasonReact.array
}
</ul>;
};
module Posts = {
type action =
| LoadData
| LoadSuccess(array(Post.t))
| LoadFail(string);
type state = Loadable.t(array(Post.t));
[@react.component]
let make = () => {
let (state, dispatch) =
React.useReducer(
(_state, action) =>
switch (action) {
| LoadData => Loadable.Loading
| LoadSuccess(posts) => Loadable.Success(posts)
| LoadFail(msg) => Loadable.Failed(msg)
},
Loadable.Init,
);
/* We only want to use the effect once, so we provide an empty array as dependencies */
React.useEffect0(() => {
open Js.Promise;
/* Wait 2 seconds to demo our init state */
Js.Global.setTimeout(
() => {
/* We start our loading process and do the fetching */
dispatch(LoadData);
Post.fetchPosts()
|> then_(posts => dispatch(LoadSuccess(posts)) |> resolve)
|> catch(_ =>
dispatch(LoadFail("Could not load posts")) |> resolve
)
|> ignore;
},
2000,
)
|> ignore;
/* We don't return any cleanup handler */
None;
});
<div>
{
switch (state) {
| Init =>
<span> "Not doing anything right now"->ReasonReact.string </span>
| Loading => <span> "Loading posts..."->ReasonReact.string </span>
| Failed(msg) =>
<span>
{("Loading failed because of: " ++ msg)->ReasonReact.string}
</span>
| Success(posts) => <PostView posts />
}
}
</div>;
};
};
/* Same example with setState instead of useReducer */
module Post = {
type t = {
id: int,
title: string,
};
/* Simulates the request with a 3 seconds delay */
let fetchPosts = (): Js.Promise.t(array(t)) =>
Js.Promise.make((~resolve, ~reject as _) =>
Js.Global.setTimeout(
() =>
resolve(. [|{id: 1, title: "Post 1"}, {id: 2, title: "Post 2"}|]),
3000,
)
|> ignore
);
};
module Loadable = {
type t('result) =
| Init
| Loading
| Failed(string)
| Success('result);
};
module PostView = {
[@react.component]
let make = (~posts: array(Post.t)) =>
<ul>
{
Belt.Array.map(posts, post =>
<li key={post.id->string_of_int}>
<h1> {ReasonReact.string(post.title)} </h1>
</li>
)
|> ReasonReact.array
}
</ul>;
};
module Posts = {
type state = Loadable.t(array(Post.t));
[@react.component]
let make = () => {
let (state, setState) = React.useState(_ => Loadable.Init);
React.useEffect0(() => {
open Js.Promise;
/* Wait 2 seconds to demo our init state */
Js.Global.setTimeout(
() => {
/* We start our loading process and do the fetching */
setState(_ => Loadable.Loading);
Post.fetchPosts()
|> then_(posts =>
setState(_ => Loadable.Success(posts)) |> resolve
)
|> catch(_ =>
setState(_ => Loadable.Failed("Could not load posts"))
|> resolve
)
|> ignore;
},
2000,
)
|> ignore;
/* We don't return any cleanup handler */
None;
});
<div>
{
switch (state) {
| Init =>
<span> "Not doing anything right now"->ReasonReact.string </span>
| Loading => <span> "Loading posts..."->ReasonReact.string </span>
| Failed(msg) =>
<span>
{("Loading failed because of: " ++ msg)->ReasonReact.string}
</span>
| Success(posts) => <PostView posts />
}
}
</div>;
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment