Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
Vesa Karvonen
@polytypic

@abstracthat I would be curious to hear about any experiences (good and bad) you've had using the library and how you might compare it to other request libraries you've used. I got the impression you've also used XHRs with L.traverse. I'd love to see examples of any interesting use cases you might have come up with (with or without L.traverse).

I'm asking, because I'm considering having a presentation on the Karet XHR library. Even though the ideas behind it are rather simple, I believe they are still not common knowledge and there are people who might find them interesting and benefit from them.

Riku Tiira
@rikutiira
@polytypic do you mean a live presentation? is it some event I can attend as well?
Vesa Karvonen
@polytypic
A company internal presentation for starters, but it would likely be possible to have a guest there. If I have such a presentation, I will likely take a screen recording.
Josh Bertrand
@abstracthat
Sorry I missed that ping. We were on summer holiday.
I'm very much enjoying using karet.xhr. I think the XHR monad makes a great container for the model layer of our app. It's very flexible to work with.
I've built two abstractions on top of it. Request formats requests (camel to snake case, format url query string, filters, etc) and responses (mostly just snake to camel) from the API. It uses a duplicate request cache so that simultaneous GET requests for the same params share the same XHR.
Then there is Resource which is for setting up our endpoints with some common functionality. This is where I've used L.traverse. Many of our endpoints return related resources as a link (or sometimes an array of id's). So we can declare what related resources we want to load (with an array of stings for the key) and get back the data structure all filled in.
U.thru(initialRequest, XHR.chain(traverseParallelRequests))
The first request uses XHR.chain which takes L.traverse using the XHR.IdentityParallel algebra. This does exactly what I need... I can create an array of XHR's with urls created by the result of the first XHR. Those get sent out as parallel XHR's to the browser. Then I XHR.apply that with a function that knows how to combine the new responses into a single response.
const traverseParallelRequests = L.traverse(
  XHR.IdentityParallel,
  initialResponse =>
    U.thru(
      prepareRelatedResourceRequests(initialResponse, relatedResourcesToLoad),
      XHR.apply(mergeResponsesWithIntitalResponse(initialResponse, relatedResourcesToLoad))
    ),
  traversalLens
)
At the component level it's great. We get to think of it as one request and observe a single XHR.isProgressing and get a single XHR.response. I'm tempted to say the result is magical but I know it's just math.
When you set up an API endpoint with Resource you get convenience methods for interacting with the API as well as a component that makes use of L.collectAs to run the data through a lens that adapts it to a component.
<Patient
  as={ComponentView}
  load={['appointments', 'invoices']}
  view={...adapt to component with lens}
  query={...optional api filters} />
As to challenges I did conceptually have a little bit of a difficult time with the XHR as applicative or monad. But after playing with it I see how they fit together and it's really nice to work with.
One thing I need to do still is allow for nested traversals so that you can load related resources from a related resource and declare that at the top most request. This is somewhat rare in our app, but I'll need to handle it eventually.
Vesa Karvonen
@polytypic

Thanks for the feedback, @abstracthat ! No problem, I assumed you might be on holiday.

Sounds like you are putting Karet XHR to good use!

The way I've been thinking about it is that Karet XHR has two rare features. I say "rare", because I haven't looked at all XHR libraries and I would find it really surprising to not find similar features in other XHR library.

The first rare feature of Karet XHR is simply that the API is comprehensive without using plain callbacks or other forms of side channel communication for anything. For example, Axios uses callbacks for upload and download progress and cancellation tokens. Both are a kind of side-channels where you setup some mutable state to observe or make changes to an ongoing XHR. In Karet XHR the XHR itself and all the properties of a XHR request are exposed via observables obtained from the one observable that represents the request. For progress monitoring you just obtain an observable(s) for the relevant progress information and hook them up where you want them. For cancellation you just make sure that you don't unnecessarily hold on to any observables obtained from the request.

The second rare feature of Karet XHR is that one can compose one or more requests in various ways and still treat the composition as if it were just one request. I think that this kind of composition is only possible because of the first feature of not requiring the use of side channels like callbacks or cancellation tokens. Currently the library provides (parallel) applicative and (sequential) monadic algebras, but there could be others. Axios, as an example again, wraps requests as promises and you can manage multiple promises as usual (e.g. via .then and Promise.all). To get combined progress reporting and cancellation you need to create and pass callbacks (and somehow combine their results) and cancellation token(s) to each individual request. With Karet XHR a composition of requests can be observed just the same as a single request.

Promises are a popular approach for wrapping HTTP requests, but it does seem to me that promises are not always ideal for the purpose. HTTP requests do not just produce a single result and you often want to report progress information (time varying properties) and cancel requests.

Josh Bertrand
@abstracthat
:100:
Josh Bertrand
@abstracthat

@polytypic Curious if you have any ideas on improving this xhrCache. The idea of is to share one request for any identical GET requests that are made before the first has successfully returned. We do this because it is much simpler to let any component that needs data be able to fetch the data itself rather than relying on passing it around through props to different components on the page.

I couldn't sort out returning the cached request and removing it from cache without using a plain object with synchronously computed keys based on the formattedUrl. It works but I can't help but think that there is a more elegant observable approach.

const xhrCache = {}

const makeRequest = method => (endpoint, params = {}, store) => {
  const isGet = R.equals('GET', method)
  const formattedUrl = U.combine([endpoint, params], formatPathWithParams)

  // endpoint/params can be plain or observable so the formattedUrl can be as well.
  // but we need a plain value to use as a key in the xhrCache object
  let currentFormattedUrl
  formattedUrl.onValue && formattedUrl.onValue(url => currentFormattedUrl = url)
  const currentUrl = currentFormattedUrl || formattedUrl

  if (isGet && xhrCache[currentUrl]) return xhrCache[currentUrl]

  const body = U.thru(
    L.get('body', params),
    R.unless(
      R.isNil,
      R.pipe(
        L.getInverse(snakeCamelKeysIso),
        U.stringify
      )
    )
  )

  const request = url =>
    XHR.perform({ body, headers, method, responseType: 'json', timeout: 60 * 1000, url })

  const xhr = U.thru(
    formattedUrl,
    request,
    XHR.map(L.get(snakeCamelKeysIso)),
    XHR.tap(xhr => {
      store && store.set(xhr)
      delete xhrCache[currentUrl]
    })
  )

  if (isGet) xhrCache[currentUrl] = xhr

  return xhr
}
Jethro Larson
@jethrolarson
get([elems, whereEq({hot: true}), 0], stuff)
doesn't do what I'd expect
how do I index into a traversal?
Vesa Karvonen
@polytypic

You just want to get the first object that has {hot: true}? In that case

get(whereEq({hot: true}), stuff)

could already work as get only returns the first focus of a traversal and whereEq can traverse through (nested) arrays and objects, so elems is superfluous.

If you specifically want to read and write and write to a single element in an array then find is probably what you are looking for.

If the data structure is more complex than and array and you actually want whereEq and you want to only read and write the first focus, then you can combine whereEq with e.g. limit. Note that limit is linear time with respect to focuses produced by the traversal like subseq.

Jethro Larson
@jethrolarson
nth
0 was example but I want to index into the filtered list
in essence
Vesa Karvonen
@polytypic

To get nth hottest stuff, you might write

get(subseq(nth, nth + 1, whereEq({hot: true})), stuff)

Note that above subseq takes time linear in the number of elements found by whereEq (which itself is linear in the size of stuff).

Here is a playground.
Vesa Karvonen
@polytypic
I should also mention partsOf that can be used to turn the focuses of a traversal into an array. Here is a playground.
Jethro Larson
@jethrolarson
tyvm.
Vesa Karvonen
@polytypic
I recently started working on an Android project so I just had to try to convert this Epoxy sample to Calmm (not yet complete).
Vesa Karvonen
@polytypic
Added basic drag-and-drop (move) to the Calmm version.
Jonathan
@jonathanphz
hello - I have a question
I'm using U.Select and need to set it to the current location. The problem is that on first load the location takes a second so it's undefined.
is there a way to change the value once the location loads?
Vesa Karvonen
@polytypic
Hmm... Is the location observable?
Jonathan
@jonathanphz
yes
I get a country code like 'US' and the select has a list with a mapping of country code = country name
When I reload the page select has the correct country as it is cached but not on first load because it takes a moment to fetch the code.
Jonathan
@jonathanphz
got it to work
:)
with help from a friend
Vesa Karvonen
@polytypic
:thumbsup:
One thing I'd recommend is that to make a minimalist example of the problem case using e.g. CodeSandbox. That way anyone can take a look and fork the sandbox to attempt a solution.
Vesa Karvonen
@polytypic

Hmm... I wonder whether CodeSandbox has any simple way to change/update the template or "environment" used as a base for a sandbox?

Unfortunately most of my Calmm sample sandboxes were made a long time ago and they are based on old versions of create-react-app template. The problem with that is that the old version of the template does not work so nicely when you export it. I just converted the Color Carousel sample to use the latest(?) create-react-app by manually creating a new sandbox from the template and then manually copying the code to the new sandbox. Now it seems to work nicely when you export it as a .zip and use it on local machine.

Kurt Milam
@kurtmilam
:wave: Is there a nicer way to do something like this: https://bit.ly/2OcyrUy ?
Also, naming tips appreciated, specifically for mkValidatorLens, val and cur :)
The idea is to update two targets in parallel, where one target always has the most recent valid value, while the other has the current value, whether or not it's valid.
Kurt Milam
@kurtmilam
My earlier attempt wasn't quite right. I need a pair of lenses in my case, and this does the trick: https://bit.ly/2Df2mFs
The use-case, in order to avoid an X Y issue, is that I have an embedded SVG with a path superimposed on a grid. I allow the user to rotate the path by updating the path's transform:rotate attribute based on user input. The user can either input a (positive or negative) number of degrees in a standard text input, or they can use + and - buttons to increase or decrease rotation by one degree with each press. All works great, except when a user wants to input a negative rotation into the input. - is not a valid first argument to transform:rotate, and it causes havoc until the user adds a digit after the minus sign.
With this second incarnation, I use numeric.val for the buttons and the path's transform:rotate attribute, whereas I use numeric.cur for the text input, which does the trick. Am open to other solutions, though, as this doesn't feel idea.
Vesa Karvonen
@polytypic
Jaakko Karvonen
@JonPhno_gitlab

Hi! I was checking out the calmm.js documentation today, hoping to find a better solution for some form of state management between sharepoint online web parts. Browsing through this gitter, I found @rikutiira had a same use case two years ago (Dec 13 2017 20:52), where he tried to have components react to changes in sessionStorage, due to the components not having a common root.

my use case is that I want to have reactive storage where I can write stuff to sessionStorage and have my components react to it

Two years later, what would be a good way to implement this with calmm, where you can retrieve and modify (part of) the state using sessionStorage as the model? I'm quite new to both observables & sharepoint, so please bear with me :D

@polytypic beautiful name you have there, btw ;)