I am writing a little "fun" Scala/Scala.js project.
On my server I have Entities which are referenced by uuid
-s
(inside Ref-s).
For the sake of "fun", I don't want to use flux/redux architecture but still use React on the client (with ScalaJS-React).
What I am trying to do instead is to have a simple cache, for example:
UserDisplayComponent
wants the display the Entity
User
with uuid=0003
render()
method calls to the Cache
(which is passed in as a prop
) UserDisplayComponent
asks for this particular User
(with uuid=0003
) and the Cache
does not have it yetCache
makes an AjaxCall
to fetch the User
from the serverAjaxCall
returns the Cache
triggers re-render
User
from the Cache
, it gets the User
Entity
from the Cache
immediately and does not trigger an AjaxCall
The way I would like to implement this is the following :
render()
render()
asks the Cache
for all sorts of Entities
Cache
returns either Loading
or the Entity
itself.Cache
sends all the AjaxRequest
-s to the server and waits for all of them to returnAjaxRequests
have returned (let's assume that they do - for the sake of simplicity) the Cache
triggers a "re-render()
" and now all entities that have been requested before are provided by the Cache
right away.Entity
-s will trigger the render()
to fetch more Entity
-s if for example I load an Entity
that is for example case class UserList(ul: List[Ref[User]])
type. But let's not worry about this now.QUESTIONS:
1) Am I doing something really wrong if I am doing the state handling this way ?
2) Is there an already existing solution for this ?
I looked around but everything was FLUX/REDUX etc... along these lines... - which I want to AVOID for the sake of :
Consider what issues you need to address to build a rich dynamic web UI, and what libraries / layers typically handle those issues for you.
1. DOM Events (clicks etc.) need to trigger changes in State
This is relatively easy. DOM nodes expose callback-based listener API that is straightforward to adapt to any architecture.
2. Changes in State need to trigger updates to DOM nodes
This is trickier because it needs to be done efficiently and in a maintainable manner. You don't want to re-render your whole component from scratch whenever its state changes, and you don't want to write tons of jquery-style spaghetti code to manually update the DOM as that would be too error prone even if efficient at runtime.
This problem is mainly why libraries like React exist, they abstract this away behind virtual DOM. But you can also abstract this away without virtual DOM, like my own Laminar library does.
Forgoing a library solution to this problem is only workable for simpler apps.
3. Components should be able to read / write Global State
This is the part that flux / redux solve. Specifically, these are issues #1 and #2 all over again, except as applied to global state as opposed to component state.
4. Caching
Caching is hard because cache needs to be invalidated at some point, on top of everything else above.
Flux / redux do not help with this at all. One of the libraries that does help is Relay, which works much like your proposed solution, except way more elaborate, and on top of React and GraphQL. Reading its documentation will help you with your problem. You can definitely implement a small subset of relay's functionality in plain Scala.js if you don't need the whole React / GraphQL baggage, but you need to know the prior art.
5. Serialization and type safety
This is the only issue on this list that relates to Scala.js as opposed to Javascript and SPAs in general.
Scala objects need to be serialized to travel over the network. Into JSON, protobufs, or whatever else, but you need a system for this that will not involve error-prone manual work. There are many Scala.js libraries that address this issue such as upickle, Autowire, endpoints, sloth, etc. Key words: "Scala JSON library", or "Scala type-safe RPC", depending on what kind of solution you want.
I hope these principles suffice as an answer. When you understand these issues, it should be obvious whether your solution will work for a given use case or not. As it is, you didn't describe how your solution addresses issues 2, 4, and 5. You can use some of the libraries I mentioned or implement your own solutions with similar ideas / algorithms.
On a minor technical note, consider implementing an async, Future-based API for your cache layer, so that it returns Future[Entity]
instead of Loading | Entity
.