Skip to content

Commit

Permalink
change useFields interface to avoid typecasting in consumers
Browse files Browse the repository at this point in the history
  • Loading branch information
karlkeefer committed Oct 11, 2022
1 parent 3b23d5f commit d5baa7c
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 25 deletions.
6 changes: 3 additions & 3 deletions react/src/Routes/Account/ChangePasswordForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import React, { useCallback } from 'react'
import { Form, Button, Message } from 'semantic-ui-react'

import API from 'Api'
import { useRequest, useFields, InputChangeHandler } from 'Shared/Hooks';
import { useRequest, useFields } from 'Shared/Hooks';

const ChangePasswordForm = () => {
const [loading, error, run, result] = useRequest({ success: false })
const [fields, handleChange] = useFields({ pass: '' })
const {fields, handleInputChange} = useFields({ pass: '' })

const handleSubmit = useCallback(() => {
run(API.updatePassword(fields))
Expand All @@ -29,7 +29,7 @@ const ChangePasswordForm = () => {
placeholder="Password"
required
value={pass}
onChange={handleChange as InputChangeHandler} />
onChange={handleInputChange} />
<Button primary fluid size="huge" type="submit">Update Password</Button>
</Form>
)
Expand Down
8 changes: 4 additions & 4 deletions react/src/Routes/LogIn/LogInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { Redirect, useLocation } from 'react-router-dom'
import { Form, Button, Message } from 'semantic-ui-react'

import API from 'Api'
import { useRequest, useFields, InputChangeHandler } from 'Shared/Hooks';
import { useRequest, useFields } from 'Shared/Hooks';
import { User } from 'Shared/Models'
import { UserContainer } from 'Shared/UserContainer'

const LogInForm = () => {
const location = useLocation<{ from: string }>()
const { user, setUser } = useContext(UserContainer)
const [loading, error, run] = useRequest<User>({} as User)
const [fields, handleChange, setFields] = useFields<User>({} as User)
const {fields, handleInputChange, setFields} = useFields<User>({} as User)

const handleSubmit = useCallback(() => {
run(API.login(fields), user => {
Expand All @@ -39,15 +39,15 @@ const LogInForm = () => {
placeholder="Email"
required
value={email}
onChange={handleChange as InputChangeHandler} />
onChange={handleInputChange} />
<Form.Input
size="big"
name="pass"
type="password"
placeholder="Password"
required
value={pass}
onChange={handleChange as InputChangeHandler} />
onChange={handleInputChange} />
<Button primary fluid size="huge" type="submit">Log In</Button>
</Form>
)
Expand Down
8 changes: 4 additions & 4 deletions react/src/Routes/Posts/PostForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { Redirect } from 'react-router'
import { Form, Message, Button } from 'semantic-ui-react'

import API from 'Api'
import { useRequest, useFields, TextAreaChangeHandler, InputChangeHandler } from 'Shared/Hooks';
import { useRequest, useFields } from 'Shared/Hooks';
import { Post } from 'Shared/Models'
import SimplePage from 'Shared/SimplePage';

const PostForm = () => {
const params = useParams<{ id: string }>();
const postID = Number(params.id);
const [loading, error, run] = useRequest<Post>({} as Post)
const [fields, handleChange, setFields] = useFields<Post>({} as Post)
const {fields, handleInputChange, handleTextAreaChange, setFields} = useFields<Post>({} as Post)
const [redirectTo, setRedirectTo] = useState('');

// if we have a post ID, fetch it
Expand Down Expand Up @@ -57,14 +57,14 @@ const PostForm = () => {
placeholder="Post Title"
required
value={title}
onChange={handleChange as InputChangeHandler} />
onChange={handleInputChange} />
<Form.TextArea
name="body"
rows={4}
placeholder="Post content"
required
value={body}
onChange={handleChange as TextAreaChangeHandler} />
onChange={handleTextAreaChange} />
<Button primary size="huge" type="submit">Save</Button>
{id && id > 0 &&
<Button negative size="huge" type="button" onClick={handleDelete}>Delete</Button>}
Expand Down
6 changes: 3 additions & 3 deletions react/src/Routes/Reset/ResetForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import React, { useCallback } from 'react'
import { Form, Button, Message } from 'semantic-ui-react'

import API from 'Api'
import { useRequest, useFields, InputChangeHandler } from 'Shared/Hooks';
import { useRequest, useFields } from 'Shared/Hooks';


const ResetForm = () => {
const [loading, error, run, result] = useRequest({ success: false })
const [fields, handleChange] = useFields({ email: '' })
const {fields, handleInputChange} = useFields({ email: '' })

const handleSubmit = useCallback(() => {
run(API.reset(fields))
Expand All @@ -30,7 +30,7 @@ const ResetForm = () => {
placeholder="Email"
required
value={email}
onChange={handleChange as InputChangeHandler} />
onChange={handleInputChange} />
<Button primary fluid size="huge" type="submit">Reset Password</Button>
</Form>
)
Expand Down
8 changes: 4 additions & 4 deletions react/src/Routes/SignUp/SignUpForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import React, { useCallback } from 'react'
import { Form, Button, Message } from 'semantic-ui-react'

import API from 'Api'
import { useRequest, useFields, InputChangeHandler } from 'Shared/Hooks';
import { useRequest, useFields } from 'Shared/Hooks';


const SignUpForm = () => {
const [loading, error, run, result] = useRequest({ success: false })
const [fields, handleChange] = useFields({ email: '', pass: '' })
const {fields, handleInputChange} = useFields({ email: '', pass: '' })

const handleSubmit = useCallback(() => {
run(API.signup(fields))
Expand All @@ -31,15 +31,15 @@ const SignUpForm = () => {
placeholder="Email"
required
value={email}
onChange={handleChange as InputChangeHandler} />
onChange={handleInputChange} />
<Form.Input
size="big"
name="pass"
type="password"
placeholder="Password"
required
value={pass}
onChange={handleChange as InputChangeHandler} />
onChange={handleInputChange} />
<Button positive fluid size="huge" type="submit">Create Account</Button>
</Form>
)
Expand Down
32 changes: 25 additions & 7 deletions react/src/Shared/Hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import { InputOnChangeData, TextAreaProps } from 'semantic-ui-react';

export type RunFunc<T> = (promise: Promise<any>, onSuccess?: (data: T) => void, onFailure?: Function) => void

export type InputChangeHandler = (event: ChangeEvent<HTMLInputElement>, data: InputOnChangeData) => void
export type TextAreaChangeHandler = (event: ChangeEvent<HTMLTextAreaElement>, data: TextAreaProps) => void


export const useRequest = <T extends Object>(initData: T): [boolean, string, RunFunc<T>, T] => {
const [data, setData] = useState(initData);
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -46,15 +42,37 @@ export const useRequest = <T extends Object>(initData: T): [boolean, string, Run
// You just have to set your input field's name attr appropriately
// e.g. w/ a schema like {person:{first_name:''}} you can do <input name="person.first_name"/>

export const useFields = <T extends Object>(initFields: T): [T, InputChangeHandler | TextAreaChangeHandler, Function] => {

export type InputChangeHandler = (event: ChangeEvent<HTMLInputElement>, data: InputOnChangeData) => void
export type TextAreaChangeHandler = (event: ChangeEvent<HTMLTextAreaElement>, data: TextAreaProps) => void

type ChangeData = {
name: string
type: string
value: string
checked?: boolean
}

export const useFields = <T extends Object>(initFields: T): {fields: T, handleInputChange: InputChangeHandler, handleTextAreaChange: TextAreaChangeHandler, setFields: (newFieldValues: T) => void} => {
const [fields, setFields] = useState(initFields)
const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>, { name, type, value, checked }: InputOnChangeData) => {

// changeHandler works for <Input> and <TextArea>, but the onChange field for semantic-ui form components
// has different type signatures so we have to create multiple handlers
const changeHandler = useCallback(({ name, type, value, checked }: ChangeData)=>{
setFields(f => {
let out = _.cloneDeep(f)
_.set(out, name, type === 'checkbox' ? checked : value);
return out
});
}, [setFields])

return [fields, handleChange, setFields];
const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>, cd: InputOnChangeData) => {
changeHandler(cd as ChangeData)
}, [changeHandler])

const handleTextAreaChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>, cd: TextAreaProps) => {
changeHandler(cd as ChangeData)
}, [changeHandler])

return {fields, handleInputChange, handleTextAreaChange, setFields};
}

0 comments on commit d5baa7c

Please sign in to comment.