Skip to content
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

[Question] Chain of Responsibility Pattern using custom Logic Builders? #160

Open
Tbhesswebber opened this issue Feb 19, 2024 · 2 comments

Comments

@Tbhesswebber
Copy link

I'm doing something admittedly weird in a side project and I ran into an issue that I managed to get around, but I don't understand the intent of custom LogicBuilders given the issue.

I have a multi-page, branching workflow that collects data in different ways depending on the path that you're on. You can think of the workflow as a directed, acyclic graph.

In order to support this, I have a top-level logic (let's call it SuperLogic) and then a couple of lower-level logics that are for a diverged path of the workflow (let's call them LogicA-LogicZ). I then wrote a function that returns a LogicBuilder calling other "core" builders (connect, listeners, etc) so that I could wire SuperLogic into each of LogicA-LogicZ via kea([connectSuperLogic({listenTo: "myAction", transformValues: transform})]) without SuperLogic needing to know anything about the dependent logics.

All of this seemed pretty straight-forward until I checked the UI and it seemed that my SuperLogic was never being connected to or updated. With only a little debugging, I realized that I could just directly invoke the core builders with the logic passed into the logic builder. Is this the expected way to handle this? Alternatively, I could do something like return an array of core builder calls instead of returning a LogicBuilder, but that seemed like it "isn't the kea way".

Example (simplified):

export function connectSuperLogic<T extends Logic = Logic>({listenTo, transformValues}: ConnectionConfig) {
  return (logic) => {
    connect(superLogic)(logic);
	listeners(({values}) => (
      {[listenTo]: superLogic.actions.grabSubForm(transformValues(values))}
    ))(logic);
  };
}
@mariusandra
Copy link
Member

Hey, one thing I immediately see that in the provided code, the listener should be a function, not calling the action directly:

export function connectSuperLogic<T extends Logic = Logic>({listenTo, transformValues}: ConnectionConfig) {
  return (logic) => {
    connect(superLogic)(logic);
    listeners(({values}) => (
      {[listenTo]: () => superLogic.actions.grabSubForm(transformValues(values))}
      // added "() =>" above 👈 
    ))(logic);
  };
}

However since this is a contrived example, I'll assume it's a typo in simplification.

Regarding:

Is this the expected way to handle this?

Yeah, I'd say so. The code looks good to me. Each standard function like actions({}) just returns a logic => {} function, which gets executed when the logic is built. You're also returning a builder with the same shape, and using it to modify the logic in question, which is correct.

The pattern of listeners(...)(logic) is also correct, and documented here: https://keajs.org/docs/meta/kea#logic-builders

@Tbhesswebber
Copy link
Author

Awesome, thanks for verifying! And yes, that was a typo in my simplification of things.

Re: listeners(...)(logic) - It might be useful to make the documentation a bit more explicit there rather than having it hidden at the end of the code block - I missed it entirely and ended up debugging to figure out how things are called. Happy to make a PR with the changes I would make if you want to see an example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants