Skip to content

Instantly share code, notes, and snippets.

@notcod
Last active March 20, 2024 16:34
Show Gist options
  • Save notcod/ff7477a0c71dc95f442b85b1963a2b48 to your computer and use it in GitHub Desktop.
Save notcod/ff7477a0c71dc95f442b85b1963a2b48 to your computer and use it in GitHub Desktop.
Qwik clientAction$

Browser form action. When you need to submit form but you don't want to send request to server with regular routeAction$ Doesn't work when js disabled.

export const clientActionQrl = (actionQrl: QRL<(form: JSONObject, event: ClientEventAction) => ValueOrPromise<ClientActionResponse>>) => {
function action() {
const initialState: Editable<Partial<ClientAction>> = {
isRunning: false,
status: undefined,
value: undefined,
formData: undefined,
};
const loc = useLocation() as Editable<RouteLocation>;
const nav = useNavigate();
const state = useStore<Editable<Partial<ClientAction>>>(() => initialState);
const submit = $((input: unknown | FormData | SubmitEvent = {}) => {
let data: unknown | FormData | SubmitEvent;
let form: HTMLFormElement | undefined;
if (input instanceof SubmitEvent) {
form = input.target as HTMLFormElement;
data = new FormData(form);
if ((input.submitter instanceof HTMLInputElement || input.submitter instanceof HTMLButtonElement) && input.submitter.name) {
if (input.submitter.name) {
(data as FormData).append(input.submitter.name, input.submitter.value);
}
}
} else {
data = input;
}
return new Promise<ClientActionResponse>((resolve) => {
if (data instanceof FormData) {
state.formData = formDataToObject(data);
}
loc.isNavigating = true;
state.isRunning = true;
const fn = actionQrl(state.formData || {}, {
formData: data as FormData,
redirect: nav,
});
return resolve(fn);
}).then((response) => {
loc.isNavigating = false;
state.isRunning = false;
state.status = typeof response?.status === 'boolean' ? response?.status : undefined;
state.value = response?.result || response;
if (form) {
if (form.getAttribute('data-spa-reset') === 'true' && state.status) {
form.reset();
}
form.dispatchEvent(
new CustomEvent('submitcompleted', {
bubbles: false,
cancelable: false,
composed: false,
detail: response,
}),
);
}
return response;
});
});
initialState.submit = submit;
return state as ClientAction;
}
return action;
};
export const clientAction$ = implicit$FirstArg(clientActionQrl);
export const useClientActionQrl = (actionQrl: QRL<(form: JSONObject, event: ClientEventAction) => ValueOrPromise<ClientActionResponse>>) => {
return clientActionQrl(actionQrl)();
};
export const useClientAction$ = implicit$FirstArg(useClientActionQrl);
export function formDataToObject(formData: FormData): JSONObject {
const object: any = {};
formData.forEach((value: any, key: PropertyKey) => {
if (!Reflect.has(object, key)) {
object[key] = value;
return;
}
if (!Array.isArray(object[key])) {
object[key] = [object[key]];
}
object[key].push(value);
});
return object;
}
export const Form = ({ action, ...rest }: FormProps, key: string | null) => {
return jsx(
'form',
{
...rest,
'preventdefault:submit': true,
onSubmit$: action.submit,
method: 'get',
},
key,
);
};
export type ClientAction = {
isRunning: boolean;
status: boolean | undefined;
value: Record<string, any> | void | null | undefined;
formData: JSONObject | undefined;
submit: QRL<(input?: unknown | FormData | SubmitEvent) => Promise<void | Record<string, any> | null>>;
};
export type Editable<T> = {
-readonly [P in keyof T]: T[P];
};
export type ClientActionResponse =
| {
status: boolean;
result?: Record<string, any>;
}
| Record<string, any>
| void
| null;
export type ClientEventAction = {
formData: FormData | undefined;
redirect: (url: string) => void;
};
export type FormProps = {
action: ClientAction;
} & Omit<QwikJSX.IntrinsicElements['form'], 'action' | 'method'>;
export const useGloballyClientAction = clientAction$(async (form, event) => {
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log(form, form.name, event.location.url.pathname);
event.redirect('/next-page/');
return {
status: true,
result: 'Hello World!',
};
});
export const TestComponent1 = component$(() => {
const ACTION = useGloballyClientAction();
const loc = useLocation();
return (
<div>
<>{JSON.stringify(ACTION)}</>
<>{loc.isNavigating}</>
<Form action={ACTION}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</Form>
</div>
);
});
/////////
export const TestComponent2 = component$(() => {
const ACTION = useClientAction$(async (form, event) => {
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log(form, form.name, event.location.url.pathname);
event.redirect('/next-page/');
return {
status: true,
result: 'Hello World!',
};
});
const loc = useLocation();
return (
<div>
<>{JSON.stringify(ACTION)}</>
<>{loc.isNavigating}</>
<Form action={ACTION}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</Form>
</div>
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment