Created
August 19, 2021 10:20
-
-
Save secretorange/3946d622943b54f7cab4ef609f7314b2 to your computer and use it in GitHub Desktop.
React Forms components that *mutate* the main object. Useful for large forms if you want performance and not fussed about immutability.
This file contains hidden or 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
import React, { useContext } from "react"; | |
const FormContext = React.createContext({ | |
obj: null, | |
events: null, | |
} as IFormContext); | |
export class FormEvents { | |
onChange?: (args: any) => void; | |
} | |
export interface IFormContext { | |
obj?: any; | |
events: any; | |
} | |
// Form arrays are used to group fields in arrays | |
const FormArrayContext = React.createContext({ | |
obj: null, | |
} as IFormArrayContext); | |
export interface IFormArrayContext { | |
obj?: any; | |
} | |
export interface IFieldProps { | |
name: string; | |
index?: number; | |
required?: boolean; | |
className?: string; | |
onChange?: React.ChangeEventHandler<HTMLInputElement>; | |
} | |
export interface IOption { | |
value: string; | |
name: string; | |
} | |
function getValue(obj: any, name: string, index: number | undefined): any { | |
return index !== undefined ? obj[name][index] : obj[name]; | |
} | |
function setValue( | |
obj: any, | |
name: string, | |
index: number | undefined, | |
value: any, | |
type: string | |
) { | |
if (type === "number") { | |
value = parseInt(value); | |
} | |
if (index !== undefined) { | |
obj[name][index] = value; | |
} else { | |
obj[name] = value; | |
} | |
} | |
function getCheckboxValue( | |
checkbox: any, | |
obj: any, | |
checkboxProps: ICheckboxProps | |
) { | |
let checkboxValue = checkbox.value; | |
let value; | |
if (checkboxProps.list) { | |
let array = obj[checkboxProps.name] || []; | |
const index = array.indexOf(checkboxProps.value); | |
if (checkbox.checked) { | |
// Ensure it's in the array | |
if (index == -1) { | |
array.push(checkboxValue); | |
} | |
} else { | |
// Ensure it's NOT in the array | |
if (index > -1) { | |
array.splice(index, 1); | |
} | |
} | |
value = array; | |
} else { | |
if (checkbox.checked) { | |
value = checkboxProps.value; | |
} else { | |
value = null; | |
} | |
} | |
return value; | |
} | |
function onChange( | |
e: any, | |
context: IFormContext, | |
props: IFieldProps, | |
obj: any, | |
type: string = "string" | |
) { | |
let value = e.target.value; | |
// The values of checkboxes work a bit differently | |
if (e.target.type === "checkbox") { | |
value = getCheckboxValue(e.target, obj, props as ICheckboxProps); | |
} | |
setValue(obj, props.name, props.index, value, type); | |
// Local component onChange | |
if (props.onChange) { | |
props.onChange(e); | |
} | |
// Form level onChange | |
if (context.events.onChange) { | |
context.events.onChange({ | |
name: props.name, | |
value: value, | |
event: e, | |
}); | |
} | |
} | |
export interface IFormProps { | |
children: JSX.Element[] | JSX.Element; | |
obj: any; | |
onChange?: (e: any) => void; | |
onSubmit: (e: any) => void; | |
} | |
export function Form({ children, obj, onChange, onSubmit }: IFormProps) { | |
const value: IFormContext = { | |
obj: obj, | |
events: { | |
onChange: onChange, | |
}, | |
}; | |
return ( | |
<FormContext.Provider value={value}> | |
<form onSubmit={(e) => !!onSubmit && onSubmit(e)}>{children}</form> | |
</FormContext.Provider> | |
); | |
} | |
function useFormContext() { | |
const context = useContext(FormContext) as IFormContext; | |
let obj = context.obj; | |
// Check to see if there is an array | |
const arrayContext = useContext(FormArrayContext) as IFormArrayContext; | |
if (arrayContext && arrayContext.obj) { | |
// Override the obj to use the obj in the array | |
obj = arrayContext.obj; | |
} | |
return { context, obj }; | |
} | |
export interface IFormArrayProps { | |
children: JSX.Element[] | JSX.Element; | |
obj: any; | |
} | |
export function FormArray({ children, obj }: IFormArrayProps) { | |
const value: IFormArrayContext = { | |
obj: obj, | |
}; | |
return ( | |
<FormArrayContext.Provider value={value}> | |
{children} | |
</FormArrayContext.Provider> | |
); | |
} | |
export interface ITextboxProps extends IFieldProps { | |
placeholder?: string; | |
type?: string; | |
} | |
export function Textbox(props: ITextboxProps) { | |
const { context, obj } = useFormContext(); | |
const type = props.type || "text"; | |
let variableType = "string"; | |
if (type === "number") { | |
variableType = "number"; | |
} | |
return ( | |
<input | |
name={props.name} | |
className={props.className} | |
placeholder={props.placeholder} | |
defaultValue={getValue(obj, props.name, props.index)} | |
onChange={(e) => onChange(e, context, props, obj, variableType)} | |
required={props.required} | |
type={type} | |
/> | |
); | |
} | |
export interface ITextareaProps extends IFieldProps { | |
placeholder?: string; | |
rows?: number; | |
} | |
export function Textarea(props: ITextareaProps) { | |
const { context, obj } = useFormContext(); | |
return ( | |
<textarea | |
name={props.name} | |
className={props.className} | |
placeholder={props.placeholder} | |
defaultValue={getValue(obj, props.name, props.index)} | |
onChange={(e) => onChange(e, context, props, obj)} | |
required={props.required} | |
rows={props.rows} | |
></textarea> | |
); | |
} | |
export interface ISelectProps extends IFieldProps { | |
options: IOption[]; | |
} | |
export function Select(props: ISelectProps) { | |
const { context, obj } = useFormContext(); | |
return ( | |
<select | |
name={props.name} | |
id={props.name} | |
className={props.className} | |
required={props.required} | |
defaultValue={obj[props.name]} | |
onChange={(e) => onChange(e, context, props, obj)} | |
> | |
{props.options?.map((option: IOption) => ( | |
<option key={option.value} value={option.value}> | |
{option.name} | |
</option> | |
))} | |
</select> | |
); | |
} | |
export interface IRadioProps extends IFieldProps { | |
value: any; | |
} | |
export function Radio(props: IRadioProps) { | |
const { context, obj } = useFormContext(); | |
return ( | |
<input | |
type="radio" | |
name={props.name} | |
value={props.value} | |
defaultChecked={obj[props.name] === props.value} | |
onChange={(e) => onChange(e, context, props, obj)} | |
className={props.className} | |
/> | |
); | |
} | |
export interface ICheckboxProps extends IFieldProps { | |
value: any; | |
list?: boolean; // Is it part of a checkbox list? | |
} | |
export function Checkbox(props: ICheckboxProps) { | |
const { context, obj } = useFormContext(); | |
return ( | |
<input | |
type="checkbox" | |
name={props.name} | |
value={props.value} | |
defaultChecked={obj[props.name] === props.value} | |
onChange={(e) => onChange(e, context, props, obj)} | |
className={props.className} | |
/> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment