Skip to content

Instantly share code, notes, and snippets.

@Ilgrim
Forked from jednano/AddToCartFormWithHooks.tsx
Created April 8, 2020 15:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ilgrim/484cc8f552f89a7b1b4209e549ac0003 to your computer and use it in GitHub Desktop.
Save Ilgrim/484cc8f552f89a7b1b4209e549ac0003 to your computer and use it in GitHub Desktop.
React Hooks and Render Props in TypeScript
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)
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);
}
}
}
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