refactored form component
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
interface FormProps {
|
||||
parent: any
|
||||
_config: FormConfig<any>
|
||||
}
|
||||
|
||||
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;
|
||||
const Form: FC<FormProps> = ({ parent, _config }) => {
|
||||
type T = typeof parent;
|
||||
const { getState } = _config;
|
||||
|
||||
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) {
|
||||
let newState = {
|
||||
...this.state,
|
||||
[this.keys[idx]]: e.target['value' as keyof EventTarget]
|
||||
}
|
||||
// initial setup
|
||||
useEffect(() => {
|
||||
if (!config) setConfig({
|
||||
..._config,
|
||||
labels: _config.labels ?? _config.keys,
|
||||
dataTypes: _config.dataTypes ?? new Array(_config.keys?.length).fill("text"),
|
||||
});
|
||||
|
||||
this.state = newState;
|
||||
this.getState(newState);
|
||||
}
|
||||
if (!state) setState(_config.initialState);
|
||||
}, [])
|
||||
|
||||
updateRichText(txt: string, idx: number) {
|
||||
this.state = {
|
||||
...this.state,
|
||||
[this.keys[idx]]: txt
|
||||
}
|
||||
// usecallback handling
|
||||
useEffect(() => {
|
||||
state && getState(state);
|
||||
}, [state]);
|
||||
|
||||
this.getState(this.state);
|
||||
}
|
||||
// update methods
|
||||
function updateRichText(txt: string, idx: number) {
|
||||
if (!config) return;
|
||||
|
||||
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';
|
||||
setState((prev: T) => {
|
||||
return {
|
||||
...prev,
|
||||
[config.keys[idx]]: txt
|
||||
}
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user