react-server-actions
Version:
A package for working with actions in React and Next.js
96 lines • 3.43 kB
JavaScript
'use client';
import React, { useEffect, useRef } from 'react';
import { z } from 'zod';
import { dateToInputDefaultValue, getZodValidationAttributes, } from './helpers.js';
const FormContext = React.createContext({ state: undefined, schema: undefined, debug: false });
export const useForm = () => {
const context = React.useContext(FormContext);
if (!context.state || !context.schema)
return { state: undefined, schema: undefined, debug: false };
return {
state: context.state,
schema: context.schema,
debug: context.debug,
};
};
const FieldContext = React.createContext({
name: '',
id: '',
});
export const useField = () => {
'use no memo'; // the useField hook should not be memoized because the value will change
const { name, id } = React.useContext(FieldContext);
const { state, schema, debug } = useForm();
if (!state || !schema)
throw new Error('<FormField> must be used within a <Form>');
// Get validation attributes from schema
const { type, attrs } = getZodValidationAttributes(schema, name.split('.'));
// Create the field object
const field = {
invalid: state.invalid?.[name],
value: state.formData?.[name],
input: {
id: id,
name: name,
'aria-invalid': !!state.invalid?.[name],
autoComplete: undefined,
...attrs,
},
};
// Set the default value for mantaining the state across submissions
let defaultValue = state.formData?.[name];
if (defaultValue && defaultValue instanceof Date) {
defaultValue = dateToInputDefaultValue(defaultValue);
}
if (type === 'enum') {
field.input.defaultValue = defaultValue;
// TODO: This is not working if the input is a radio
// if the input is a radio, we don't know which of the inputs is this one
}
else if (type === 'boolean') {
if (defaultValue) {
field.input.defaultChecked = defaultValue;
}
else {
field.input.defaultChecked = false;
}
}
else if (type === 'file') {
// File inputs don't support default values, skip setting it
}
else {
field.input.defaultValue = defaultValue;
}
if (debug) {
console.log(field);
}
return field;
};
export function Form({ children, action, state, schema, className, reset, onSuccess, onError, debug, ...props }) {
const formRef = useRef(null);
if (reset !== false) {
if (formRef.current && state.success) {
formRef.current.reset();
}
}
useEffect(() => {
if (state.success) {
onSuccess?.(state.data, state.formData);
}
else if (state.error) {
onError?.(state.error);
}
}, [state, onSuccess, onError]);
return (React.createElement(FormContext.Provider, { value: { state, schema, debug } },
React.createElement("form", { action: action, ref: formRef, className: className, ...props }, children)));
}
export function FormField({ render, name, }) {
const id = React.useId();
return (React.createElement(FieldContext.Provider, { value: { name: name, id } },
React.createElement(FormFieldRenderer, { render: render })));
}
function FormFieldRenderer({ render, }) {
const field = useField();
return render(field);
}
//# sourceMappingURL=index.js.map