Skip to content

Instantly share code, notes, and snippets.

@secretorange
Created August 19, 2021 10:20
Show Gist options
  • Save secretorange/3946d622943b54f7cab4ef609f7314b2 to your computer and use it in GitHub Desktop.
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.
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