Skip to content

Instantly share code, notes, and snippets.

@broerjuang
Created July 11, 2019 17:56
Show Gist options
  • Save broerjuang/181dd1a958833c84e651e10a58f37893 to your computer and use it in GitHub Desktop.
Save broerjuang/181dd1a958833c84e651e10a58f37893 to your computer and use it in GitHub Desktop.
Renata
module SharedTypes = {
type search = list((string, string));
type path = list(string);
type body = Js.Json.t;
type params = {
search: option(search),
path: option(path),
body: option(body),
formData: option(Fetch.formData),
};
};
open SharedTypes;
module Utils = {
let createElement =
(
make,
makeProps:
(~children: 'b, ~key: string=?, unit) => {. "children": 'b},
children,
) => {
React.createElementVariadic(make, makeProps(~children, ()), [||]);
};
let fromParamsToKey = params => {
ReUtils.Routes.(
switch (params.search, params.path) {
| (Some(s), Some(p)) => s->createQueryString ++ p->createPath
| (Some(s), None) => s->createQueryString
| (None, Some(p)) => p->createPath
| _ => "initialData"
}
);
};
};
module Decode = Atdgen_codec_runtime.Decode;
type networkPolicy =
| CacheFirst
| CacheAndNetwork
| NetworkOnly;
type error = {
code: option(int),
message: string,
};
module type GeneralConfig = {
type response('data);
let readResponse: Decode.t('data) => Decode.t(response('data));
let setResponseState:
(response('data), option('data) => unit, error => unit) => unit;
};
module type RenataConfig = {
type data;
let decoder: Decode.t(data);
let handler: params => Js.Promise.t(Js.Json.t);
};
module Init = (GC: GeneralConfig, RC: RenataConfig) => {
type data = option(RC.data);
type generalResponse = GC.response(data);
type currentState =
| Idle
| Error(error)
| Loaded(data)
| Loading;
type key = string;
type state = {
data: list((key, data)),
currentState,
};
type callback = {
onCompleted: data => unit,
onError: error => unit,
};
type action =
| FetchRequested(networkPolicy, params, callback)
| Internal_FreshFetch(params, callback)
| FetchFailed(error, callback)
| FetchSucceed(key, data, callback)
| Reset;
type value = {
state: currentState,
send: action => unit,
};
module RenataContext = {
let defaultValue = {state: Idle, send: _ => ()};
let context = React.createContext(defaultValue);
module Provider = {
let make = context->React.Context.provider;
[@bs.obj]
external makeProps:
(~value: value=?, ~children: React.element, ~key: string=?, unit) =>
{
.
"value": value,
"children": React.element,
} =
"";
};
};
module Provider = {
[@react.component]
let make = (~children) => {
let (state, setState) =
React.useState(() => {data: [], currentState: Idle});
let rec send = action => {
switch (action) {
| FetchRequested(networkPolicy, params, cb) =>
let key = params->Utils.fromParamsToKey;
let data =
switch (state.data |> List.assoc(key)) {
| data => Some(data)
| exception Not_found => None
};
switch (networkPolicy) {
| CacheFirst =>
switch (data) {
| Some(data) =>
setState(prevState =>
{data: prevState.data, currentState: Loaded(data)}
)
| _ =>
setState(prevState =>
{data: prevState.data, currentState: Loading}
);
send(Internal_FreshFetch(params, cb));
}
| CacheAndNetwork =>
switch (data) {
| Some(data) =>
setState(prevState =>
{data: prevState.data, currentState: Loaded(data)}
)
| _ => ()
};
send(Internal_FreshFetch(params, cb));
| NetworkOnly =>
setState(_ => {data: [], currentState: Loading});
send(Internal_FreshFetch(params, cb));
};
| Internal_FreshFetch(params, callback) =>
let key = params->Utils.fromParamsToKey;
Js.Promise.(
RC.handler(params)
|> then_(json => {
let response =
json |> GC.readResponse(Decode.optional(RC.decoder));
response |> resolve;
})
|> then_(response => {
let setSuccess = data => {
send(FetchSucceed(key, data |? None, callback));
};
let setError = error => {
send(FetchFailed(error, callback));
};
GC.setResponseState(response, setSuccess, setError)
|> resolve;
})
|> ignore
);
| FetchFailed(error, cb) =>
setState(_ => {...state, currentState: Error(error)});
cb.onError(error);
| FetchSucceed(key, data, callback) =>
setState(prevState =>
{
data: prevState.data @ [(key, data)],
currentState: Loaded(data),
}
);
callback.onCompleted(data);
| Reset => setState(_ => {data: [], currentState: Idle})
};
};
<RenataContext.Provider value={state: state.currentState, send}>
...children
</RenataContext.Provider>;
};
};
module Get = {
let useState =
(
~body=None,
~search=?,
~path=?,
~onCompleted=_ => (),
~onError=_ => (),
~networkPolicy=CacheFirst,
(),
) => {
let {state, send} = React.useContext(RenataContext.context);
let (s, p) =
ReUtils.Routes.(
search->createQueryStringFromOption,
path->createPathToStringFromOption,
);
let fetch = (~networkPolicy=networkPolicy, ()) => {
send(
FetchRequested(
networkPolicy,
{search, path, body, formData: None},
{onCompleted, onError},
),
);
};
let refetch = () => fetch(~networkPolicy=NetworkOnly, ());
React.useEffect2(
() => {
fetch();
if (networkPolicy == NetworkOnly) {
Some(() => send(Reset));
} else {
None;
};
},
(s, p),
);
(state, refetch);
};
};
module Post = {
type callbackValue = {
data,
error: option(error),
};
let usePost =
(~search=?, ~path=?, ~onCompleted=_ => (), ~onError=_ => (), ()) => {
let {state, send} = React.useContext(RenataContext.context);
let submit = (~callback=?, ~formData=?, body) =>
send(
FetchRequested(
NetworkOnly,
{search, path, body, formData},
{
onCompleted: data => {
onCompleted(data);
switch (callback) {
| Some(cb) => cb({data, error: None})
| None => ()
};
},
onError: error => {
onError(error);
switch (callback) {
| Some(cb) => cb({data: None, error: error->Some})
| None => ()
};
},
},
),
);
(state, submit);
};
};
};
@broerjuang
Copy link
Author

Usage

Create Renata Instance

module Create =
  RenataManager.Init({
    type response('data) = General_bs.response('data);
    let readResponse = General_bs.read_response;
    let setResponseState = (response: response('data), setSuccess, setError) =>
      if (response.status != "error" && response.status != "") {
        setSuccess(response.data);
      } else {
        let error: RenataManager.error = {
          code: response.errorCode,
          message:
            ApiError.getMessage(
              response.errorCode,
              response.errorDescription,
            ),
        };
        setError(error);
      };
  });

Store Instance

module SomeStore =
  Renata.Create({
    type data = SomeStore_bs.pagination;
    let decoder = SomeStore_bs.read_pagination;
    let handler = {
      ApiClient.send(~method=`Get, ~endpoint="/end-point");
    };
  });

module StoreThatPostData =
  Renata.Create({
    type data = SomeStore_bs.pagination;
    let decoder = SomeStore_bs.read_pagination;
    let handler = {
      ApiClient.send(~method=`Post, ~endpoint="/end-point");
    };
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment