Created
July 11, 2019 17:56
-
-
Save broerjuang/181dd1a958833c84e651e10a58f37893 to your computer and use it in GitHub Desktop.
Renata
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}; | |
}; | |
}; |
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
ReUtils.re