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 { 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>
|
||||||
|
|||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user