-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alternate implementation of events/handlers for codesandbox TodoMVC #71
Comments
What's the point of introducing the Redux action/creator function pattern? I can see some value in extracting the magic strings into constants for example, but I think mimicking common React patterns for the sake of familiarity is an antipattern for Crank which shouldn't be encouraged |
I think parts of the TodoMVC example are putting a lot of people off, and this is an attempt to show them that (at least some of) the things they are put off by can be easily remedied, and are not integral to crank.js. There's a lot of boilerplate involved in creating the I can see how "redux"-ifying the code could feel counter to crank.js's goals, but I also think it's important not to forget all the good things about the frameworks we've used before. They've lasted so long for a reason. Also, this is a good example for people to see that crank.js can feel like home for them coming from the frameworks they currently use, and it doesn't have to be a lot more typing and string constants. |
I like how you use the function reference as the switch condition. Would start without the ...args thing because it adds more indirection overhead than a POJO payload. And real-world entities tend to have more initial properties than one title, so you eventually add the POJO code path. What purpose does |
I like @geophree's version very much - I think this is actually the natural way to go, isn't it? Of course @geophree's way will cause more registered event handlers, but so what (same does Preact compared to React)?
This is actually a great idea when you are using TypeScript ... with TypeScript this switch (event.type) {
case Todo.create.type:
// Here, TypeScript will know exactly that the event is a "todo.create" CustomEvent...
...
case: Todo.edit.type:
// Here, TypeScript will know exactly that the event is a "todo.edit" CustomEvent...
...
} But finding the correct types to do so could be a bit challenging .... Last but not least, just as a remark: Of couse, if someone likes to save a few characters you could also use the following API (finding the proper TypeScript types will be even more challenging than with the other API and unfortunately you will have to add some additional information to the event object): const Todo = defineActions('todo', {
create: title => ({ title }),
edit: (id, title) => ({ id, title }),
destroy: (id, completed) => ({ id, completed }),
clearCompleted: null
}) |
@geophree Looks interesting and I encourage you to continue experimenting! Personally, I’m not a fan of this specific style of programming. At a glance, I’m just not smart enough to regularly think through higher-order functions which reference themselves in their return value. I prefer to write dumb code for my increasingly smooth brain and have left most functional programming tactics behind. Since you’ve decided to be the delegate for the people who have criticized Crank’s event system, let me respond to y’all, whoever you may be. I don’t understand the criticism that You’ve solved this additional “weakness” of Redux by using the actual function as reference, and this feels tempting, but a little analysis of what bugs you were trying to prevent makes me question the value of this approach. The class of bugs which people are worried about is: What if I listen to an event type but receive an unexpected event object/detail? However, insofar as the In short, welcome to JavaScript 😄 Branding your event details with the function which created it makes dispatching events of the wrong shape harder to do, but it also comes at the cost of having to import the Finally, this isn’t just Crank’s event system but the event system of the actual browser. The “boilerplate” you see is exactly what you’d need if you were doing vanilla JavaScript. I firmly believe in adopting conventions wherever possible, and Crank‘s EventTarget system tries to behave exactly as the actual EventTarget system does, except it operates at a component level. This makes the things you learn in Crank transferable to vanilla JS and vice versa. So when you criticize Crank’s event system, you’re actually criticizing the browser’s, which to be honest, is fair. But I’d much rather mimic an API and have people abstract on top of it if they feel it’s too “low-level,” as opposed to doing something different and forcing people to learn two different APIs. I think your example shows that this sort of abstraction is possible, and I encourage you to experiment! I’m a big fan of not telling people how to write their code when I don’t have to personally deal with it, and if this is what gets you excited, I am onboard. Just sharing my thoughts as a fellow developer is all. |
@brainkim I think the most important part in @geophree example is the fact that you can write this (patter 1): function* Counter() {
let count = 0
const onClick = () => {
++count
this.refresh()
}
while (true) {
yield (
<div>
<label>Counter: </label>
<button onclick={onClick}>
{count}
</button>
</div>
)
}
} instead of this (pattern 2): function* Counter() {
let count = 0
this.addEventListener('click', ev => {
if (ev.target.tagName === 'BUTTON') {
++count
this.refresh()
}
})
while (true) {
yield (
<div>
<label>Counter: </label>
<button>{count}</button>
</div>
)
}
} I prefer "Pattern 1" by far but honestly until I've seen @geophree's TodoMVC implementation I've always used "Pattern 2" just because stupid me really thought "Pattern 2" is the (one and only) way to handle events in Crank, because in all demos I've seen it's done that way. Are there any important reasons why you prefer "Pattern 2" to "Pattern 1" or is it just a matter of taste (what would be perfectly fine, of course)? |
@mcjazzyfunky I updated the docs to reflect the “Pattern 1” in https://crank.js.org/guides/components, but you may not have seen it. It definitely is a matter of taste. I like “Pattern 2” for reasons I detail in the following comments: #10 (comment) I’ll keep defending Pattern 2 but both styles of API are available. |
@brainkim Maybe you've catched me at not being good in reading the f****n' manual .... 😄 |
@brainkim BTW (a bit off-topic - a copyright question): I have this little web components toy library and I have taken you're TodoMVC demo and replaced all Crank components by custom elements. |
@mcjazzyfunky Yes of course! Everything is hopefully licensed under the MIT license and if there are copyright issues let me know. |
Updated with better-named link and gist link. https://codesandbox.io/s/crank-todomvc-alternative-jluse |
I think we're going to have to agree to disagree on this one. At least now you can point people here when they complain about it! Thanks again for making such an interesting framework. |
@geophree I sense a religious war brewing. And I’m not sure I’ll win 😄 |
When the 10th-generation |
The following may be a bit off-topic: If you are using action creators in TypeScript and also are interested in how they could be typed properly, then maybe the follwing could help you a bit - if not, it will surely bore you to death 😃 ... so feel free to quit at this point 😉 .... anyway... The main challenge regarding proper action (creator) typing is that if you have an action like follows {
type: 'todo.add',
...
} then the type of property 'type' must not be just const action = {
type: 'todo.add' as const,
...
} Otherwise TS type inference will not work exactly as you want. If you define your action creators the way @geophree did, proper TS typing will be possible: const Todo = {
create: defineAction('todo.create', (title: string) => ({ title })),
edit: defineAction('todo.edit', (id: number, title: string) => ({ id, title })),
...
} If it's okay in your use case that the type names are not like const Todo = defineActions({
create: (title: string) => ({ title }), // value of prop 'type': 'create'
edit: (id: number, title: string) => ({ id, title }), // value of prop 'type': 'edit'
...
}) But the following solution is NOT properly typeable with the current TS typing features (this is basically the alternative I've mentioned somewhere above, with the remark that there will be a lot of trouble with the typing of that): const Todo = defineActions('todo', {
create: (title: string) => ({ title }), // value of prop'type': 'toto.create'
edit: (id: number, title: string) => ({ id, title }), // value of prop'type': 'todo.edit'
...
}) The reason that the latter API is not properly typeable is that with the current TS type system you cannot build programmatically a new type Some workarounds like additional properties for the action object could help {
type: 'todo.create', // type: string
group: 'todo', // type: 'todo'
kind: 'create', // type: 'create'
...
} but this would be really confusing and at the end of the day IMHO that's not a good solution. To make a long story short: Use that action creator pattern that @geophree is using in his example 😉 (at least if you are using TypeScript) Please find here a demo with all mentioned patterns that are properly typeable (be aware that the actions/messages in this demo are not DOM CustomEvents but just simple data objects - but that does not really make a difference here). https://codesandbox.io/s/priceless-curran-xjbcz?file=/src/index.ts Thanks again @geophree ... your TodoMVC implementation was really a big help. |
Hope nobody minds me to write again a few words on this topic (I know it has been closed three weeks ago :-) ). What really bugs me a bit, is that we in the Crank community (same problem for other communities - but we are the cool community 😉 we should do better) seem to have a light tendency to mix statements that describe facts with statements that describe just personal opinions or taste. And quite often topics of personal taste are labeled as advantage of Crank itself. For example @geophree has mentioned somewhere above that using these And under really NO circumstances I'll believe that using Redux or pure functional state transitions in general is an anti-pattern for crank or any other UI library. Claiming that something that's impure, not really properly typeable, hard to test and that might even result in more lines of code is better than using mostly pure and perfectly testable functions and patterns that can be typed 100% properly in TypeScript is really really ambitious. I really understand that many in the Crank community prefer writing components without additional libraries, but that's fine for simple components and small project, but things will change quickly if the project increases in size, IMHO. Please have a look at a modification of @geophree's TodoMVC demo. It's just an opinion whether you like or hate this kind of programming, but it's a fact, that the main part of the demo is shorter than the original one (in the Crank repo), that things are better typeable, that the state transitions are way better testable and that there's a better separation between UI logic and business logic. And btw: Yes it's true that you (quote see above) "hav(e) to import the Todo helper object anywhere you want to dispatch those events" as this is part of the public API of the business logic. (of course this PS: @geophree I've used the shorter syntax for the action creator definitions here, but only to prove myself wrong that this was not typeable in TypeScript. You'll have some trouble in |
Okay, something else: I've taken this Crank+Redux TodoMVC demo from above: and converted it to React (franky I've cheated a tiny little bit to make it under 300 LOC ;-) ): I'm quite sure there are some bugs in both versions but for a simple comparision that's fine, I think. It's interesting that the React version is 300 to 500 characters longer (depending on how you count) and the Crank version will even be shorter as soon as a "crank-router" package will be available. My personal opinion: When you implement something in Crank and after that you implement something in React then these |
I (and others) have not been super comfortable with the way crank.js encourages using raw strings in events and event handlers. I re-wrote your codesandbox example using something a little closer to a "action creator" and "reducer switch statement" style of coding, as well as binding directly to the
on
-attributes of elements instead of usingaddEventHandler
everywhere. I thought it might be useful to show people an alternative without so manyaddEventHandler
s and raw strings.https://codesandbox.io/s/crank-todomvc-alternative-jluse
https://gist.github.com/geophree/a317c658fd6d7bc51e1e80aa5243be7d
Let me know what you think!
The text was updated successfully, but these errors were encountered: