qwik icon indicating copy to clipboard operation
qwik copied to clipboard

[🐞] Running multiple action$ simultaneously only runs one

Open genki opened this issue 2 years ago • 6 comments

Which component is affected?

Qwik City (routing)

Describe the bug

Doing: Trying to run() multiple actions when a button is clicked.

      <button onClick$={() => {
        foo.run();
        bar.run();
      }}>Foo Bar</button>

Expect: These actions have run.

Happened: Randomly selected only one action has run.

Reproduction

https://github.com/genki/qwik-test/tree/no_multi_actions

Steps to reproduce

git clone the the above and npm run dev The reproduction code is in src/router/index.tsx Please note this expects the latest qwik is located ~/project/clone/qwik

System Info

System:
    OS: macOS 13.1
    CPU: (8) arm64 Apple M2
    Memory: 84.31 MB / 24.00 GB
    Shell: 3.5.1 - /opt/homebrew/bin/fish
  Binaries:
    Node: 19.7.0 - /opt/homebrew/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 9.5.0 - /opt/homebrew/bin/npm
  Browsers:
    Chrome: 110.0.5481.177
    Firefox: 110.0.1
    Safari: 16.2
  npmPackages:
    @builder.io/qwik: file:~/project/clone/qwik/packages/qwik => 0.21.0
    @builder.io/qwik-city: file:~/project/clone/qwik/packages/qwik-city => 0.5.3
    undici: ^5.20.0 => 5.20.0
    vite: 4.0.4 => 4.0.4

Additional Information

I faced this issue as well at other situation such that several actions run at the same timing. For example, when one signal triggers several useTask$ at once and each of them runs different actions.

genki avatar Mar 11 '23 22:03 genki

Here's workaround I found for this issue:

Define the function that serializes execution of actions.

let prevAction = Promise.resolve();                             
export const enqueueAction = async (action, ...args) => {       
  await prevAction;                                  
  return prevAction = action.run(...args);        
};

Then call it instead of run(...) like this:

  // foo.run(args)
  // bar.run(args)
  enqueuAction(foo, args)
  enqueuAction(bar, args)

genki avatar Mar 12 '23 00:03 genki

man, I was going nuts with this issue

anyway, this solution seemed to work for me

useVisibleTask$(async () => {
    await firstAction.submit({ data });
    await secondAction.submit({ data });
  });

which is kinda simpler

AleksKislov avatar Jan 11 '24 15:01 AleksKislov

man, I was going nuts with this issue

anyway, this solution seemed to work for me

useVisibleTask$(async () => {
    await firstAction.submit({ data });
    await secondAction.submit({ data });
  });

which is kinda simpler

I think this is a simple and elegant solution. Thanks @AleksKislov for sharing.

gioboa avatar Jan 11 '24 17:01 gioboa

man, I was going nuts with this issue

anyway, this solution seemed to work for me

useVisibleTask$(async () => {
    await firstAction.submit({ data });
    await secondAction.submit({ data });
  });

which is kinda simpler

But this is not "simultaneously", they are sequential.

I have no context about this discussion, tho.

nelsonprsousa avatar Jan 11 '24 20:01 nelsonprsousa

But this is not "simultaneously", they are sequential.

well, yes, but at least it works for some cases.

But the thing that an action can interrupt other actions is a bummer

AleksKislov avatar Jan 12 '24 05:01 AleksKislov

Hello team! I don't know if I understood this issue correctly, but I just created this stackblitz https://stackblitz.com/edit/qwik-starter-gckxmmvh?file=src/routes/index.tsx,src/routes/layout.tsx with this example, and it works!

Just let me know I'm wrong @genki @gioboa

Thanks!

damianpumar avatar Jan 17 '25 21:01 damianpumar

I tested with Qwik v2 and this is still an issue. In the console I can see only the log bar and not both as expected.

import { component$ } from "@qwik.dev/core";
import { globalAction$ } from "@qwik.dev/router";

export const useAction1 = globalAction$(() => {
  console.log('foo');
});

export const useAction2 = globalAction$(() => {
  console.log('bar');
});

export default component$(() => {
  const action1 = useAction1();
  const action2 = useAction2();
  return (
    <div>
      <button onClick$={() => {
        action1.submit();
        action2.submit();
      }}>Foo Bar</button>
    </div>
  );
});

gioboa avatar Aug 22 '25 20:08 gioboa

@Varixo do you think our routeloader changes might help here?

wmertens avatar Sep 09 '25 09:09 wmertens