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 { useNavigate, useParams } from "react-router-dom";
import { AuthContext, useAuthContext } from "../../context/AuthContext"; import { AuthContext, useAuthContext } from "../../context/AuthContext";
import { attemptLogin } from "../../util/apiUtils"; import { attemptLogin } from "../../util/apiUtils";
import { IUserAuth } from "../../schemas"; import { IUser, IUserAuth } from "../../schemas";
import { Button, Form, Page, Panel } from "../ui"; import { Button, Form, Page, Panel } from "../ui";
import { FormConfig } from "../ui/Form";
export default function Login() { export default function Login() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const redirect = params.get("redirect"); const redirect = params.get("redirect");
const { user, setUser } = useContext(AuthContext); const { user, setUser } = useContext(AuthContext);
const [form, setForm] = useState<JSX.Element>();
// setup and local state // setup and local state
const navigate = useNavigate(); const navigate = useNavigate();
const [form, setForm] = useState<JSX.Element>();
const [input, setInput] = useState<IUserAuth>({ email: '', password: '' }); const [input, setInput] = useState<IUserAuth>({ email: '', password: '' });
// retrieve and store state from form // retrieve and store state from form
const getFormState = useCallback((received: IUserAuth) => { const getFormState = useCallback((received: IUserAuth) => {
setInput(received); setInput(received);
}, []) }, [input])
const handleLogin = async () => { const handleLogin = async () => {
@@ -31,25 +32,35 @@ export default function Login() {
// check for logged in user and mount form // check for logged in user and mount form
useEffect(() => { useEffect(() => {
if (user) navigate('/'); 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 ( return (
<Page> <Page>
<h1>Hello! Nice to see you again.</h1> <h1>Hello! Nice to see you again.</h1>
<Panel extraStyles="form-panel"> <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> <Button onClick={handleLogin}>Log In</Button>
</Panel> </Panel>
<aside>Not registered yet? You can do that <a href="/register">here.</a></aside> <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 { ChangeEvent, FC, useEffect, useState } from "react"
import { v4 } from 'uuid'; import { v4 } from "uuid"
import RichText from "./RichText"; 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>>
**/
export interface FormConfig<T> { export interface FormConfig<T> {
parent: string parent: string
@@ -20,83 +13,95 @@ export interface FormConfig<T> {
extraStyles?: string extraStyles?: string
} }
export default class Form<T> { interface FormProps {
private parent: string; parent: any
private labels: string[]; _config: FormConfig<any>
private keys: string[]; }
private dataTypes: any[]
private state: T;
private getState: (received: T) => void
private richTextInitialValue?: string;
private extraStyles?: string
constructor(config: FormConfig<T>){ const Form: FC<FormProps> = ({ parent, _config }) => {
this.parent = config.parent; type T = typeof parent;
this.keys = config.keys; const { getState } = _config;
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(); const [config, setConfig] = useState<FormConfig<T>>();
} const [state, setState] = useState<T>();
const [contents, setContents] = useState<JSX.Element[]>();
update(e: ChangeEvent<HTMLElement>, idx: number) { // initial setup
let newState = { useEffect(() => {
...this.state, if (!config) setConfig({
[this.keys[idx]]: e.target['value' as keyof EventTarget] ..._config,
} labels: _config.labels ?? _config.keys,
dataTypes: _config.dataTypes ?? new Array(_config.keys?.length).fill("text"),
});
this.state = newState; if (!state) setState(_config.initialState);
this.getState(newState); }, [])
}
updateRichText(txt: string, idx: number) { // usecallback handling
this.state = { useEffect(() => {
...this.state, state && getState(state);
[this.keys[idx]]: txt }, [state]);
}
this.getState(this.state); // update methods
} function updateRichText(txt: string, idx: number) {
if (!config) return;
mount() { setState((prev: T) => {
let output = new Array<JSX.Element>(); return {
...prev,
for (let i = 0; i < this.keys.length; i++) { [config.keys[idx]]: txt
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)} />
</div>
)
} else {
input = (
<div id={`${this.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${this.parent}-${this.keys[i]}`}>{this.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}>
</input>
</div>
)
}
output.push(input);
}
return <div className={`ui-form-component ${this.extraStyles}`}>{output}</div>;
} }
}
function update(e: ChangeEvent<HTMLElement>, idx: number) {
if (!config) return;
setState((prev: T) => {
return {
...prev,
[config.keys[idx]]: e.target['value' as keyof EventTarget]
}
})
}
// mount the form once config has been loaded
useEffect(() => {
if (state && config) {
const result = config.keys.map((each: string, i: number) => {
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 {
return (
<div id={`${config.parent}-row-${i}`} key={v4()}>
<label htmlFor={`${config.parent}-${each}`}>{config.labels![i]}</label>
<input
type={config.dataTypes![i]}
id={`${config.parent}-${each}`}
onChange={(e) => update(e, i)}
value={state[i as keyof T] as string}>
</input>
</div>
)
}
});
setContents(result);
}
}, [config]);
return (
<div className={`ui-form-component ${_config.extraStyles}`}>
{ contents }
</div>
)
}
export default Form;