refactored form component

This commit is contained in:
Mikayla Dobson
2023-02-11 17:35:11 -06:00
parent 3af0af8066
commit fd743825e2
2 changed files with 111 additions and 95 deletions

View File

@@ -2,23 +2,24 @@ import { useCallback, useContext, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { AuthContext, useAuthContext } from "../../context/AuthContext";
import { attemptLogin } from "../../util/apiUtils";
import { IUserAuth } from "../../schemas";
import { IUser, IUserAuth } from "../../schemas";
import { Button, Form, Page, Panel } from "../ui";
import { FormConfig } from "../ui/Form";
export default function Login() {
const params = new URLSearchParams(window.location.search);
const redirect = params.get("redirect");
const { user, setUser } = useContext(AuthContext);
const [form, setForm] = useState<JSX.Element>();
// setup and local state
const navigate = useNavigate();
const [form, setForm] = useState<JSX.Element>();
const [input, setInput] = useState<IUserAuth>({ email: '', password: '' });
// retrieve and store state from form
const getFormState = useCallback((received: IUserAuth) => {
setInput(received);
}, [])
}, [input])
const handleLogin = async () => {
@@ -31,25 +32,35 @@ export default function Login() {
// check for logged in user and mount form
useEffect(() => {
if (user) navigate('/');
setForm(
new Form<IUserAuth>({
parent: 'login',
keys: Object.keys(input),
labels: ["Email", "Password"],
dataTypes: Object.keys(input),
initialState: input,
getState: getFormState
}).mount()
);
}, [])
// useEffect(() => {
// setForm(
// )
// }, [getFormState])
useEffect(() => {
console.log(input);
}, [getFormState])
return (
<Page>
<h1>Hello! Nice to see you again.</h1>
<Panel extraStyles="form-panel">
{ form || <h2>Loading...</h2> }
<Form parent={input} _config={{
parent: 'login',
keys: Object.keys(input),
labels: ["Email", "Password"],
dataTypes: Object.keys(input),
initialState: input,
getState: getFormState
} as FormConfig<typeof input>} />
<Button onClick={handleLogin}>Log In</Button>
</Panel>
<aside>Not registered yet? You can do that <a href="/register">here.</a></aside>

View File

@@ -1,13 +1,6 @@
import { ChangeEvent, FC } from "react";
import { v4 } from 'uuid';
import RichText from "./RichText";
/**
* For the generation of more complex form objects with
* larger stateful values; expects to receive an object of
* type T to a form which can mutate T with a state setter
* of type Dispatch<SetStateAction<T>>
**/
import { ChangeEvent, FC, useEffect, useState } from "react"
import { v4 } from "uuid"
import RichText from "./RichText"
export interface FormConfig<T> {
parent: string
@@ -20,83 +13,95 @@ export interface FormConfig<T> {
extraStyles?: string
}
export default class Form<T> {
private parent: string;
private labels: string[];
private keys: string[];
private dataTypes: any[]
private state: T;
private getState: (received: T) => void
private richTextInitialValue?: string;
private extraStyles?: string
constructor(config: FormConfig<T>){
this.parent = config.parent;
this.keys = config.keys;
this.labels = config.labels || this.keys;
this.dataTypes = config.dataTypes || new Array(this.keys.length).fill('text');
this.state = config.initialState;
this.getState = config.getState;
this.richTextInitialValue = config.richTextInitialValue;
this.extraStyles = config.extraStyles;
this.mount();
interface FormProps {
parent: any
_config: FormConfig<any>
}
update(e: ChangeEvent<HTMLElement>, idx: number) {
let newState = {
...this.state,
[this.keys[idx]]: e.target['value' as keyof EventTarget]
const Form: FC<FormProps> = ({ parent, _config }) => {
type T = typeof parent;
const { getState } = _config;
const [config, setConfig] = useState<FormConfig<T>>();
const [state, setState] = useState<T>();
const [contents, setContents] = useState<JSX.Element[]>();
// initial setup
useEffect(() => {
if (!config) setConfig({
..._config,
labels: _config.labels ?? _config.keys,
dataTypes: _config.dataTypes ?? new Array(_config.keys?.length).fill("text"),
});
if (!state) setState(_config.initialState);
}, [])
// usecallback handling
useEffect(() => {
state && getState(state);
}, [state]);
// update methods
function updateRichText(txt: string, idx: number) {
if (!config) return;
setState((prev: T) => {
return {
...prev,
[config.keys[idx]]: txt
}
})
}
this.state = newState;
this.getState(newState);
function update(e: ChangeEvent<HTMLElement>, idx: number) {
if (!config) return;
setState((prev: T) => {
return {
...prev,
[config.keys[idx]]: e.target['value' as keyof EventTarget]
}
})
}
updateRichText(txt: string, idx: number) {
this.state = {
...this.state,
[this.keys[idx]]: txt
}
// mount the form once config has been loaded
useEffect(() => {
if (state && config) {
const result = config.keys.map((each: string, i: number) => {
this.getState(this.state);
}
mount() {
let output = new Array<JSX.Element>();
for (let i = 0; i < this.keys.length; i++) {
let input: JSX.Element | null;
if (this.dataTypes[i] == 'custom picker') {
console.log('noted!');
this.dataTypes[i] = 'text';
}
if (this.dataTypes[i] == 'TINYMCE') {
input = (
<div id={`${this.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${this.parent}-${this.keys[i]}`}>{this.labels[i]}</label>
<RichText id={`${this.parent}-${this.keys[i]}`} initialValue={this.richTextInitialValue} getState={(txt) => this.updateRichText(txt, i)} />
if (config.dataTypes![i] == 'TINYMCE') {
return (
<div id={`${config.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
<RichText id={`${config.parent}-${each}`} initialValue={config.richTextInitialValue} getState={(txt) => updateRichText(txt, i)} />
</div>
)
} else {
input = (
<div id={`${this.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${this.parent}-${this.keys[i]}`}>{this.labels[i]}</label>
return (
<div id={`${config.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
<input
type={this.dataTypes[i]}
id={`${this.parent}-${this.keys[i]}`}
onChange={(e) => this.update(e, i)}
value={this.state[i as keyof T] as string}>
type={config.dataTypes![i]}
id={`${config.parent}-${each}`}
onChange={(e) => update(e, i)}
value={state[i as keyof T] as string}>
</input>
</div>
)
}
});
output.push(input);
setContents(result);
}
}, [config]);
return (
<div className={`ui-form-component ${_config.extraStyles}`}>
{ contents }
</div>
)
}
return <div className={`ui-form-component ${this.extraStyles}`}>{output}</div>;
}
}
export default Form;