suzaku-io / diode Goto Github PK
View Code? Open in Web Editor NEWScala library for managing immutable application model
License: Apache License 2.0
Scala library for managing immutable application model
License: Apache License 2.0
As I tried to move over Diode, one of the errors I faced was passing action type as a parameter instead of an action object. With actions type expected to be of the AnyRef
type, there was no help from the compiler. I kept trying to figure out why my handler didn't get invoked.
Perhaps an example (not using Diode, but illustrates the problem, nevertheless) will help (which, btw, happens with Akka and similarly typed systems as well).
def handle: PartialFunction[AnyRef, Unit] = {
case Foo(i) => println(s"Received Foo with $i")
}
case class Foo(i: Int)
handle(Foo(5))
Received Foo with 5
scala> handle(Foo) // Ouch... no compiler error
scala.MatchError: Foo (of class Foo$)
at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:253)
at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:251)
at $anonfun$handle$1.applyOrElse(<console>:12)
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:36)
... 33 elided
With Diode, I don't get runtime error either since any unhandled type is eventually silently handled.
The solution is quite simple, I think. Just introduce a marker trait Action
and change all AnyRef
s expecting an action to Action
. Since Diode is very new I don't think that will pose too much burden on people already using it and I doubt that anyone will want to use arbitrary types to signify actions (for example, a String
type), so asking to extend Action
for all action type seems like a small price to pay for type safety.
On a related note, it will make reading Diode code easier. Currently, there are too many AnyRef
s out there and that type doesn't guide in figuring such the code.
I'm having trouble with diode's React interoperability, specifically in changes to a Circuit's state not triggering re-rendering of React elements. However, I fairly new to both Diode and React so the fault may be mine.
I have a widget that holds a list of values:
val DiodeClassificationBox = ReactComponentB[ModelProxy[Pot[PaperInfoShort]]]("ClassBox")
.render_P { case proxy =>
<.div(
TitleBar("Classification"),
ReadOnlyInputBar(proxy.value.get.mrPrim),
<.div(
^.onMouseUp --> proxy.dispatch(HighlightChange),
ReadOnlyInputBar((for(s <- proxy.value.get.secondaries) yield s.classCode).mkString(" "))
)
)
}.build
val ClassificationBox = SelectCircuit.connect(_.paperInfo)(p => DiodeClassificationBox(p))
And another widget that holds a selected value and is only rendered when that selected value matches one of the values in the above list. This selected value is set with the Callback "HighlightChange" above.
val DiodeChangeClassButton = ReactComponentB[ModelProxy[String]]("SaveButton")
.render_P { case proxy =>
if(proxy.value != "")
<.div(
^.cursor := "pointer",
^.onClick --> proxy.dispatch(ChangeClass(proxy.value)),
"String is: " + proxy.value
)
else
<.div()
}.build
val ChangeClassButton = SelectCircuit.connect(_.selection)(p => DiodeChangeClassButton(p))
Both of these are rendered inside a holder widget.
However, when HighlightChange is fired by making a selection and the selection string is changed in the Circuit state, the ChangeClassButton is not getting a render call (meaning the button does not appear as intended).
Am I incorrect in thinking that all element connected into SelectCircuit's "_.selection" should be getting a rerender call when _.selection changes?
Given the following code:
Effect.action(Http.call(url).map { result =>
Action1()
Action2()
}
It would be useful to be able to chain Action1 and Action2 without having to create an Action3 that combines the two.
Similar to how you have EffectSeq it would be useful to have ActionSeq so we can do:
Effect.action(Http.call(url).map { result =>
Action1() >> Action2()
}
So the latest versions e.g. 1.2.0.RC6 is not showing up on MVNRepository:
I'm following the example in the Usage with React docs that suggest storing the ReactConnectorProxy in the State (in the example it has case class State(component: ReactConnectProxy[Pot[String])
).
When doing that I am getting an "inferred existential type" warning. For example, doing
case class State(component: ReactConnectorProxy[Model])
results in
inferred existential type Option[japgolly.scalajs.react.ReactComponentC.ReqProps[diode.react.ModelProxy[Model] => japgolly.scalajs.react.ReactElement,Model,_$1,org.scalajs.dom.raw.Element]] forSome { type _$1 }, which cannot be expressed by wildcards, should be enabled
by making the implicit value scala.language.existentials visible.
This can be achieved by adding the import clause 'import scala.language.existentials'
or by setting the compiler option -language:existentials.
See the Scaladoc for value scala.language.existentials for a discussion
why the feature should be explicitly enabled.
There are many existing libraries:
One could also create their own lenses specifically for diode using macros/meta without using additional dependencies. Something like sauron.
The PotAction.handler() function does not work as per the examples - will not compile, Type mismatch.
Taking the example from Scala js App:
case class UpdateMotd(potResult: Pot[String] = Empty) extends PotAction[String, UpdateMotd] {
override def next(value: Pot[String]) = UpdateMotd(value)
}
class MotdHandler[M](modelRW: ModelRW[M, Pot[String]]) extends ActionHandler(modelRW) {
override def handle = {
case action: UpdateMotd =>
val updateF = action.effect(AjaxClient.welcomeMsg("User X"))(identity _)
action.handleWith(this, updateF)(PotAction.handler())
}
}
I see the compiler warning:
Type mismatch, expected: (action, ActionHandler[M, Pot[String]], Effect) => ActionResult[M], actual: (PotAction[Nothing, Nothing], ActionHandler[Nothing, Pot[Nothing]], Effect) => ActionResult[Nothing]
Hello, there is a published version of diode for ScalaJS 1.X
https://search.maven.org/artifact/io.suzaku/diode_sjs1_2.13/1.1.9/jar
but it looks like there is no diode-react for ScalaJS 1.X
Could we expect diode-react_sjs1_2.13 anytime soon ?
Hi,
when using PotAction.handler(progressInterval)
the original startTime is overwritten with every progress update:
case PotPending =>
if (value.isPending && progressInterval > Duration.Zero)
updated(value.pending()
the value.pending()
goes to
final case class Pending(startTime: Long = Pot.currentTime) {
override def pending(startTime: Long = Pot.currentTime) = copy(startTime)
}
Which always returns a Pot copy with the current time.
Shouldn't here the startTime be used from the existing instance? I thought to make a PR already but then I've seen that the tests explicitly test for this behaviour.
assert(pend.pending(startTime0).asInstanceOf[PendingBase].startTime == startTime0)
assert(pend.pending().asInstanceOf[PendingBase].startTime > startTime0)
Is my assumption wrong or how can I retrieve the overall duration of the Pending pot.
In the future, when native is mature enough.
If it does, I'm probably not searching hard enough but if not; I can help with creating it.
I find the tutorial quite useful but I think having an API documentation would still work better.
would it make sense to write something like:
class EffectSingle[A:ActionType] private[diode] (f: () => Future[A], ec: ExecutionContext) extends EffectBase(ec) {
override def run(dispatch: Any => Unit) = f().map(dispatch)(ec)
override def toFuture = f()
}
Dear diode-team,
first of all: This is a really great library. I just have one issue with implementing a model reset functionality. I need a way to reset the ENTIRE root model to the initial state. Sadly, all the zoomTo functions don't allow me to access the root model, because they claim "an illegal field reference". How would I implement an action handler that resets the entire model?
~Karl
In this code
object AppCircuit extends Circuit[RootModel] {
var model = RootModel(0)
val actionHandler: PartialFunction[AnyRef, ActionResult[RootModel]] = {
case Increase(a) => ModelUpdate(model.copy(counter = model.counter + a))
case Decrease(a) => ModelUpdate(model.copy(counter = model.counter - a))
case Reset => ModelUpdate(model.copy(counter = 0))
}
I think the model variable should be rename initialState, and changed to a value (val) instead. There should be a private model variable (var model) in the Circuit object that subsequence inheritant cannot access and change. This is to avoid misunderstanding that model is something that can be modify if see fit.
Hello,
I'm trying to work around multiple fetching of PotMap data during initial mount/render of React component. The code is very similar to examples here: https://diode.suzaku.io/advanced/PotCollection.html
I've tried to exclude (key, pot) pairs with Pending
state from update keys set:
def handle: PartialFunction[Any, ActionResult[M]] = {
case update: UpdateUserOrders =>
val current = value.seq
val keys = update.keys.filterNot { k =>
current.exists {
case (id, pot) if id == k => pot.isPending
case _ => false
}
}
if (keys.nonEmpty) {
val updateF = update.effect(AutowireClient[API].getUserOrders(keys).call())(_.mapValues(Ready(_)))
update.handleWith(this, updateF)(AsyncAction.mapHandler(keys))
} else noChange
}
But as soon as I'm adding noChange
as empty key set action result, model stops to update at all and stays in Pending
state forever.
Could you point to me what I'm doing wrong? Or maybe there is a better way to stop unnecessary updates of common keys?
I'm interested in using Pot within another framework (no offense meant against Diode! I may yet try it). If it were to happen I'm not sure if it would make sense to use Cats's Monad typeclass as a more standardized typeclass (the other framework I'm using, at least, uses Cats).
If I try to edit the title of a todo item, I get
diode-react-todomvc-opt.js:664 Uncaught TypeError: Cannot read property 'value' of null
.
Readme and Github have MIT but (old) License.md has Apache.
Quick and dirty rushed implementation. Could be useful for some ideas.
EDIT: Original gist deleted but I dug up an old commit which should work.
import java.util.Date
import diode.ActionResult.NoChange
import diode.data.AsyncAction
import diode.{ActionProcessor, ActionResult, Dispatcher}
class DebounceProcessor[M <: AnyRef] extends ActionProcessor[M] {
var cleanup = Seq[(Long, AnyRef)]()
var actionRefLastSent = Map[AnyRef, Long]()
val delta = 1000
def clean(now: Long): Unit = {
cleanup = cleanup.dropWhile { case (itemTime, ref) =>
actionRefLastSent.get(ref) match {
case None => {
// This action has already been cleaned. Drop current item.
true
}
case Some(lastSentTime) => {
if (itemTime < lastSentTime) {
// This item has been superseeded by a new action
true
}
else {
if (lastSentTime + delta <= now) {
actionRefLastSent -= ref
true
}
else {
// Item hasn't expired yet
false
}
}
}
}
}
}
override def process(dispatch: Dispatcher, action: Any, next: (Any) => ActionResult[M], currentModel: M): ActionResult[M] = {
val now = new Date().getTime
clean(now)
action match {
case a: AsyncAction[_, _] => {
if (actionRefLastSent.contains(a)) {
NoChange
}
else {
actionRefLastSent += a -> now
cleanup :+= now -> a
next(action)
}
}
case _ => {
next(action)
}
}
}
}
I tried to publish the latest code but it is failing (at least trying to do a RC) and got a failure
Unfortunately I don't have access to the secrets to change the settings
@ochrons Do you know if something has changed ?
Hello,
are there any plans of migrating this to scalajs-react 2.0?
Thanks :)
Hi,
https://diode.suzaku.io/examples/treeview/ is not working with error
diode.suzaku.io/:24 Uncaught TypeError: SimpleApp is not a function
at diode.suzaku.io/:24
Did anyone try to integrate diode with redux dev tools?
Are there some fundamental differences preventing from writing a "plugin"?
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en
Example for rationale:
Root model has a: Pot[A]
, where A
is a case class with some property prop: String
. We request a
to the server, updating the model to a.pending
.
We have a React control that has an <.input
bound to a.map(_.prop)
(resulting in a Pot[String]
, and we want to disable it (or display an Ajax animation next to it) if it is Pending
.
Similarly for flatMap
when prop
is an Option
.
I'm using the todomvc example to explain my problem:
Suppose you add a new action "InitTodos" and therefore you add a new case in the handler:
case InitTodos =>
val todos = List(Todo(TodoId.random, "first", false), Todo(TodoId.random, "second", false))
updated(todos)
and then consider that the TodoList loads a initial set of todos by dispatching a InitTodos action after mounting the component:
...
class Backend($: BackendScope[Props, State]) {
def mounted(props: Props) = {
println("initializing todos")
props.proxy.dispatch(InitTodos)
}
....
}
...
private val component = ReactComponentB[Props]("TodoList")
....
.componentDidMount(scope => scope.backend.mounted(scope.props))
.build
I would expect the list to be populated but instead I only observe the message in the console "initializing todos". If I dispatch the same action but manually, say for example, from a button, then the list is updated as expected.
Is this ok? Or maybe there is something wrong with what I'm doing?
Thanks! and sorry for my english.
Nice library, looks very promising. I came across a couple of minor issues:
examples/treeview> sbt "~fastOptJS" results in
error sbt.ResolveException: unresolved dependency: me.chrons#diode-core_sjs0.6_2.11;1.0.1-SNAPSHOT: not found
I changed build.sbt to reference "me.chrons" %%% "diode-core" % "1.0.0" instead of "me.chrons" %%% "diode-core" % "1.0.1-SNAPSHOT" and life was ok.
Also, I think http://ochrons.github.io/diode/examples/raf/ is not quite right at the moment... the pulldown is empty so it doesnt seem to do anything
I'm not entirely confident that my usage of RefTo
is correct here, but what I saw from the documentation I assume it might be a good fit for my use case. I added a simplified example that demonstrates the error on ScalaFiddle.
Hello, are there any plans for implementing Scala 3 support?
A toy example. Given the following model structure:
case class MsgDB(users: Seq[User])
case class User(id: String, msgs: Seq[String])
and an action:
case class Post(user: String, msg:String)
I'd like to handle a Post
action, but only if that user exists. Thinking in a lensy way, I'd like a:
-- | Traverses into a User's message list, if that user exists.
msgsT :: String -> Traversal' MsgDB [String]
is this possible with Diode's Model*
pseudo-Lenses?
Ideally I'd have:
val msgsT: ModelRW[MsgDB, Seq[String]] = ...
val postH = new ActionHandler(msgsT) {
override def handle = {
case Post(u,m) => updated(value :+ m)
}
}
Given the following code:
Effect.action(Action1) >> Effect(longRunningTask.map { result => {
println("MyAction");
MyAction(result)
})
MyAction will not be printed out and MyAction will not be triggered.
Given the following code:
def futureWork: Future[Action] = {
println("MyAction");
longRunningTask.map { result => MyAction(result) }
}
Effect.action(Action1) >> Effect(futureWork)
MyAction will be printed out but MyAction will not be triggered.
I sometimes get this message in the console when using PotMap
:
Warning: DiodeWrapper is accessing isMounted inside its render() function.
render() should be a pure function of props and state. It should never
access something that requires stale data from the previous render,
such as refs. Move this logic to componentDidMount and componentDidUpdate instead.
It looks like the problem is that the mapHandler
in AsyncActionRetriable
wants to set the value of the Pot
to Pending
right away. If this happens while rendering, React gets cranky.
As a React component that knows whether it needs the ModelProxy to be connected or wrapped, it would be nice if that aspect were type safe. In other words, it would be useful if the component could require a ConnectedModelProxy[A]
or WrappedModelProxy[A]
instead of ModelProxy[A]
.
Is there any way to use reusable functions for dispatch calls, to avoid repeated rendering of buttons etc.? (https://github.com/japgolly/scalajs-react/blob/master/doc/PERFORMANCE.md)
Currently I've defined a singleton for the action functions, and passing that object for every component that needs to dispatch actions. This leaves the dispatcher in the ModelProxy unused.
case class Actions(dispatcher: Dispatcher) {
val increase: ReusableFn0[Unit] = ReusableFn0(() => dispatcher(Increase(2)))
val decrease: ReusableFn0[Unit] = ReusableFn0(() => dispatcher(Decrease(1)))
val reset: ReusableFn0[Unit] = ReusableFn0(() => dispatcher(Reset))
}
val actions = Actions(AppCircuit)
val routerConfig: RouterConfig[Page] = RouterConfigDsl[Page].buildConfig { dsl =>
...
| staticRoute(root, HomePage) ~> renderR { router =>
AppCircuit.connect { model => model } { proxy =>
HomeView.Component(HomeView.Props("Home", proxy, actions, router))
}
}
...
And finally in the component:
class Backend(scope: BackendScope[Props, Unit]) {
def render(props: Props) = {
<.div(
<.p("Value = ", <.b(props.proxy.value.counter)),
ButtonGroup.Component(
Button(Button.Props(props.actions.increase), "Increase"),
Button(Button.Props(props.actions.decrease), "Decrease"),
Button(Button.Props(props.actions.reset), "Reset")))
}
}
(In case you are wondering where ReusableFn0, I defined a custom 0-arity ReusableFn. As such type does not exist in scalajs-react, I might be doing something wrong there.)
Hello,
The live demonstration available at http://ochrons.github.io/diode/examples/todomvc/#/ has a bug. Double clicking an item for editing it, as mentioned in the page itself, displays the editing field but as soon as any character is typed, the content gets reset to the original.
Tested in firefox and chrome.
Regards,
Oswaldo
Hi guys,
I was wondering if it's an issue or a desired behavior. I'm trying to subscribe to a zipped model but the update is never trigger. I've also try to override the implicits FastEq but nothing can do.
Here is what I've done to create this issue:
// model
case class Model(name: String, desc: String)
// action
case class ChangeName(name: String) extends Action
case class ChangeDesc(desc: String) extends Action
// circuit
object AppCircuit extends Circuit[Model] {
def initialModel = Model("", "")
def actionHandler: AppCircuit.HandlerFunction =
(model, action) => action match {
case ChangeName(a) => Some(ModelUpdate(model.copy( name = a )))
case ChangeDesc(a) => Some(ModelUpdate(model.copy( desc = a )))
case ChangeBoth(a, b) => Some(ModelUpdate(model.copy(name = a, desc = b)))
}
}
// test
val nameReader = AppCircuit.zoom(_.name)
val descReader = AppCircuit.zoom(_.desc)
val zipped = nameReader.zip(descReader)
AppCircuit.subscribe(zipped)(a => {
println(s"Someting has change: ${a.value}") // never called
})
AppCircuit.dispatch(ChangeName("a name"))
AppCircuit.dispatch(ChangeBoth("a name", "a description"))
First of all, congratulations to such a thoughtful library!
I have experienced in my usage just a small glitch so far. The same action is only handled by one of the handlers. Before slimming down my example, I noticed that CircuitTests (see link below) is comparing (wrongly) against the initial value 42, whereas it should compare to 43. I guess that's the reason why the test case doesn't fail and the bug is not showing up.
https://github.com/ochrons/diode/blob/master/diode-core/shared/src/test/scala/diode/CircuitTests.scala#L363
The issue revolves around this code:
https://github.com/suzaku-io/diode/blob/master/diode-core/shared/src/main/scala/diode/Circuit.scala#L298
If a handler is defined than baseHandler is never called which means that ActionBatch actions are never executed.
It seems like the code should be reversed where you check if it's an ActionBatch first and then default to the provided handler.
And if you try and manually handle the ActionBatch yourself in your own handler it won't work because the ActionBatch class is not a case class:
https://github.com/suzaku-io/diode/blob/master/diode-core/shared/src/main/scala/diode/Circuit.scala#L46
If I put Circuit.connect
in an iterator I get an error from React of the form
Warning: Each child in an array or iterator should have a unique "key" prop.
This happens even if my wrapped components have a key
I think this is because when doing a connect you endup with multiple instances of a component without a key
I checked the connect
code and I think it should contain a call:
.buildU.withKey("mykey").apply()
but I'm not sure what's the best way to pass the key argument. If you have any suggestions I could produce a PR
Hello.
According to Pot.scala, Pot have an abstract method Pot.pending(startTime: Long = ...)
.
But, in implementations, argument startTime
is ignored. For example, Unavailable
contains this:
override def pending(startTime: Long = new Date().getTime) = Pending()
Ready[T]
contains similar code:
override def pending(startTime: Long = new Date().getTime) = PendingStale(x)
Same for Pending
, PendingStale
and others.
This is designed for some reason or it should be fixed?
Thanks.
I saw that the changes to support scalajs-react 1.2 got merged to master and wondering if it's possible to release a new version with the upgraded dep.
Although the README already stated that we should use 1.1.6
, it doesn't really work out of the box as 1.1.6
isn't resolved in any repository as of the moment.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.