Last active
June 24, 2022 10:11
-
-
Save jednano/8169a94d58c5aa3571c58c42c0c9ef55 to your computer and use it in GitHub Desktop.
React Hooks and Render Props in TypeScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { FC, useCallback } from 'react' | |
import { connect } from 'react-redux'; | |
import addToCart from '../actions/cart' | |
import useAddToCart, { UseAddToCartOptions } from './useAddToCart' | |
interface DispatchProps { | |
onSubmit(options: UseAddToCartOptions): Promise<void>, | |
} | |
const AddToCartForm: React.FC<DispatchProps> = ({ onSubmit }) => { | |
const [ | |
{ errors, loading }, | |
{ onOptionSelected, onSubmit: _onSubmit }, | |
] = useAddToCart(onSubmit) | |
const _onOptionSelected = useCallback(( | |
e: React.MouseEvent<HTMLInputElement, MouseEvent>, | |
) => { | |
onOptionSelected({ [e.currentTarget.name]: e.currentTarget.value }) | |
}, [onOptionSelected]) | |
return ( | |
<form onSubmit={_onSubmit}> | |
<fieldset> | |
<legend>Size</legend> | |
{['S', 'M', 'L'].map((size, i) => ( | |
<input | |
key={i} | |
type="radio" | |
name="size" | |
value={size} | |
onClick={_onOptionSelected} | |
/> | |
))} | |
</fieldset> | |
{errors.map(({ message }) => <p>{message}</p>)} | |
<button type="submit" disabled={loading}> | |
{loading ? 'Adding...' : 'Add to Cart'} | |
</button> | |
</form> | |
) | |
} | |
export default connect<void, DispatchProps>( | |
null, | |
{ | |
onSubmit: addToCart, | |
}, | |
)(AddToCartForm) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ReactNode, useState } from 'react'; | |
export function AddToCartForm({ | |
onSubmit, | |
renderErrors, | |
renderOptions, | |
renderSubmit, | |
}: { | |
onSubmit: (options: Record<string, any>) => Promise<void>; | |
renderErrors(errors: Error[]): ReactNode; | |
renderOptions( | |
options: Record<string, any>, | |
errors: Error[], | |
onOptionSelected: (value: Record<string, any>) => void, | |
): ReactNode[]; | |
renderSubmit( | |
submitting: boolean, | |
): ReactNode; | |
}) { | |
const [errors, setErrors] = useState<Error[]>([]); | |
const [options, setOptions] = useState<Record<string, any>>({}); | |
const [submitting, setSubmitting] = useState(false); | |
return ( | |
<form onSubmit={_onSubmit}> | |
<fieldset> | |
<legend>Options</legend> | |
{renderOptions(options, errors, onOptionSelected)} | |
</fieldset> | |
{errors.length && renderErrors(errors)} | |
{renderSubmit(submitting)} | |
</form> | |
); | |
function onOptionSelected(value: Record<string, any>) { | |
setOptions({ ...options, ...value }) | |
} | |
async function _onSubmit() { | |
setErrors([]); | |
setSubmitting(true); | |
try { | |
return await onSubmit(options); | |
} catch (_errors) { | |
setErrors(_errors); | |
} finally { | |
setSubmitting(false); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useMemo, useState } from 'react' | |
type Options = Record<string, any> | |
export interface UseAddToCartProps { | |
onSubmit: (options: Options) => Promise<void> | |
} | |
export default function useAddToCart({ onSubmit }: UseAddToCartProps) { | |
const [errors, setErrors] = useState<Error[]>([]) | |
const [options, setOptions] = useState<Options>({}) | |
const [loading, setLoading] = useState(false) | |
// this isn't optimized | |
// it'll make a new callback every time options changes | |
const onOptionSelected = useCallback( | |
(value: Options) => { | |
setOptions({ ...options, ...value }) | |
}, | |
[options, setOptions] | |
) | |
// also not optimized | |
// it'll also make a new callback when options changes | |
const _onSubmit = useCallback( | |
async () => { | |
setErrors([]) | |
setLoading(true) | |
try { | |
return await onSubmit(options) | |
} catch (_errors) { | |
setErrors(_errors) | |
} finally { | |
setLoading(false) | |
} | |
}, | |
[onSubmit, options, setErrors, setLoading] | |
) | |
const state = useMemo( | |
() => ({ | |
errors, | |
loading, | |
options, | |
}), | |
[errors, loading, options] | |
) | |
const api = useMemo( | |
() => ({ | |
onOptionSelected, | |
onSubmit: _onSubmit, | |
setErrors, | |
setLoading, | |
setOptions, | |
}), | |
[_onSubmit, setErrors, setLoading, setOptions] | |
) | |
return [state, api] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment