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.
Last active
March 20, 2024 16:34
-
-
Save notcod/ff7477a0c71dc95f442b85b1963a2b48 to your computer and use it in GitHub Desktop.
Qwik clientAction$
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const Form = ({ action, ...rest }: FormProps, key: string | null) => { | |
return jsx( | |
'form', | |
{ | |
...rest, | |
'preventdefault:submit': true, | |
onSubmit$: action.submit, | |
method: 'get', | |
}, | |
key, | |
); | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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