S
SolidJS8mo ago
dohn

Do something when a submission ends if there's more than 1 action

Hey everyone. For showing user feedback (like a toast) when an action was done I was checking the action.result inside a createEffect where action is the returned value from useSubmission(myAction) This has been working fine but breaks the moment I use more than one useSubmission in the same component. All effects run on success even if just one action was fired (in my case showing multiple toasts) What's the pattern for handling this?
10 Replies
Maciek50322
Maciek503228mo ago
Would be easier if you shared some code you already have. If your useSubmission isn't context signal I don't see why would it trigger other signals. I guess you have something like this?
function createSubmission(func) {
const [result, setResult] = createSignal();
return {
get result() { return result() },
performAction: () => {
setResult(func());
}
};
}

function Component() {
const action1 = createSubmission(() => 1);
const action2 = createSubmission(() => 2);

createEffect(() => {
console.log(action1.result);
})

createEffect(() => {
console.log(action2.result);
})

return (
<div>
<button onClick={action1.performAction} >
Action1
</button>
<button onClick={action2.performAction} >
Action2
</button>
</div>
);
}
function createSubmission(func) {
const [result, setResult] = createSignal();
return {
get result() { return result() },
performAction: () => {
setResult(func());
}
};
}

function Component() {
const action1 = createSubmission(() => 1);
const action2 = createSubmission(() => 2);

createEffect(() => {
console.log(action1.result);
})

createEffect(() => {
console.log(action2.result);
})

return (
<div>
<button onClick={action1.performAction} >
Action1
</button>
<button onClick={action2.performAction} >
Action2
</button>
</div>
);
}
Maciek50322
Maciek503228mo ago
Here's the example in playground https://playground.solidjs.com/anonymous/728692bc-1edc-429f-a48e-dfa2e78cafc2 In console you get only single action result on button click
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
dohn
dohnOP8mo ago
@Maciek50322 Thanks for your response! Note I'm talking about useSubmission exported by solid router, not a custom hook. Your example is pretty much the case, except with two useSubmission using a form and a formAction on a button to trigger different server actions.
function Component() {
const action1 = useSubmission(myServerAction1);
const action2 = useSubmission(myServerAction2);

createEffect(() => {
console.log(action1.result);
})

createEffect(() => {
console.log(action2.result);
})

return (
<form action={action1}>
<button type="submit">
Action1
</button>
<button type="submit" formAction={action2}>
Action2
</button>
</form>
);
}
function Component() {
const action1 = useSubmission(myServerAction1);
const action2 = useSubmission(myServerAction2);

createEffect(() => {
console.log(action1.result);
})

createEffect(() => {
console.log(action2.result);
})

return (
<form action={action1}>
<button type="submit">
Action1
</button>
<button type="submit" formAction={action2}>
Action2
</button>
</form>
);
}
Maciek50322
Maciek503228mo ago
Ok, I never really used solid router, but looking at source code I see that it uses useRouter (which uses context), which holds single signal for all sumbissions. I guess it then triggers all submissions' results when one of them changes. I also see that it can get some custom filter, that depends on sumbmission's input (idk what that really means, but maybe you can try work something out with that, take a look here https://github.com/solidjs/solid-router/blob/main/src/data/action.ts#L21 ) Seeing how it's done it was probably designated to have 1 submission per page
GitHub
solid-router/src/data/action.ts at main · solidjs/solid-router
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
Maciek50322
Maciek503228mo ago
As a workaround you can always check yourself if the result changed
createEffect((prevResult) => {
const result = action1.result;
if (prevResult && prevResult !== result) {
console.log(action1.result)
}
return result;
});
createEffect((prevResult) => {
const result = action1.result;
if (prevResult && prevResult !== result) {
console.log(action1.result)
}
return result;
});
peerreynders
peerreynders8mo ago
Can you console.log myServerAction1.url and myServerAction2.url? The behaviour suggests that they are identical.
dohn
dohnOP7mo ago
Thank you@peerreynders sorry for the late response! They are functions in the same file, but they are separate and the url does seem different
No description
peerreynders
peerreynders7mo ago
As it turns out, this is just a variation on the theme already established earlier The difference here is that you trigger on the true -> false transition of pending, so undefined or identical successive results aren't a problem; they will still trigger properly with each "new" result.
// file: src/routes/about.tsx (basic)
import { createEffect } from 'solid-js';
import { Title } from '@solidjs/meta';
import { useAction, useSubmission } from '@solidjs/router';
import { echo1, echo2 } from '../api';

const selectClass = (pending: boolean) => (pending ? 'u-optimistic' : '');

type EchoSubmission = ReturnType<
typeof useSubmission<[message: string], string>
>;

function makeEffectFn(submission: EchoSubmission, label: string) {
return (previous: boolean) => {
const pending = submission.pending;
// Only execute on pending true -> false transitions
if (pending || !previous) return pending;

if (!submission.error) {
console.log(label, submission.result);
}

return false;
};
}

export default function About() {
const submitEcho1 = useAction(echo1);
const submission1 = useSubmission(echo1);
createEffect(makeEffectFn(submission1, 'Effect1'), false);

const submitEcho2 = useAction(echo2);
const submission2 = useSubmission(echo2);
createEffect(makeEffectFn(submission2, 'Effect2'), false);

return (
<main>
<style>
{'.u-optimistic { color: whitesmoke; background-color: slategrey; }'}
</style>
<Title>About</Title>
<h1>About</h1>
<div class={selectClass(submission2.pending)}>
{submission2.pending ? submission2.input[0] : 'n/a'}
</div>
<button onClick={() => submitEcho2(new Date().toISOString())}>
Echo 2
</button>
<div class={selectClass(submission1.pending)}>
{submission1.pending ? submission1.input[0] : 'n/a'}
</div>
<button onClick={() => submitEcho1(new Date().toISOString())}>
Echo 1
</button>
</main>
);
}
// file: src/routes/about.tsx (basic)
import { createEffect } from 'solid-js';
import { Title } from '@solidjs/meta';
import { useAction, useSubmission } from '@solidjs/router';
import { echo1, echo2 } from '../api';

const selectClass = (pending: boolean) => (pending ? 'u-optimistic' : '');

type EchoSubmission = ReturnType<
typeof useSubmission<[message: string], string>
>;

function makeEffectFn(submission: EchoSubmission, label: string) {
return (previous: boolean) => {
const pending = submission.pending;
// Only execute on pending true -> false transitions
if (pending || !previous) return pending;

if (!submission.error) {
console.log(label, submission.result);
}

return false;
};
}

export default function About() {
const submitEcho1 = useAction(echo1);
const submission1 = useSubmission(echo1);
createEffect(makeEffectFn(submission1, 'Effect1'), false);

const submitEcho2 = useAction(echo2);
const submission2 = useSubmission(echo2);
createEffect(makeEffectFn(submission2, 'Effect2'), false);

return (
<main>
<style>
{'.u-optimistic { color: whitesmoke; background-color: slategrey; }'}
</style>
<Title>About</Title>
<h1>About</h1>
<div class={selectClass(submission2.pending)}>
{submission2.pending ? submission2.input[0] : 'n/a'}
</div>
<button onClick={() => submitEcho2(new Date().toISOString())}>
Echo 2
</button>
<div class={selectClass(submission1.pending)}>
{submission1.pending ? submission1.input[0] : 'n/a'}
</div>
<button onClick={() => submitEcho1(new Date().toISOString())}>
Echo 1
</button>
</main>
);
}
// file: src/api.ts (basic)
import {action} from '@solidjs/router';

const echo1 = action((message: string) => {
const p = new Promise<string>((resolve, _r) => setTimeout(resolve, 1000, message));
p.then((message) => console.log(message));
return p;
});

const echo2 = action((message: string) => {
const p = new Promise<string>((resolve, _r) => setTimeout(resolve, 2000, message));
p.then((message) => console.log(message));
return p;
});

export { echo1, echo2 };
// file: src/api.ts (basic)
import {action} from '@solidjs/router';

const echo1 = action((message: string) => {
const p = new Promise<string>((resolve, _r) => setTimeout(resolve, 1000, message));
p.then((message) => console.log(message));
return p;
});

const echo2 = action((message: string) => {
const p = new Promise<string>((resolve, _r) => setTimeout(resolve, 2000, message));
p.then((message) => console.log(message));
return p;
});

export { echo1, echo2 };
peerreynders
peerreynders7mo ago
This version explains a bit more of what is going on.
dohn
dohnOP7mo ago
@peerreynders thanks!
Want results from more Discord servers?
Add your server